From b4ca9598003691853b5a8bfb5a05f4cdd8f2c2b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:46:12 +0000 Subject: [PATCH 01/80] build(deps): bump micromatch from 4.0.4 to 4.0.8 in /testnet-faucet Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.4 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.4...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] --- testnet-faucet/yarn.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/testnet-faucet/yarn.lock b/testnet-faucet/yarn.lock index f39a0205eb..e0d0539f4b 100644 --- a/testnet-faucet/yarn.lock +++ b/testnet-faucet/yarn.lock @@ -1541,7 +1541,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1: +braces@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -2792,12 +2792,12 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.3" + picomatch "^2.3.1" miller-rabin@^4.0.0: version "4.0.1" @@ -3075,10 +3075,10 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== postcss-value-parser@^4.2.0: version "4.2.0" From b28e953a2b2b5c277e319bdad9d2cc9af9e6240b Mon Sep 17 00:00:00 2001 From: Fran Arbanas Date: Tue, 12 Nov 2024 17:16:59 +0100 Subject: [PATCH 02/80] fix: validator-rewarder GH job --- .github/workflows/push-validator-rewarder.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-validator-rewarder.yaml b/.github/workflows/push-validator-rewarder.yaml index aa64899798..98508d4361 100644 --- a/.github/workflows/push-validator-rewarder.yaml +++ b/.github/workflows/push-validator-rewarder.yaml @@ -29,7 +29,7 @@ jobs: uses: mikefarah/yq@v4.44.3 id: get_version with: - cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml + cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml - name: Remove existing tag if exists run: | From d2e85f2bfe18a18e09d9624492c5cde4bf0e550d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:32:10 +0000 Subject: [PATCH 03/80] build(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /testnet-faucet Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] --- testnet-faucet/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testnet-faucet/yarn.lock b/testnet-faucet/yarn.lock index 9c97a04c98..5b18dd9a2c 100644 --- a/testnet-faucet/yarn.lock +++ b/testnet-faucet/yarn.lock @@ -1794,9 +1794,9 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: sha.js "^2.4.8" cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" From 27637ae6b4e65b8ede5c4b463ddd5266be3419cb Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:27:52 +0000 Subject: [PATCH 04/80] [DOCs/operators]: Routine guides update with release changes (#5173) * finish doc updates - ready for review * info to warning change * add non root guide and a new error * syntax fix * syntax edit --- .../outputs/api-scraping-outputs/time-now.md | 2 +- .../outputs/command-outputs/nym-api-help.md | 5 +- .../command-outputs/nym-node-run-help.md | 6 +- .../docs/pages/operators/changelog.mdx | 129 +++++++++++++++--- .../nodes/maintenance/manual-upgrade.mdx | 44 +++--- .../nodes/nym-node/configuration.mdx | 89 ++++++++++++ .../pages/operators/nodes/nym-node/setup.mdx | 9 +- .../node-api-check.mdx | 9 +- .../nodes/preliminary-steps/vps-setup.mdx | 3 + .../docs/pages/operators/tokenomics.mdx | 2 +- .../operators/tokenomics/mixnet-rewards.mdx | 2 +- 11 files changed, 239 insertions(+), 61 deletions(-) diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index a203dd666d..1d7a6e3d3b 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Wednesday, November 20th 2024, 15:53:00 UTC +Monday, November 25th 2024, 13:24:04 UTC diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-help.md b/documentation/docs/components/outputs/command-outputs/nym-api-help.md index ca6467eb19..fcaf94ce6b 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-help.md @@ -1,5 +1,4 @@ ```sh -2024-11-20T15:53:01.702819Z  INFO nym-api/src/main.rs:40: Starting nym api... Usage: nym-api [OPTIONS] Commands: @@ -10,9 +9,9 @@ Commands: Options: -c, --config-env-file - Path pointing to an env file that configures the Nym API [env: NYMAPI_CONFIG_ENV_FILE_ARG=] + Path pointing to an env file that configures the Nym API --no-banner - A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops) [env: NYMAPI_NO_BANNER_ARG=] + A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops) -h, --help Print help -V, --version diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md index 32508afe64..0a5070b615 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md @@ -44,8 +44,6 @@ Options: Specify whether detailed system crypto hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] --mixnet-bind-address Address this node will bind to for listening for mixnet packets default: `0.0.0.0:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] - --mixnet-announce-port - If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=] --nym-api-urls Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] --nyxd-urls @@ -54,14 +52,14 @@ Options: Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false] --wireguard-bind-address Socket address this node will use for binding its wireguard interface. default: `0.0.0.0:51822` [env: NYMNODE_WG_BIND_ADDRESS=] + --wireguard-private-ip + Private IP address of the wireguard gateway. default: `10.1.0.1` [env: NYMNODE_WG_IP=] --wireguard-announced-port Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=] --wireguard-private-network-prefix The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] --verloc-bind-address Socket address this node will use for binding its verloc API. default: `0.0.0.0:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] - --verloc-announce-port - If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=] --entry-bind-address Socket address this node will use for binding its client websocket API. default: `0.0.0.0:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] --announce-ws-port diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index ffcb57e8e0..8bb3e4f3b8 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -33,6 +33,101 @@ This page displays a full list of all the changes during our release cycle from + +## `v2024.13-magura-patched` + +- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.13-magura-patched) +- [`nym-node`](nodes/nym-node.mdx) version `1.1.11` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2024-11-22T14:30:48.067329245Z +Build Version: 1.1.11 +Commit SHA: 01c7b2819ee3d328deccd303b4113ff415d7e276 +Commit Date: 2024-11-22T10:50:59.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.82.0 +rustc Channel: stable +cargo Profile: release +``` + + + +After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Insstead of fixing this outdated explorer, we are working on a new one, coming out soon. + +[Nym Harbourmaster](https://harbourmaster.nymtech.net) has cache of 90min, expect your values to be updated with delay. We are aware of some issues with Nym Harbourmaster and working hard to resolve them in the upcoming explorer v2. To check your routing values in real time, you can use [`nym-gateway-probe`](nodes/performance-and-testing/gateway-probe). + + + +### Operators Updates & Tools + +- Updated [`network_tunnel_manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh) (moved to our monorepo) helps operators to configure their IP tables rules for `nymtun` and `wireguard` routing. + +- **Please re-run [routing configuration steps](https://nymtech.net/docs/operators/nodes/nym-node/configuration#routing-configuration) to update your routing settings.** + +- New manual how to [run `nym-node` as non-root](nodes/nym-node/configuration#running-nym-node-as-a-non-root) + +- Since `v2024.13-magura`, operators do not update their node version in the wallet. [Manual upgrading steps](nodes/maintenance/manual-upgrade.mdx) has been updated accordingly. + +- CLI tool [`node_api_check.py`](nodes/performance-and-testing/node-api-check.mdx), helping operators to collect all API values about their nodes locally, is not up to date with the API changes introduced with `v2024.13-magura` release version. Please treat it as unstable before we fix it. + +#### Error Log + +In case you encounter this error: +``` +[ERROR] nym-node/src/node/mod.rs:628: the exit gateway subtask has failed with the following message: failed to start authenticator: internal wireguard error no private IP set for peer.. +``` + +You can follow these steps to make a workaround: + +
+ + + +###### 1. Find the error + +- In the node logs, locate the ERROR message which says `the exit gateway subtask has failed with the following message: failed to start authenticator: internal wireguard error no private IP set for peer KN5GPvkC+p6G/SM4PD2Z3ObAtRGiDjHPRnQOPpbdUQk=` + +- Copy the end part of that peer, later denoted as `` (in our example `GiDjHPRnQOPpbdUQk=`) to use later in the sql commands + +###### 2. Fix the issue in sqlite3 db + + +Be careful when running commands within sqlite database. + + +- Navigate to the data directory: +```sh +cd $HOME/.nym/nym-nodes//data +``` +- Enter the database: +```sh +sqlite3 clients.sqlite +``` + +- Run these commands: +```sh +# Change with your unique +select * from wireguard_peer where public_key like "%%" +# Make sure that only ONE line is returned and it contains the key + +delete from wireguard_peer where public_key like "%%"; +``` +- Confirm that peer has been removed by running this again: +```sh +select * from wireguard_peer where public_key like "%%"; +``` + +###### 3. Exit and restart the service + +Run `.quit` and: +```sh +systemctl restart nym-node.service +``` + + + ## `v2024.13-magura` Magura release represents a bigger milestone in [project Smoosh](archive/faq/smoosh-faq.mdx) development where `nym-node` is one binary able to perform any function in Nym Mixnet. This release is especially crucial for operators, please pay attention to the section [*Operators Updates & Tooling*](#operators-updates--tooling) below. @@ -70,15 +165,15 @@ cargo Profile: release - [Switch over the last set of jobs to arc runners](https://github.com/nymtech/nym/pull/4938): Switch over the remaining GH jobs using 16-core runners to self-hosted arc runners. Since we can't currently use Docker on the ubuntu-20.04 runners, remove the matrix notification steps -}> +}> Confirm that the deployment workflows work through manual testing - [x] cd-docs - [x] publish-sdk-npm -- [V2 performance monitoring feature flag](https://github.com/nymtech/nym/pull/4943): Feature flag to use v2 network monitor results in rewarding +- [V2 performance monitoring feature flag](https://github.com/nymtech/nym/pull/4943): Feature flag to use v2 network monitor results in rewarding -- [Add `utoipa` feature to nym-node](https://github.com/nymtech/nym/pull/4945): `cargo build -p nym-node` was failing, since its depending on `QueryParams` having `utoipa` traits derived +- [Add `utoipa` feature to nym-node](https://github.com/nymtech/nym/pull/4945): `cargo build -p nym-node` was failing, since its depending on `QueryParams` having `utoipa` traits derived - [Ticket type storage](https://github.com/nymtech/nym/pull/4947) @@ -103,8 +198,8 @@ Confirm that the deployment workflows work through manual testing - [chore: remove unused rocket code](https://github.com/nymtech/nym/pull/4968) - [add Dockerfile for nym node](https://github.com/nymtech/nym/pull/4972) - -- [`Product Data` Add session type based on ecash ticket received](https://github.com/nymtech/nym/pull/4974): Fire an `EcashTicket` event for the `GatewayStatisticsCollector`, when an Ecash ticket is being accepted. This allows to mark an active session as being a mixnet session or a vpn session. It also changes the format of the related self-described data, to accommodate that new session type. + +- [`Product Data` Add session type based on ecash ticket received](https://github.com/nymtech/nym/pull/4974): Fire an `EcashTicket` event for the `GatewayStatisticsCollector`, when an Ecash ticket is being accepted. This allows to mark an active session as being a mixnet session or a vpn session. It also changes the format of the related self-described data, to accommodate that new session type. - [Top up bandwidth](https://github.com/nymtech/nym/pull/4975) @@ -147,7 +242,7 @@ Confirm that the deployment workflows work through manual testing - log gw identity key - better agent testrun logging - log responses on server side - - change response code for agents + - change response code for agents - update sqlx data - fix agent - probe gw bug @@ -165,7 +260,7 @@ Confirm that the deployment workflows work through manual testing - [Allow custom http port to be reset](https://github.com/nymtech/nym/pull/5073) -- [Fix gateway decreasing bandwidth](https://github.com/nymtech/nym/pull/5075): Make sure to update the storage after each decrease with the new values. Also set the storage values to 0 on restart for existing peers, as kernel peers can't have those values set to 0 +- [Fix gateway decreasing bandwidth](https://github.com/nymtech/nym/pull/5075): Make sure to update the storage after each decrease with the new values. Also set the storage values to 0 on restart for existing peers, as kernel peers can't have those values set to 0 - [Fix expiration date as today + 7 days](https://github.com/nymtech/nym/pull/5076) @@ -187,7 +282,7 @@ Confirm that the deployment workflows work through manual testing - [Add NYM_VPN_API to env files](https://github.com/nymtech/nym/pull/5099) -- [Feature/force refresh node](https://github.com/nymtech/nym/pull/5101): currently if nodes update their role from say mixnode to entry-gateway, it might take quite a while for `nym-api` to pick up the change and thus they might be losing performance. With this change, the node will be force refreshed on its startup +- [Feature/force refresh node](https://github.com/nymtech/nym/pull/5101): currently if nodes update their role from say mixnode to entry-gateway, it might take quite a while for `nym-api` to pick up the change and thus they might be losing performance. With this change, the node will be force refreshed on its startup - [`nym-credential-proxy-requests`: reqwest use rustls-tls](https://github.com/nymtech/nym/pull/5116) @@ -205,7 +300,7 @@ Confirm that the deployment workflows work through manual testing - [Fix critical issues SI84 and SI85 from Cure53](https://github.com/nymtech/nym/pull/4758): This pull request fixes the following issues: - NYM-01-009 WP5: BLS12-381 EC signature bypasses in Coconut library (Critical) - NYM-01-014 WP5: Partial signature bypass in offline eCash (Critical) - + - [bugfix: correctly paginate through 'search_tx' endpoint](https://github.com/nymtech/nym/pull/4936): when `results.append(&mut res.txs);` was called, `res.txs` was always empty thus it was impossible to return more than page size number of results - [Fix broken build after merge](https://github.com/nymtech/nym/pull/4937) @@ -214,7 +309,7 @@ Confirm that the deployment workflows work through manual testing - dedicated commands to request specific blocks for processing - decreased websocket failure timeout - ensuring we do actually have sufficient number of blocks to process rewarding for given epoch - - additional error logging + - additional error logging - [bugfix: fix expected return type on /v1/gateways endpoint](https://github.com/nymtech/nym/pull/4965) @@ -224,7 +319,7 @@ Confirm that the deployment workflows work through manual testing - Faulty aggregation to invalid offline eCash signatures - Signature forgery of Pointcheval-Sanders schema -- [bugfix: client memory leak](https://github.com/nymtech/nym/pull/4991): This fixes memory leaks in all the clients. however, they were most prominent in `nym-api` during network monitoring due to the sheer amount of packets being pushed +- [bugfix: client memory leak](https://github.com/nymtech/nym/pull/4991): This fixes memory leaks in all the clients. however, they were most prominent in `nym-api` during network monitoring due to the sheer amount of packets being pushed - [Fix rustfmt in nym-credential-proxy](https://github.com/nymtech/nym/pull/4992) @@ -279,7 +374,7 @@ Confirm that the deployment workflows work through manual testing - [bugfix: make sure to assign correct node_id and identity during 'gateway_details' table migration](https://github.com/nymtech/nym/pull/5142) - [bugifx: assign 'node_id' when converting from 'GatewayDetails' to 'TestNode'](https://github.com/nymtech/nym/pull/5143) - + ### Operators Updates & Tooling @@ -292,7 +387,7 @@ Confirm that the deployment workflows work through manual testing - **[Operators release & rewards roadmap](tokenomics/mixnet-rewards.mdx#roadmap)** -- **New [Operators landing pag e](https://nymtech.net/operators) +- **New [Operators landing page](https://nymtech.net/operators)** - [Nym Harbourmaster](https://harbourmaster.nymtech.net) had a new tab `NODE SEARCH` where operators can easily search nodes by identity keys and owner accounts and get all public information listed. @@ -335,7 +430,7 @@ Confirm that the deployment workflows work through manual testing - Confirm the transaction -###### 5. Welcome to new episode of `nym-node`! +###### 5. Welcome to new episode of `nym-node`! @@ -360,13 +455,13 @@ Confirm that the deployment workflows work through manual testing - Currently in *Native rewarding*, the rewards are split equally across the [rewarded set of nodes](https://validator.nymtech.net/api/v1/epoch/reward_params) (which now = active set and it's size is 240 nodes) for both Mixnet mode and dVPN mode. Every node being assigned 1 / 240 work factor (hence *naive rewarding*). -#### Directory Services v2.1: API & Mixnet Contract Changes +#### Directory Services v2.1: API & Mixnet Contract Changes -Magura release brings [breaking changes on API](https://github.com/nymtech/nym/pull/4903) logic of Nym. New APIs will only communicate with `nym-node` from this release and newer. Also old version of APIs won't be able to communicate with the new version of `nym-node`. We are also moving towards completely removing Nym Explorer API, which now has been only used to report nodes location. +Magura release brings [breaking changes on API](https://github.com/nymtech/nym/pull/4903) logic of Nym. New APIs will only communicate with `nym-node` from this release and newer. Also old version of APIs won't be able to communicate with the new version of `nym-node`. We are also moving towards completely removing Nym Explorer API, which now has been only used to report nodes location. Any new bonded node will provide only the bare minimum information: host, identity key and optionally custom port of its HTTP api - we highly recommend to set that one up to `8080`. Everything else will be discovered via the self-described API for maximum flexibility. This also includes the sphinx key, meaning if the API is not exposed, the node will be unable to route any traffic. Furthermore, this allows to arbitrary change of `nym-node` from mixnode into a gateway modes (and vice versa) without losing any delegations. -The contract changes also mean any node functionality can get rewards. Rather than just with assigned mixing roles, gateways now also added into the pool. However, to be eligible for gateway rewarding, one must [migrate into a `nym-node`](#wallet-changes) on a smart contract level (or bond a new node). +The contract changes also mean any node functionality can get rewards. Rather than just with assigned mixing roles, gateways now also added into the pool. However, to be eligible for gateway rewarding, one must [migrate into a `nym-node`](#wallet-changes) on a smart contract level (or bond a new node). ##### API High Level Changes diff --git a/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx b/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx index 9139ebc7e9..a68d01b1fd 100644 --- a/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx +++ b/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx @@ -14,30 +14,27 @@ This page explains how to upgrade [`nym-node`](#nym-node-upgrade) or [`validator ## Nym node Upgrade -**Upgrading your node is a straight forward two-step process:** - - - -#### 1. Updating the binary and `~/.nym/nym-nodes//config/config.toml` on your VPS - -#### 2. Updating the node information in the [mixnet smart contract](https://nymtech.net/docs/nyx/mixnet-contract.html). This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net). - - +Since `v2024.13-magura` (`nym-node v1.1.10`), **operators NO longer update node information in the Mixnet smart contract** (wallet version information), **only upgrade node binary** (on VPS), resulting in `~/.nym/nym-nodes//config/config.toml` update. Below are detailed steps how to do it: -###### 1. Upgrading node binary and information in config file +###### 1. Upgrade `nym-node` binary - Pause your node process. - If you run your node as `systemd` service (recommended), run: `service nym-node stop` - - Otherwise open the terminal window with your node logs and press `ctrl + c` + - Otherwise open the terminal window with your node logs and press once `ctrl + c` and wait for the node to terminate gracefully - Replace the existing `nym-node` binary with the newest binary (which you can either [compile yourself](../../binaries/building-nym.mdx) or [download](../../binaries/pre-built-binaries.mdx). +- To verify node version, run `./nym-node --version` + +###### 2. Restart the node + - [Re-run with the same values](../nym-node/setup.mdx#initialise--run) as you use to run your `nym-node`. If you want keep changes in your config file, use flag `-w` (`--write-changes`), **This will just update the config file, it will not overwrite existing keys**. - - If you automated your node with `systemd` (recommended) run: + +- If you automated your node with `systemd` (recommended), make sure you have all needed flags in `ExecStart` line of the service config file, and run: ```sh systemctl daemon-reload service nym-node start @@ -48,25 +45,19 @@ service nym-node start journalctl -f -u nym-node.service ``` -###### 2. Updating your node information in the smart contract -Follow these steps to update the information about your `nym-node` which is publicly available from the [`nym-api`](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [Mixnet explorer](https://explorer.nymtech.net). +###### 3. Check if your node is reporting the version correctly -You can either do this graphically via the Desktop Wallet, or the CLI. - - -
- Desktop Wallet (recommended), - CLI (superusers), - ]} defaultIndex="0"> - - - -
+- Open [Nym Harbourbourmaster](https://harbourmaster.nymtech.net), search your node and verify that everything is working as expected and your node shows expected version.
+ +After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Insstead of fixing this outdated explorer, we are working on a new one, coming out soon. + +[Nym Harbourmaster](https://harbourmaster.nymtech.net) has cache of 90min, expect your values to be updated with delay. We are aware of some issues with Nym Harbourmaster and working hard to resolve them in the upcoming explorer v2. To check your routing values in real time, you can use [`nym-gateway-probe`](../performance-and-testing/gateway-probe). + + ## Validator Upgrade Upgrading from `v0.31.1` -> `v0.32.0` process is fairly simple. Grab the `v0.32.0` release tarball from the [`nyxd` releases page](https://github.com/nymtech/nyxd/releases), and untar it. Inside are two files: @@ -92,4 +83,3 @@ The most common reason for your validator being jailed is that it runs out of me Running the command `df -H` will return the size of the various partitions of your VPS. If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. - diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx index ac41036ce6..63ba5b3f0d 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx @@ -426,6 +426,95 @@ journalctl -u nym-node.service -f -n 100 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). +## Running `nym-node` as a non-root + +Some operators prefer to run `nym-node` without root privileges. It's possible but still `nym-node` binary needs higher privileges for network-level operations demanding these permissions. Below is a guide how to go about such setup: + + +Copying nodes database and the `.nym/` directories from `/root/.nym` to `/home//.nym/` should be treated as experimental, therefore we would advise this section for operators starting new nodes, rather than tweaking an existing one. We will publish a detailed guide for changing permissions of an existing node soon. + + + +###### 1. Setup a new user + +- Define a variable `user_name` using your desired user name: +```sh +user_name="" +``` + +- Run: +```sh +user_home="/home/$user_name" + +if ! id "$user_name" &>/dev/null; then + sudo adduser --home "$user_home" --disabled-login --gecos "" "$user_name" +else + echo "user $user_name already exists" +fi +``` + +- And follow by: + +```sh +sudo usermod -aG sudo "$user_name" +``` + +- Optional: Add to sudoers group: +```sh +echo "$user_name ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/$user_name +``` + +###### 2. Grant needed permissions for network-level operations + +While `nym-node` will be set as a user process, it requires higher privileges for network-level operations, set them up with this command: + +```sh +sudo setcap 'cap_net_bind_service=+ep cap_net_admin=+ep' nym-node +``` + +**After replacing or upgrading the binary, you must reapply these permissions each time!** + +###### 3. Edit service config file + +- Add these new lines to your `/etc/systemd/system/nym-node.service` [service config file](#systemd) + - `After=network.target` + - `Group=` + - `Type=simple` + +- Your service file will then look like this: + +```ini +[Unit] +Description=Nym Node +After=network.target +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User= +Group= +Type=simple +LimitNOFILE=65536 +ExecStart=/nym-node run # add all the flags you use to run your node +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +###### 4. Reload and restart the service + +```sh +systemctl daemon-reload && service nym-node restart +``` + +- If you want to follow the logs, run: +```sh +journalctl -u nym-node -f +``` + ## Next Steps diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 8104e58fb3..31eb89308c 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -17,15 +17,16 @@ 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: 2024-11-18T17:02:50.947941194Z -Build Version: 1.1.10 -Commit SHA: b49ef643df86f0c670672429812c632fbbaf6cf1 -Commit Date: 2024-11-18T17:56:57.000000000+01:00 +Build Timestamp: 2024-11-22T14:30:48.067329245Z +Build Version: 1.1.11 +Commit SHA: 01c7b2819ee3d328deccd303b4113ff415d7e276 +Commit Date: 2024-11-22T10:50:59.000000000+01:00 Commit Branch: HEAD rustc Version: 1.82.0 rustc Channel: stable cargo Profile: release ``` + {/* COMMENTING THIS OUT ASS WE HAVE TO FIGURE OUT HOW TO SHOW THE LATEST VERSION FROM MASTER BRANCH */} diff --git a/documentation/docs/pages/operators/nodes/performance-and-testing/node-api-check.mdx b/documentation/docs/pages/operators/nodes/performance-and-testing/node-api-check.mdx index 3487984388..43867da088 100644 --- a/documentation/docs/pages/operators/nodes/performance-and-testing/node-api-check.mdx +++ b/documentation/docs/pages/operators/nodes/performance-and-testing/node-api-check.mdx @@ -8,6 +8,10 @@ import NodeApiCheckQueryHelp from 'components/outputs/command-outputs/node-api-c # Node API Check + +CLI tool `node_api_check.py`, helping operators to collect all API values about their nodes locally, is not up to date with the API changes introduced with `v2024.13-magura` release version. Please treat it as unstable before we fix it. + + Operating a `nym-node` is not a *"set and forget"* endeavor, it takes some work. To diagnose node network performance through querying APIs, is a good knowledge to have. These are the main places to look for API endpoints regarding `nym-node`: @@ -93,9 +97,9 @@ python --version -###### 2. Install `node_api_check.py` and make executable +###### 2. Install `node_api_check.py` and make executable -To run the program you neet to have [`node_api_check.py`](https://github.com/nymtech/nym/tree/develop/scripts/node_api_check.py) and [`api_endpoints.json`](https://github.com/nymtech/nym/tree/develop/scripts/api_endpoints.json). +To run the program you neet to have [`node_api_check.py`](https://github.com/nymtech/nym/tree/develop/scripts/node_api_check.py) and [`api_endpoints.json`](https://github.com/nymtech/nym/tree/develop/scripts/api_endpoints.json). - If you [compiled from source](../../binaries/building-nym.mdx), you already have both of these files. Note that the latest version of this program may be on `develop` branch. @@ -159,4 +163,3 @@ Another command is `version_count` where at least one `nym-node` version is requ ```sh ./node_api_check version_count 1.1.0 1.1.1 1.1.2 1.1.3 --markdown ``` - diff --git a/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx b/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx index 940872c24e..5864d3129d 100644 --- a/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx +++ b/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx @@ -232,6 +232,9 @@ username soft nofile 4096 Then reboot your server and restart your node. +## Running `nym-node` as a non-root + +Some operators prefer to run `nym-node` without root privileges. It's possible but still `nym-node` binary needs higher privileges for network-level operations demanding these permissions. If you are starting a new `nym-node` and want to run it as a non-root, follow [this guide](../nym-node/configuration#running-nym-node-as-a-non-root) before you proceed with the node setup sections. ## Ports reference tables diff --git a/documentation/docs/pages/operators/tokenomics.mdx b/documentation/docs/pages/operators/tokenomics.mdx index de8e2f66f8..0a65a0051f 100644 --- a/documentation/docs/pages/operators/tokenomics.mdx +++ b/documentation/docs/pages/operators/tokenomics.mdx @@ -161,7 +161,7 @@ In dVPN (2-hop) mode every node which meets the performance criteria, including In both cases, the selection algorithm also looks whether the node runs with [Terms & Conditions](nodes/nym-node/setup.mdx#terms--conditions) accepted **AND** if it's not a legacy binary version. In case either of these criterias are not met, the node will have be excluded from the rewarded set selection. -To read more about rewards calculation, please see next page [*Nym Operators Rewards*](mixnet-rewards.md). +To read more about rewards calculation, please see next page [*Nym Operators Rewards*](tokenomics/mixnet-rewards.md). ## Query Validator API diff --git a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx index 835339627a..facd3860f3 100644 --- a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx +++ b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx @@ -153,7 +153,7 @@ The entire active set selection probablity: For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1 and not 0):
- + > node_1 = 1.00 ^ 20 \* 1.0 = 1
> node_2 = 1.00 ^ 20 \* 0.5 = 0.5
> node_3 = 0.99 ^ 20 \* 1.0 = 0.818
From 5668e123d9da407b6c1bffd63b6a2f9f15dd960c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 25 Nov 2024 15:41:49 +0000 Subject: [PATCH 05/80] introduced initial internal commands for nym-cli: ecash key and request generation (#5174) * introduced initial internal commands for nym-cli: ecash key and request generation * reduced args logging level --- Cargo.lock | 2 + common/commands/Cargo.toml | 2 + .../internal/ecash/generate_keypair/mod.rs | 41 ++++++++++ common/commands/src/internal/ecash/mod.rs | 23 ++++++ .../src/internal/ecash/withdrawal_request.rs | 78 +++++++++++++++++++ common/commands/src/internal/mod.rs | 19 +++++ common/commands/src/lib.rs | 1 + .../src/scheme/keygen.rs | 9 +++ tools/nym-cli/src/internal/ecash/mod.rs | 26 +++++++ tools/nym-cli/src/internal/mod.rs | 20 +++++ tools/nym-cli/src/main.rs | 9 +++ 11 files changed, 230 insertions(+) create mode 100644 common/commands/src/internal/ecash/generate_keypair/mod.rs create mode 100644 common/commands/src/internal/ecash/mod.rs create mode 100644 common/commands/src/internal/ecash/withdrawal_request.rs create mode 100644 common/commands/src/internal/mod.rs create mode 100644 tools/nym-cli/src/internal/ecash/mod.rs create mode 100644 tools/nym-cli/src/internal/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d004324f62..ab3aa84ddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4752,12 +4752,14 @@ dependencies = [ "nym-coconut-dkg-common", "nym-config", "nym-contracts-common", + "nym-credential-proxy-requests", "nym-credential-storage", "nym-credential-utils", "nym-credentials", "nym-credentials-interface", "nym-crypto", "nym-ecash-contract-common", + "nym-ecash-time", "nym-id", "nym-mixnet-contract-common", "nym-multisig-contract-common", diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index c9bd11775d..d382cb696f 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -48,6 +48,7 @@ nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-cont nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } +nym-ecash-time = { path = "../../common/ecash-time" } nym-sphinx = { path = "../../common/nymsphinx" } nym-client-core = { path = "../../common/client-core" } nym-config = { path = "../../common/config" } @@ -56,6 +57,7 @@ nym-credentials-interface = { path = "../../common/credentials-interface" } nym-credential-storage = { path = "../../common/credential-storage" } nym-credential-utils = { path = "../../common/credential-utils" } nym-id = { path = "../nym-id" } +nym-credential-proxy-requests = { path = "../../nym-credential-proxy/nym-credential-proxy-requests" } nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } nym-types = { path = "../../common/types" } diff --git a/common/commands/src/internal/ecash/generate_keypair/mod.rs b/common/commands/src/internal/ecash/generate_keypair/mod.rs new file mode 100644 index 0000000000..7be40d986d --- /dev/null +++ b/common/commands/src/internal/ecash/generate_keypair/mod.rs @@ -0,0 +1,41 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Parser; +use log::trace; +use nym_credentials_interface::{generate_keypair_user, generate_keypair_user_from_seed, Base58}; +use serde::{Deserialize, Serialize}; +use std::io::stdout; + +#[derive(Serialize, Deserialize)] +pub struct Bs58EncodedKeys { + pub secret_key: String, + pub public_key: String, +} + +#[derive(Debug, Parser)] +pub struct Args { + /// Secret value that's used for deriving underlying ecash keypair + #[clap(long)] + pub(crate) bs58_encoded_client_secret: Option, +} + +pub fn generate_ecash_keypair(args: Args) -> anyhow::Result<()> { + trace!("args: {args:?}"); + + let keypair = if let Some(secret) = args.bs58_encoded_client_secret { + let seed = bs58::decode(&secret).into_vec()?; + generate_keypair_user_from_seed(&seed) + } else { + generate_keypair_user() + }; + + let encoded = Bs58EncodedKeys { + secret_key: keypair.secret_key().to_bs58(), + public_key: keypair.public_key().to_bs58(), + }; + + serde_json::to_writer_pretty(stdout(), &encoded)?; + + Ok(()) +} diff --git a/common/commands/src/internal/ecash/mod.rs b/common/commands/src/internal/ecash/mod.rs new file mode 100644 index 0000000000..7a9ab05d9b --- /dev/null +++ b/common/commands/src/internal/ecash/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::{Args, Subcommand}; + +pub mod generate_keypair; +pub mod withdrawal_request; + +#[derive(Debug, Args)] +#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] +pub struct InternalEcash { + #[clap(subcommand)] + pub command: InternalEcashCommands, +} + +#[derive(Debug, Subcommand)] +pub enum InternalEcashCommands { + /// Generate a dummy withdrawal request + GenerateWithdrawalRequest(withdrawal_request::Args), + + /// Generate dummy ecash keypair + GenerateKeypair(generate_keypair::Args), +} diff --git a/common/commands/src/internal/ecash/withdrawal_request.rs b/common/commands/src/internal/ecash/withdrawal_request.rs new file mode 100644 index 0000000000..bf56babdd9 --- /dev/null +++ b/common/commands/src/internal/ecash/withdrawal_request.rs @@ -0,0 +1,78 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Parser; +use log::trace; +use nym_credential_proxy_requests::api::v1::ticketbook::models::TicketbookRequest; +use nym_credentials_interface::{ + generate_keypair_user, withdrawal_request, Base58, SecretKeyUser, TicketType, +}; +use nym_ecash_time::{ecash_default_expiration_date, EcashTime}; +use serde::{Deserialize, Serialize}; +use std::io::stdout; +use time::macros::format_description; +use time::Date; +use zeroize::Zeroizing; + +fn parse_date(raw: &str) -> Result { + let format = format_description!("[year]-[month]-[day]"); + Date::parse(raw, &format) +} + +#[derive(Serialize, Deserialize)] +pub struct Bs58EncodedOutput { + pub ecash_proxy_request: TicketbookRequest, + pub ecash_secret: String, + + /// Needed to later unblind shares + pub ecash_request_info_bs58: String, +} + +#[derive(Debug, Parser)] +pub struct Args { + /// Specify which type of ticketbook + #[clap(long, default_value_t = TicketType::V1MixnetEntry)] + pub(crate) ticketbook_type: TicketType, + + /// Set expiration date for the ticketbook + #[clap(long, value_parser = parse_date, default_value_t = ecash_default_expiration_date())] + pub(crate) expiration_date: Date, + + /// Provide ecash secret key (or generate a fresh one) + #[clap(long)] + pub(crate) ecash_secret_key_bs58: Option, +} + +pub async fn generate_withdrawal_request(args: Args) -> anyhow::Result<()> { + trace!("args: {args:?}"); + + let ecash_keypair = if let Some(secret_key) = args.ecash_secret_key_bs58 { + let secret_key = Zeroizing::new(bs58::decode(Zeroizing::new(secret_key)).into_vec()?); + let sk = SecretKeyUser::from_bytes(&secret_key)?; + sk.into() + } else { + generate_keypair_user() + }; + + let (withdrawal_request, request_info) = withdrawal_request( + ecash_keypair.secret_key(), + args.expiration_date.ecash_unix_timestamp(), + args.ticketbook_type.encode(), + )?; + + let encoded = Bs58EncodedOutput { + ecash_proxy_request: TicketbookRequest { + withdrawal_request: withdrawal_request.into(), + ecash_pubkey: ecash_keypair.public_key(), + expiration_date: args.expiration_date, + ticketbook_type: args.ticketbook_type, + is_freepass_request: false, + }, + ecash_secret: ecash_keypair.secret_key().to_bs58(), + ecash_request_info_bs58: request_info.to_bs58(), + }; + + serde_json::to_writer_pretty(stdout(), &encoded)?; + + Ok(()) +} diff --git a/common/commands/src/internal/mod.rs b/common/commands/src/internal/mod.rs new file mode 100644 index 0000000000..44fa4d2659 --- /dev/null +++ b/common/commands/src/internal/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::{Args, Subcommand}; + +pub mod ecash; + +#[derive(Debug, Args)] +#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] +pub struct Internal { + #[clap(subcommand)] + pub command: InternalCommands, +} + +#[derive(Debug, Subcommand)] +pub enum InternalCommands { + /// Ecash related internal commands + Ecash(ecash::InternalEcash), +} diff --git a/common/commands/src/lib.rs b/common/commands/src/lib.rs index e3670ac572..f48415d7a6 100644 --- a/common/commands/src/lib.rs +++ b/common/commands/src/lib.rs @@ -3,5 +3,6 @@ pub mod context; pub mod ecash; +pub mod internal; pub mod utils; pub mod validator; diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index f5926d9cb6..e49c6309df 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -530,6 +530,15 @@ impl From for SecretKeyUser { } } +impl From for KeyPairUser { + fn from(value: SecretKeyUser) -> Self { + KeyPairUser { + public_key: value.public_key(), + secret_key: value, + } + } +} + impl KeyPairUser { #[allow(clippy::new_without_default)] pub fn new() -> Self { diff --git a/tools/nym-cli/src/internal/ecash/mod.rs b/tools/nym-cli/src/internal/ecash/mod.rs new file mode 100644 index 0000000000..ee9f8ef47c --- /dev/null +++ b/tools/nym-cli/src/internal/ecash/mod.rs @@ -0,0 +1,26 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_cli_commands::context::ClientArgs; +use nym_cli_commands::internal::ecash::InternalEcashCommands; +use nym_network_defaults::NymNetworkDetails; + +pub(super) async fn execute( + global_args: ClientArgs, + ecash: nym_cli_commands::internal::ecash::InternalEcash, + nym_network_details: &NymNetworkDetails, +) -> anyhow::Result<()> { + // I reckon those will be needed later + let _ = global_args; + let _ = nym_network_details; + + match ecash.command { + InternalEcashCommands::GenerateWithdrawalRequest(args) => { + nym_cli_commands::internal::ecash::withdrawal_request::generate_withdrawal_request(args) + .await + } + InternalEcashCommands::GenerateKeypair(args) => { + nym_cli_commands::internal::ecash::generate_keypair::generate_ecash_keypair(args) + } + } +} diff --git a/tools/nym-cli/src/internal/mod.rs b/tools/nym-cli/src/internal/mod.rs new file mode 100644 index 0000000000..253c45b227 --- /dev/null +++ b/tools/nym-cli/src/internal/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_cli_commands::context::ClientArgs; +use nym_cli_commands::internal::InternalCommands; +use nym_network_defaults::NymNetworkDetails; + +mod ecash; + +pub(super) async fn execute( + global_args: ClientArgs, + internal: nym_cli_commands::internal::Internal, + nym_network_details: &NymNetworkDetails, +) -> anyhow::Result<()> { + match internal.command { + InternalCommands::Ecash(ecash_commands) => { + ecash::execute(global_args, ecash_commands, nym_network_details).await + } + } +} diff --git a/tools/nym-cli/src/main.rs b/tools/nym-cli/src/main.rs index c5b4cc51e3..7854dbb0d2 100644 --- a/tools/nym-cli/src/main.rs +++ b/tools/nym-cli/src/main.rs @@ -9,6 +9,7 @@ use nym_validator_client::nyxd::AccountId; mod completion; mod ecash; +mod internal; mod validator; #[derive(Debug, Parser)] @@ -74,6 +75,11 @@ pub(crate) enum Commands { VestingSchedule(nym_cli_commands::validator::vesting::VestingSchedule), /// Manage your mixnet infrastructure, delegate stake or query the directory Mixnet(nym_cli_commands::validator::mixnet::Mixnet), + + #[clap(hide = true)] + /// Internal commands used for testing and debugging + Internal(nym_cli_commands::internal::Internal), + /// Generates shell completion GenerateFig, } @@ -118,6 +124,9 @@ async fn execute(cli: Cli) -> anyhow::Result<()> { Commands::Mixnet(mixnet) => { validator::mixnet::execute(args, mixnet, &network_details).await? } + Commands::Internal(internal_commands) => { + internal::execute(args, internal_commands, &network_details).await? + } Commands::GenerateFig => { let mut cmd = Cli::command(); completion::print_fig(&mut cmd); From 546a486f9f82c1179fd1aa6e95a8aae8f819f2b6 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Mon, 25 Nov 2024 17:45:10 +0100 Subject: [PATCH 06/80] script overhaul - improved iptables management: apply_iptables_rules and apply_iptables_rules_wg now automatically remove duplicate rules before reapplying them, ensuring a clean setup without disrupting iptables - consolidated joke feature: unified the "joke through the mixnet" logic into a generic function, allowing it to work seamlessly across any specified interface - enhanced tunnel checks: added check_nym_wg_tun alongside check_nymtun_iptables, making it easier to verify the state of both tunnels - reduced error-prone behavior: simplified workflows to avoid issues caused by running commands multiple times how to use: 1. download the script and make it executable: curl -L -o network_tunnel_manager.sh https://raw.download.github.of.this.file && chmod u+x network_tunnel_manager.sh 2. run the following commands as needed: - apply_iptables_rules: apply and clean iptables rules for nymtun0 - apply_iptables_rules_wg: apply and clean iptables rules for nymwg - check_ipv6_ipv4_forwarding: verify if ipv4 and ipv6 forwarding are enabled - check_ip_routing: display the current ipv4 and ipv6 routing tables tldr: - improved iptables handling to avoid duplicates - unified functionality for better maintainability - reduced potential errors when rerunning commands --- scripts/network_tunnel_manager.sh | 339 ++++++++++-------------------- 1 file changed, 115 insertions(+), 224 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 54ca4e653c..35ead017e3 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -11,11 +11,12 @@ else echo "iptables-persistent is already installed." fi -fetch_ipv6_address_nym_tun() { - ipv6_global_address=$(ip -6 addr show $tunnel_interface scope global | grep inet6 | awk '{print $2}' | head -n 1) +fetch_ipv6_address() { + local interface=$1 + ipv6_global_address=$(ip -6 addr show "$interface" scope global | grep inet6 | awk '{print $2}' | head -n 1) if [[ -z "$ipv6_global_address" ]]; then - echo "no globally routable IPv6 address found on $tunnel_interface. please configure IPv6 or check your network settings." + echo "no globally routable IPv6 address found on $interface. Please configure IPv6 or check your network settings." exit 1 else echo "using IPv6 address: $ipv6_global_address" @@ -23,90 +24,90 @@ fetch_ipv6_address_nym_tun() { } fetch_and_display_ipv6() { - ipv6_address=$(ip -6 addr show ${network_device} scope global | grep inet6 | awk '{print $2}') + ipv6_address=$(ip -6 addr show "$network_device" scope global | grep inet6 | awk '{print $2}') if [[ -z "$ipv6_address" ]]; then - echo "no global IPv6 address found on ${network_device}." - else - echo "IPv6 address on ${network_device}: $ipv6_address" + echo "no global IPv6 address found on $network_device." + elsen + echo "IPv6 address on $network_device: $ipv6_address" fi } adjust_ip_forwarding() { - ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1" - ipv4_forwarding_setting="net.ipv4.ip_forward=1" - echo "$ipv6_forwarding_setting" | sudo tee -a /etc/sysctl.conf - echo "$ipv4_forwarding_setting" | sudo tee -a /etc/sysctl.conf - sysctl -p /etc/sysctl.conf -} - -apply_iptables_rules_wg() { - echo "applying IPtables rules..." - echo "network device: ${network_device}" - echo "tunnel_interface: ${wg_tunnel_interface}" - sleep 2 - sudo iptables -A FORWARD -i "$wg_tunnel_interface" -o "$network_device" -j ACCEPT - sudo iptables -A FORWARD -i "$network_device" -o "$wg_tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - sudo ip6tables -A FORWARD -i "$network_device" -o "$wg_tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - sudo ip6tables -A FORWARD -i "$wg_tunnel_interface" -o "$network_device" -j ACCEPT - sudo iptables-save | sudo tee /etc/iptables/rules.v4 - sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 -} - -remove_iptables_rules_wg() { - echo "removing IPtables rules..." - echo "network device: ${network_device}" - echo "tunnel_interface: ${wg_tunnel_interface}" - sleep 2 - - # IPv4 rules removal wg - sudo iptables -D FORWARD -i "$wg_tunnel_interface" -o "$network_device" -j ACCEPT - sudo iptables -D FORWARD -i "$network_device" -o "$wg_tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - - # IPv6 rules removal wg - sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE - sudo ip6tables -D FORWARD -i "$network_device" -o "$wg_tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - sudo ip6tables -D FORWARD -i "$wg_tunnel_interface" -o "$network_device" -j ACCEPT - - sudo iptables-save | sudo tee /etc/iptables/rules.v4 - sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 + echo "adjusting IP forwarding settings..." + sudo sysctl -w net.ipv6.conf.all.forwarding=1 + sudo sysctl -w net.ipv4.ip_forward=1 } apply_iptables_rules() { - echo "applying IPtables rules..." - echo "network device: ${network_device}" - echo "tunnel_interface: ${tunnel_interface}" + local interface=$1 + echo "applying IPtables rules for $interface..." sleep 2 + + # remove duplicates for IPv4 + sudo iptables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true + sudo iptables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + sudo iptables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true + + # remove duplicates for IPv6 + sudo ip6tables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true + sudo ip6tables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true + + # add new rules for IPv4 sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE - sudo iptables -A FORWARD -i "$tunnel_interface" -o "$network_device" -j ACCEPT - sudo iptables -A FORWARD -i "$network_device" -o "$tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT + sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT + sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT + + # add new rules for IPv6 sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE - sudo ip6tables -A FORWARD -i "$network_device" -o "$tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - sudo ip6tables -A FORWARD -i "$tunnel_interface" -o "$network_device" -j ACCEPT - adjust_ip_forwarding + sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT + sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT + sudo iptables-save | sudo tee /etc/iptables/rules.v4 sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 } -remove_iptables_rules() { - echo "removing IPtables rules..." - echo "network device: ${network_device}" - echo "tunnel_interface: ${tunnel_interface}" +apply_iptables_rules_wg() { + local interface=$wg_tunnel_interface + echo "applying IPtables rules for WireGuard ($interface)..." sleep 2 - # IPv4 rules removal - sudo iptables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE - sudo iptables -D FORWARD -i "$tunnel_interface" -o "$network_device" -j ACCEPT - sudo iptables -D FORWARD -i "$network_device" -o "$tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT + # remove duplicates for IPv4 + sudo iptables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true + sudo iptables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + sudo iptables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - # IPv6 rules removal - sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE - sudo ip6tables -D FORWARD -i "$network_device" -o "$tunnel_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - sudo ip6tables -D FORWARD -i "$tunnel_interface" -o "$network_device" -j ACCEPT + # remove duplicates for IPv6 + sudo ip6tables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true + sudo ip6tables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true + + # add new rules for IPv4 + sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE + sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT + sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT + + # add new rules for IPv6 + sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE + sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT + sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT sudo iptables-save | sudo tee /etc/iptables/rules.v4 sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 } + +check_tunnel_iptables() { + local interface=$1 + echo "inspecting IPtables rules for $interface..." + echo "---------------------------------------" + echo "IPv4 rules:" + iptables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw-reject-forward"' + echo "---------------------------------------" + echo "IPv6 rules:" + ip6tables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw6-reject-forward"' +} + check_ipv6_ipv4_forwarding() { result_ipv4=$(cat /proc/sys/net/ipv4/ip_forward) result_ipv6=$(cat /proc/sys/net/ipv6/conf/all/forwarding) @@ -114,204 +115,94 @@ check_ipv6_ipv4_forwarding() { echo "IPv6 forwarding is $([ "$result_ipv6" == "1" ] && echo "enabled" || echo "not enabled")." } -check_nymtun_iptables() { - echo "network Device: $network_device" - echo "---------------------------------------" - echo - echo "inspecting IPv4 firewall rules..." - iptables -L FORWARD -v -n | awk -v dev="$network_device" '/^Chain FORWARD/ || /nymtun0/ && dev || dev && /nymtun0/ || /ufw-reject-forward/' - - echo "---------------------------------------" - echo - echo "inspecting IPv6 firewall rules..." - ip6tables -L FORWARD -v -n | awk -v dev="$network_device" '/^Chain FORWARD/ || /nymtun0/ && dev || dev && /nymtun0/ || /ufw6-reject-forward/' -} - -joke_through_the_mixnet() { - echo "checking Internet and mixnet connectivity (IPv4) via nymtun0..." - ipv4_address=$(ip addr show nymtun0 | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) - - if [ -z "$ipv4_address" ]; then - echo - echo "no IPv4 address found on nymtun0." - echo "please ensure IPv4 is configured correctly on your device." - echo "unfortunately, there's no joke for you :( and you might not be able to route IPv4 traffic through your gateway to the internet." - else - joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -c .joke) - - if [ -z "$joke" ] || [ "$joke" = "null" ]; then - echo "failed to fetch a joke. there might be an issue with the Internet connectivity or the joke service." - else - echo "joke fetched successfully:" - echo "$joke" - fi - fi - - echo "checking Internet and mixnet connectivity (IPv6) via nymtun0..." - ipv6_address=$(ip addr show nymtun0 | grep 'inet6 ' | awk '{print $2}' | cut -d'/' -f1 | grep -v '^fe80:') - - if [ -z "$ipv6_address" ]; then - echo - echo "no globally routable IPv6 address found on nymtun0." - echo "please ensure IPv6 is enabled on your VPS or configure your security groups/firewall settings appropriately." - echo "unfortunately there's no joke fo you :( and you can't route ipv6 traffic through your gateway to the internet" - else - joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -c .joke) - - if [ -z "$joke" ] || [ "$joke" = "null" ]; then - echo "failed to fetch a joke. there might be an issue with the Internet connectivity or the joke service." - else - echo "joke fetched successfully:" - echo "$joke" - fi - fi -} - -check_ip6_ipv4_routing() { - echo "---------------------------------------" - echo "examining IPv4 routing table..." +check_ip_routing() { + echo "IPv4 routing table:" ip route echo "---------------------------------------" - echo - echo "examining IPv6 routing table..." + echo "IPv6 routing table:" ip -6 route - echo } -perform_ipv4_ipv6_pings() { - echo "---------------------------------------" - echo "checking IPv4 connectivity (example: google.com)..." +perform_pings() { + echo "performing IPv4 ping to google.com..." ping -c 4 google.com echo "---------------------------------------" - echo - echo "checking IPv6 connectivity (example: google.com)..." + echo "performing IPv6 ping to google.com..." ping6 -c 4 google.com - echo } -configure_dns_and_icmp_wg() { - echo "allowing icmp (ping)..." - sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT - sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT +joke_through_tunnel() { + local interface=$1 + echo "checking tunnel connectivity and fetching a joke for $interface..." + ipv4_address=$(ip addr show "$interface" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) + ipv6_address=$(ip addr show "$interface" | grep 'inet6 ' | awk '{print $2}' | grep -v '^fe80:' | cut -d'/' -f1) - echo "allowing dns over udp (port 53)..." - sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT - - echo "allowing dns over tcp (port 53)..." - sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT - - echo "saving iptables rules..." - sudo iptables-save > /etc/iptables/rules.v4 - - echo "dns and icmp configuration completed." -} - -joke_through_wg_tunnel() { - echo "checking nymwg tunnel status..." - - tunnel_status=$(ip link show nymwg | grep -o "state [A-Z]*") - - if [[ $tunnel_status == "state UNKNOWN" ]]; then - echo "nymwg tunnel is up." - else - echo "nymwg tunnel is down." - echo "please check your nymwg tunnel configuration." + if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then + echo "no IP address found on $interface. Unable to fetch a joke." return fi - echo "checking internet and mixnet connectivity (ipv4) via nymwg..." - ipv4_address=$(ip addr show nymwg | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) - - if [ -z "$ipv4_address" ]; then - echo - echo "no ipv4 address found on nymwg." - echo "please ensure ipv4 is configured correctly on your device." - echo "unfortunately, there's no joke for you :( and you might not be able to route ipv4 traffic through your gateway to the internet." - else - joke=$(curl -s -H "accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -c .joke) - - if [ -z "$joke" ] || [ "$joke" = "null" ]; then - echo "failed to fetch a joke. there might be an issue with the internet connectivity or the joke service." - else - echo "joke fetched successfully:" - echo "$joke" - fi + if [[ -n "$ipv4_address" ]]; then + joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke) + [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv4 joke: $joke" || echo "Failed to fetch a joke via IPv4." fi - echo "checking internet and mixnet connectivity (ipv6) via nymwg..." - ipv6_address=$(ip addr show nymwg | grep 'inet6 ' | awk '{print $2}' | cut -d'/' -f1 | grep -v '^fe80:') - - if [ -z "$ipv6_address" ]; then - echo - echo "no globally routable ipv6 address found on nymwg." - echo "please ensure ipv6 is enabled on your vps or configure your security groups/firewall settings appropriately." - echo "unfortunately, there's no joke for you :( and you can't route ipv6 traffic through your gateway to the internet." - else - joke=$(curl -s -H "accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -c .joke) - - if [ -z "$joke" ] || [ "$joke" = "null" ]; then - echo "failed to fetch a joke. there might be an issue with the internet connectivity or the joke service." - else - echo "joke fetched successfully:" - echo "$joke" - fi + if [[ -n "$ipv6_address" ]]; then + joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke) + [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv6 joke: $joke" || echo "Failed to fetch a joke via IPv6." fi } case "$1" in fetch_ipv6_address_nym_tun) - fetch_ipv6_address_nym_tun + fetch_ipv6_address "$tunnel_interface" ;; fetch_and_display_ipv6) fetch_and_display_ipv6 ;; -check_nymtun_iptables) - check_nymtun_iptables - ;; apply_iptables_rules) - apply_iptables_rules + apply_iptables_rules "$tunnel_interface" ;; -remove_iptables_rules) - remove_iptables_rules +apply_iptables_rules_wg) + apply_iptables_rules "$wg_tunnel_interface" + ;; +check_nymtun_iptables) + check_tunnel_iptables "$tunnel_interface" + ;; +check_nym_wg_tun) + check_tunnel_iptables "$wg_tunnel_interface" ;; check_ipv6_ipv4_forwarding) check_ipv6_ipv4_forwarding ;; -check_ip6_ipv4_routing) - check_ip6_ipv4_routing +check_ip_routing) + check_ip_routing ;; -perform_ipv4_ipv6_pings) - perform_ipv4_ipv6_pings +perform_pings) + perform_pings ;; joke_through_the_mixnet) - joke_through_the_mixnet - ;; -apply_iptables_rules_wg) - apply_iptables_rules_wg + joke_through_tunnel "$tunnel_interface" ;; joke_through_wg_tunnel) - joke_through_wg_tunnel - ;; -configure_dns_and_icmp_wg) - configure_dns_and_icmp_wg + joke_through_tunnel "$wg_tunnel_interface" ;; *) - echo "usage: $0 [command]" - echo "commands:" - echo " fetch_ipv6_address_nym_tun - Fetches the IPv6 address assigned to the '$tunnel_interface'." - echo " fetch_and_display_ipv6 - Displays the IPv6 address on the default network device." - echo " apply_iptables_rules - Applies necessary IPv4 and IPv6 iptables rules." - echo " apply_iptables_rules_wg - Applies iptable rules for IPv4 and IPv6 for Wireguard." - echo " remove_iptables_rules - Removes applied IPv4 and IPv6 iptables rules." - echo " remove_iptables_rules_wg - Removes applied IPv4 and IPv6 iptables rules for Wireguard." - echo " check_ipv6_ipv4_forwarding - Checks if IPv4 and IPv6 forwarding are enabled." - echo " check_nymtun_iptables - Check nymtun0 device." - echo " perform_ipv4_ipv6_pings - Perform IPv4 and IPv6 pings to google." - echo " check_ip6_ipv4_routing - Check IPv6 and IPv4 routing." - echo " joke_through_the_mixnet - Run a joke through the mixnet via IPv4 and IPv6." - echo " joke_through_wg_tunnel - Run a wg test, and get a joke through the wg tunnel." - echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns" - echo "please provide one of the above commands." + echo "Usage: $0 [command]" + echo "Commands:" + echo " fetch_ipv6_address_nym_tun - Fetch IPv6 for nymtun0." + echo " fetch_and_display_ipv6 - Show IPv6 on default device." + echo " apply_iptables_rules - Apply IPtables rules for nymtun0." + echo " apply_iptables_rules_wg - Apply IPtables rules for nymwg." + echo " remove_iptables_rules - Remove IPtables rules for nymtun0." + echo " remove_iptables_rules_wg - Remove IPtables rules for nymwg." + echo " check_nymtun_iptables - Check IPtables for nymtun0." + echo " check_nym_wg_tun - Check IPtables for nymwg." + echo " check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding." + echo " check_ip_routing - Display IP routing tables." + echo " perform_pings - Test IPv4 and IPv6 connectivity." + echo " joke_through_the_mixnet - Fetch a joke via nymtun0." + echo " joke_through_wg_tunnel - Fetch a joke via nymwg." exit 1 ;; esac From 1b84639c342a66fc4907662def55a3a365fe6766 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Mon, 25 Nov 2024 17:48:03 +0100 Subject: [PATCH 07/80] re-add the configure icmp command --- scripts/network_tunnel_manager.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 35ead017e3..75866dd4e2 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -153,6 +153,23 @@ joke_through_tunnel() { fi } +configure_dns_and_icmp_wg() { + echo "allowing icmp (ping)..." + sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT + sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT + + echo "allowing dns over udp (port 53)..." + sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT + + echo "allowing dns over tcp (port 53)..." + sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT + + echo "saving iptables rules..." + sudo iptables-save > /etc/iptables/rules.v4 + + echo "dns and icmp configuration completed." +} + case "$1" in fetch_ipv6_address_nym_tun) fetch_ipv6_address "$tunnel_interface" @@ -187,6 +204,9 @@ joke_through_the_mixnet) joke_through_wg_tunnel) joke_through_tunnel "$wg_tunnel_interface" ;; +configure_dns_and_icmp_wg) + configure_dns_and_icmp_wg + ;; *) echo "Usage: $0 [command]" echo "Commands:" @@ -203,6 +223,7 @@ joke_through_wg_tunnel) echo " perform_pings - Test IPv4 and IPv6 connectivity." echo " joke_through_the_mixnet - Fetch a joke via nymtun0." echo " joke_through_wg_tunnel - Fetch a joke via nymwg." + echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns" exit 1 ;; esac From 5f3bb5db82ea16cc99234817ba09999b6a03c8dc Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Mon, 25 Nov 2024 17:52:49 +0100 Subject: [PATCH 08/80] remove command features --- scripts/network_tunnel_manager.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 75866dd4e2..6bb7d1275d 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -214,8 +214,6 @@ configure_dns_and_icmp_wg) echo " fetch_and_display_ipv6 - Show IPv6 on default device." echo " apply_iptables_rules - Apply IPtables rules for nymtun0." echo " apply_iptables_rules_wg - Apply IPtables rules for nymwg." - echo " remove_iptables_rules - Remove IPtables rules for nymtun0." - echo " remove_iptables_rules_wg - Remove IPtables rules for nymwg." echo " check_nymtun_iptables - Check IPtables for nymtun0." echo " check_nym_wg_tun - Check IPtables for nymwg." echo " check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding." From edd3f9108ae498917da5742786fe29632e8e2e9b Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:32:09 +0000 Subject: [PATCH 09/80] [DOCs/operators]: Guide to change wg private address (#5178) --- .../docs/pages/operators/changelog.mdx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 8bb3e4f3b8..b498b5156c 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -66,6 +66,41 @@ After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Expl - **Please re-run [routing configuration steps](https://nymtech.net/docs/operators/nodes/nym-node/configuration#routing-configuration) to update your routing settings.** +- We found out that some operators have a wrong value for wireguard IP. Follow these steps to ensure your value is set to `10.1.0.1` (default on new nodes): + + +###### 1. Open your node config file: +```sh +nano $HOME/.nym/nym-nodes//config/config.toml + +# change for your local nym moniker for example: +# nano $HOME/.nym/nym-nodes/default-nym-node/config/config.toml +``` +###### 2. Control or change the value of wireguard private IP +- Scroll down to section starting with `[wireguard]` +- Find line `private_ip` and ensure it's set to value `10.1.0.1` +- The section will look like this: +```toml +[wireguard] +# Specifies whether the wireguard service is enabled on this node. +enabled = true + +# Socket address this node will use for binding its wireguard interface. +# default: `0.0.0.0:51822` +bind_address = '0.0.0.0:51822' + +# Private IP address of the wireguard gateway. +# default: `10.1.0.1` +private_ip = '10.1.0.1' +``` +###### 3. Save, exit and restart the service +- If you used `nano` editor - press `ctrl` + `x` and confirm the changes +- Run these commands to update the service with new values and restart your node process: +```sh +systemctl daemon-reload && service nym-node restart +``` + + - New manual how to [run `nym-node` as non-root](nodes/nym-node/configuration#running-nym-node-as-a-non-root) - Since `v2024.13-magura`, operators do not update their node version in the wallet. [Manual upgrading steps](nodes/maintenance/manual-upgrade.mdx) has been updated accordingly. From e7f806219c12243fef25550f3f06220811bfca7f Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:59:42 +0100 Subject: [PATCH 10/80] Move NS client to separate package under NS API (#5171) * Move client code to NS API * Move client to separate package * Move things around * Adjust run scripts * rustfmt * Add client to workspace --- Cargo.lock | 36 +++++----- Cargo.toml | 11 ++-- common/models/Cargo.toml | 17 ----- common/models/src/lib.rs | 1 - nym-node-status-agent/.gitignore | 2 - nym-node-status-api/.gitignore | 3 +- .../nym-node-status-agent/.gitignore | 0 .../nym-node-status-agent}/Cargo.toml | 13 +--- .../nym-node-status-agent}/Dockerfile | 0 .../nym-node-status-agent}/gen_keypair.sh | 0 .../nym-node-status-agent}/run.sh | 10 +-- .../src/cli/generate_keypair.rs | 0 .../nym-node-status-agent}/src/cli/mod.rs | 0 .../src/cli/run_probe.rs | 32 +++++++++ .../nym-node-status-agent}/src/main.rs | 0 .../nym-node-status-agent}/src/probe.rs | 0 ...55c69cd5c1a7e7d87073c94600c783a0a3984.json | 0 ...7dc549cf503409fd6dfedcdc02dbcd61d5454.json | 0 ...f2c63cdf90f2f3b6d81afb9000bb0968dcaea.json | 0 ...5ceb89b9925cba46efcf4ed79ef0759a01129.json | 0 ...965acf3bcfb3f84ba8d24ed645d79976cf784.json | 0 ...e692afc04b51e8782bcbf59f1eb4116112536.json | 0 ...8632c29c81bfafb043bb8744129cf9e0266ad.json | 0 ...203c8569f03fdd39ce09d7b74177896e52a8c.json | 0 ...289dd776749dd7a20cda61b0480f2fba60889.json | 0 ...570ee787fc7daf00fcb916f18d1cb7d6c8f08.json | 0 ...17b40dc01845f554b5479f37855d89b309e6f.json | 0 ...a667e8cacdf677ccb906415b3fe92be0c436b.json | 0 ...56f816956b0824c66a25da611dce688105d36.json | 0 ...df795147ac3724e89df5b2f24f7215d87dce1.json | 0 ...f3de5949a5ff2083453cb879af8a1689efe2f.json | 0 ...29d1320a5eb10797cdb5a435455d22c6aeac1.json | 0 ...03a2752e28df775d1af8fd943a5fc8d7dc00d.json | 0 ...6c5e41f3c0bb56a27648b5e25466b8634a578.json | 0 ...d17ca6eb090df7f6d52ef89c5d51564f8b45c.json | 0 ...86b86d1b9be62336c9eac0eef44987a5451b5.json | 0 ...90f6c66c14d4267a2cc2ca73354becc2c8bb8.json | 0 ...c586c4c29c03efb4cf0c40b73a5c76159cf5c.json | 0 ...ea1f6dc2aed00cedde25f2be3567e47064351.json | 0 ...dadfcf19095935867a51cbd5b8362d1505fcc.json | 0 ...ee2aa37f6163525bbdce1a4cebef4a285f8d9.json | 0 ...74a5bf4912ee73468e62e7d0d96b1d9074cbe.json | 0 ...3d31cb4bcc4071d31d8d2f7755e2d2c2e3e35.json | 0 ...b925e033ceec4f8112258feb4ac9e96fc5924.json | 0 ...b5d2f2d08b2e53203aab9f07ea9b52acbd407.json | 0 .../{ => nym-node-status-api}/Cargo.toml | 17 +++-- .../{ => nym-node-status-api}/Dockerfile | 0 .../{ => nym-node-status-api}/Dockerfile.dev | 0 .../{ => nym-node-status-api}/build.rs | 0 .../launch_node_status_api.sh | 22 +++---- .../migrations/000_init.sql | 0 .../migrations/001_last_assigned_utc.sql | 0 .../{ => nym-node-status-api}/src/cli/mod.rs | 0 .../{ => nym-node-status-api}/src/db/mod.rs | 0 .../src/db/models.rs | 0 .../src/db/queries/gateways.rs | 0 .../src/db/queries/misc.rs | 0 .../src/db/queries/mixnodes.rs | 0 .../src/db/queries/mod.rs | 0 .../src/db/queries/summary.rs | 0 .../src/db/queries/testruns.rs | 0 .../src/http/api/gateways.rs | 0 .../src/http/api/mixnodes.rs | 0 .../src/http/api/mod.rs | 0 .../src/http/api/services/json_path.rs | 0 .../src/http/api/services/mod.rs | 0 .../src/http/api/summary.rs | 0 .../src/http/api/testruns.rs | 20 +++--- .../src/http/api_docs.rs | 2 +- .../src/http/error.rs | 0 .../{ => nym-node-status-api}/src/http/mod.rs | 0 .../src/http/models.rs | 2 +- .../src/http/server.rs | 0 .../src/http/state.rs | 0 .../{ => nym-node-status-api}/src/logging.rs | 0 .../{ => nym-node-status-api}/src/main.rs | 0 .../src/monitor/mod.rs | 0 .../src/testruns/mod.rs | 0 .../src/testruns/models.rs | 0 .../src/testruns/queue.rs | 0 .../nym-node-status-client/Cargo.toml | 25 +++++++ .../nym-node-status-client/src/api.rs | 18 +++++ .../nym-node-status-client/src/auth.rs | 30 +++++++++ .../nym-node-status-client/src/lib.rs | 65 +++++-------------- .../nym-node-status-client/src/models.rs | 35 ++-------- 85 files changed, 187 insertions(+), 174 deletions(-) delete mode 100644 common/models/Cargo.toml delete mode 100644 common/models/src/lib.rs delete mode 100644 nym-node-status-agent/.gitignore create mode 100644 nym-node-status-api/nym-node-status-agent/.gitignore rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/Cargo.toml (64%) rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/Dockerfile (100%) rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/gen_keypair.sh (100%) rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/run.sh (88%) rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/src/cli/generate_keypair.rs (100%) rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/src/cli/mod.rs (100%) create mode 100644 nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/src/main.rs (100%) rename {nym-node-status-agent => nym-node-status-api/nym-node-status-agent}/src/probe.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-3c584e211d07c511644c8079187965acf3bcfb3f84ba8d24ed645d79976cf784.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-46d76bc6d3fba2dae3b21511a36289dd776749dd7a20cda61b0480f2fba60889.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-6d7967b831b355d5f2c77950abc56f816956b0824c66a25da611dce688105d36.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-f5048d9926a5f5329f7f3b96d43b925e033ceec4f8112258feb4ac9e96fc5924.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/.sqlx/query-ff9334ba7b670b218b2f9100e9ab5d2f2d08b2e53203aab9f07ea9b52acbd407.json (100%) rename nym-node-status-api/{ => nym-node-status-api}/Cargo.toml (77%) rename nym-node-status-api/{ => nym-node-status-api}/Dockerfile (100%) rename nym-node-status-api/{ => nym-node-status-api}/Dockerfile.dev (100%) rename nym-node-status-api/{ => nym-node-status-api}/build.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/launch_node_status_api.sh (62%) rename nym-node-status-api/{ => nym-node-status-api}/migrations/000_init.sql (100%) rename nym-node-status-api/{ => nym-node-status-api}/migrations/001_last_assigned_utc.sql (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/cli/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/models.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/queries/gateways.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/queries/misc.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/queries/mixnodes.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/queries/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/queries/summary.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/db/queries/testruns.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api/gateways.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api/mixnodes.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api/services/json_path.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api/services/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api/summary.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api/testruns.rs (98%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/api_docs.rs (87%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/error.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/models.rs (97%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/server.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/http/state.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/logging.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/main.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/monitor/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/testruns/mod.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/testruns/models.rs (100%) rename nym-node-status-api/{ => nym-node-status-api}/src/testruns/queue.rs (100%) create mode 100644 nym-node-status-api/nym-node-status-client/Cargo.toml create mode 100644 nym-node-status-api/nym-node-status-client/src/api.rs create mode 100644 nym-node-status-api/nym-node-status-client/src/auth.rs rename nym-node-status-agent/src/cli/run_probe.rs => nym-node-status-api/nym-node-status-client/src/lib.rs (61%) rename common/models/src/ns_api.rs => nym-node-status-api/nym-node-status-client/src/models.rs (67%) diff --git a/Cargo.lock b/Cargo.lock index ab3aa84ddf..e7c3b1f622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5009,16 +5009,6 @@ dependencies = [ "nym-multisig-contract-common", ] -[[package]] -name = "nym-common-models" -version = "0.1.0" -dependencies = [ - "anyhow", - "bincode", - "nym-crypto", - "serde", -] - [[package]] name = "nym-compact-ecash" version = "0.1.0" @@ -6058,19 +6048,13 @@ name = "nym-node-status-agent" version = "1.0.0-rc.1" dependencies = [ "anyhow", - "bincode", - "chrono", "clap 4.5.20", "nym-bin-common", - "nym-common-models", "nym-crypto", + "nym-node-status-client", "rand", - "reqwest 0.12.4", - "serde", - "serde_json", "tempfile", "tokio", - "tokio-util", "tracing", "tracing-subscriber", ] @@ -6081,7 +6065,6 @@ version = "1.0.0-rc.2" dependencies = [ "anyhow", "axum 0.7.7", - "bincode", "chrono", "clap 4.5.20", "cosmwasm-std", @@ -6089,11 +6072,11 @@ dependencies = [ "futures-util", "moka", "nym-bin-common", - "nym-common-models", "nym-crypto", "nym-explorer-client", "nym-network-defaults", "nym-node-requests", + "nym-node-status-client", "nym-task", "nym-validator-client", "regex", @@ -6116,6 +6099,21 @@ dependencies = [ "utoipauto", ] +[[package]] +name = "nym-node-status-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode", + "chrono", + "nym-crypto", + "nym-http-api-client", + "reqwest 0.12.4", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "nym-node-tester-utils" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7735cb8f59..ae3f32e389 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ members = [ "common/ip-packet-requests", "common/ledger", "common/mixnode-common", - "common/models", "common/network-defaults", "common/node-tester-utils", "common/nonexhaustive-delayqueue", @@ -126,8 +125,9 @@ members = [ "nym-node", "nym-node/nym-node-http-api", "nym-node/nym-node-requests", - "nym-node-status-api", - "nym-node-status-agent", + "nym-node-status-api/nym-node-status-agent", + "nym-node-status-api/nym-node-status-api", + "nym-node-status-api/nym-node-status-client", "nym-outfox", "nym-validator-rewarder", "tools/echo-server", @@ -155,7 +155,6 @@ members = [ default-members = [ "clients/native", "clients/socks5", - "common/models", "explorer-api", "gateway", "mixnode", @@ -163,9 +162,9 @@ default-members = [ "nym-credential-proxy/nym-credential-proxy", "nym-data-observatory", "nym-node", - "nym-node-status-api", + "nym-node-status-api/nym-node-status-agent", + "nym-node-status-api/nym-node-status-api", "nym-validator-rewarder", - "nym-node-status-api", "service-providers/authenticator", "service-providers/ip-packet-router", "service-providers/network-requester", diff --git a/common/models/Cargo.toml b/common/models/Cargo.toml deleted file mode 100644 index 3192a83b53..0000000000 --- a/common/models/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "nym-common-models" -version = "0.1.0" -authors.workspace = true -repository.workspace = true -homepage.workspace = true -documentation.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true -readme.workspace = true - -[dependencies] -anyhow = { workspace = true } -bincode = { workspace = true } -nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] } -serde = { workspace = true, features = ["derive"] } diff --git a/common/models/src/lib.rs b/common/models/src/lib.rs deleted file mode 100644 index 3d85e66947..0000000000 --- a/common/models/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ns_api; diff --git a/nym-node-status-agent/.gitignore b/nym-node-status-agent/.gitignore deleted file mode 100644 index 4bb044ec5e..0000000000 --- a/nym-node-status-agent/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -nym-gateway-probe -keys/ diff --git a/nym-node-status-api/.gitignore b/nym-node-status-api/.gitignore index 91459afabe..db7ea3635a 100644 --- a/nym-node-status-api/.gitignore +++ b/nym-node-status-api/.gitignore @@ -1,6 +1,7 @@ +nym-node-status-agent/nym-gateway-probe +nym-node-status-agent/keys/ data/ enter_db.sh nym-gateway-probe -nym-node-status-api *.sqlite *.sqlite-journal diff --git a/nym-node-status-api/nym-node-status-agent/.gitignore b/nym-node-status-api/nym-node-status-agent/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml similarity index 64% rename from nym-node-status-agent/Cargo.toml rename to nym-node-status-api/nym-node-status-agent/Cargo.toml index db64eeec99..d9f11f518e 100644 --- a/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -1,7 +1,6 @@ # Copyright 2024 - Nym Technologies SA # SPDX-License-Identifier: Apache-2.0 - [package] name = "nym-node-status-agent" version = "1.0.0-rc.1" @@ -16,20 +15,14 @@ readme.workspace = true [dependencies] anyhow = { workspace = true} -bincode = { workspace = true } -chrono = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } -nym-bin-common = { path = "../common/bin-common", features = ["models"]} -nym-common-models = { path = "../common/models" } -nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } +nym-bin-common = { path = "../../common/bin-common", features = ["models"]} +nym-node-status-client = { path = "../nym-node-status-client" } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } rand = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process"] } -tokio-util = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } -reqwest = { workspace = true, features = ["json"] } -serde = { workspace = true } -serde_json = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/nym-node-status-agent/Dockerfile b/nym-node-status-api/nym-node-status-agent/Dockerfile similarity index 100% rename from nym-node-status-agent/Dockerfile rename to nym-node-status-api/nym-node-status-agent/Dockerfile diff --git a/nym-node-status-agent/gen_keypair.sh b/nym-node-status-api/nym-node-status-agent/gen_keypair.sh similarity index 100% rename from nym-node-status-agent/gen_keypair.sh rename to nym-node-status-api/nym-node-status-agent/gen_keypair.sh diff --git a/nym-node-status-agent/run.sh b/nym-node-status-api/nym-node-status-agent/run.sh similarity index 88% rename from nym-node-status-agent/run.sh rename to nym-node-status-api/nym-node-status-agent/run.sh index 0b38466031..680c87db8b 100755 --- a/nym-node-status-agent/run.sh +++ b/nym-node-status-api/nym-node-status-agent/run.sh @@ -3,14 +3,14 @@ set -eu export ENVIRONMENT=${ENVIRONMENT:-"sandbox"} -probe_git_ref="nym-vpn-core-v1.0.0-rc.6" +probe_git_ref="nym-vpn-core-v1.0.0-rc.14" crate_root=$(dirname $(realpath "$0")) -monorepo_root=$(dirname "${crate_root}") +monorepo_root=$(realpath "${crate_root}/../..") + echo "Expecting nym-vpn-client repo at a sibling level of nym monorepo dir" gateway_probe_src=$(dirname "${monorepo_root}")/nym-vpn-client/nym-vpn-core echo "gateway_probe_src=$gateway_probe_src" -echo "crate_root=$crate_root" set -a source "${monorepo_root}/envs/${ENVIRONMENT}.env" @@ -30,9 +30,11 @@ function copy_gw_probe() { pushd $gateway_probe_src git fetch -a git checkout $probe_git_ref + cargo build --release --package nym-gateway-probe cp target/release/nym-gateway-probe "$crate_root" $crate_root/nym-gateway-probe --version + popd } @@ -44,7 +46,7 @@ function swarm() { local workers=$1 for ((i = 1; i <= workers; i++)); do - ../target/release/nym-node-status-agent run-probe & + ${monorepo_root}/target/release/nym-node-status-agent run-probe & done wait diff --git a/nym-node-status-agent/src/cli/generate_keypair.rs b/nym-node-status-api/nym-node-status-agent/src/cli/generate_keypair.rs similarity index 100% rename from nym-node-status-agent/src/cli/generate_keypair.rs rename to nym-node-status-api/nym-node-status-agent/src/cli/generate_keypair.rs diff --git a/nym-node-status-agent/src/cli/mod.rs b/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs similarity index 100% rename from nym-node-status-agent/src/cli/mod.rs rename to nym-node-status-api/nym-node-status-agent/src/cli/mod.rs diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs new file mode 100644 index 0000000000..e9b7eaf44a --- /dev/null +++ b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs @@ -0,0 +1,32 @@ +use crate::cli::GwProbe; +use anyhow::Context; +use nym_crypto::asymmetric::ed25519::PrivateKey; + +pub(crate) async fn run_probe( + server_ip: &str, + server_port: u16, + ns_api_auth_key: &str, + probe_path: &str, +) -> anyhow::Result<()> { + let auth_key = PrivateKey::from_base58_string(ns_api_auth_key) + .context("Couldn't parse auth key, exiting")?; + + let ns_api_client = nym_node_status_client::NsApiClient::new(server_ip, server_port, auth_key); + + let probe = GwProbe::new(probe_path.to_string()); + + let version = probe.version().await; + tracing::info!("Probe version:\n{}", version); + + if let Some(testrun) = ns_api_client.request_testrun().await? { + let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key)); + + ns_api_client + .submit_results(testrun.testrun_id, log, testrun.assigned_at_utc) + .await?; + } else { + tracing::info!("No testruns available, exiting") + } + + Ok(()) +} diff --git a/nym-node-status-agent/src/main.rs b/nym-node-status-api/nym-node-status-agent/src/main.rs similarity index 100% rename from nym-node-status-agent/src/main.rs rename to nym-node-status-api/nym-node-status-agent/src/main.rs diff --git a/nym-node-status-agent/src/probe.rs b/nym-node-status-api/nym-node-status-agent/src/probe.rs similarity index 100% rename from nym-node-status-agent/src/probe.rs rename to nym-node-status-api/nym-node-status-agent/src/probe.rs diff --git a/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json similarity index 100% rename from nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json diff --git a/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json similarity index 100% rename from nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json diff --git a/nym-node-status-api/.sqlx/query-18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea.json similarity index 100% rename from nym-node-status-api/.sqlx/query-18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea.json diff --git a/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json similarity index 100% rename from nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json diff --git a/nym-node-status-api/.sqlx/query-3c584e211d07c511644c8079187965acf3bcfb3f84ba8d24ed645d79976cf784.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3c584e211d07c511644c8079187965acf3bcfb3f84ba8d24ed645d79976cf784.json similarity index 100% rename from nym-node-status-api/.sqlx/query-3c584e211d07c511644c8079187965acf3bcfb3f84ba8d24ed645d79976cf784.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-3c584e211d07c511644c8079187965acf3bcfb3f84ba8d24ed645d79976cf784.json diff --git a/nym-node-status-api/.sqlx/query-3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536.json similarity index 100% rename from nym-node-status-api/.sqlx/query-3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536.json diff --git a/nym-node-status-api/.sqlx/query-3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad.json similarity index 100% rename from nym-node-status-api/.sqlx/query-3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad.json diff --git a/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json similarity index 100% rename from nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json diff --git a/nym-node-status-api/.sqlx/query-46d76bc6d3fba2dae3b21511a36289dd776749dd7a20cda61b0480f2fba60889.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-46d76bc6d3fba2dae3b21511a36289dd776749dd7a20cda61b0480f2fba60889.json similarity index 100% rename from nym-node-status-api/.sqlx/query-46d76bc6d3fba2dae3b21511a36289dd776749dd7a20cda61b0480f2fba60889.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-46d76bc6d3fba2dae3b21511a36289dd776749dd7a20cda61b0480f2fba60889.json diff --git a/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json similarity index 100% rename from nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json diff --git a/nym-node-status-api/.sqlx/query-4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f.json similarity index 100% rename from nym-node-status-api/.sqlx/query-4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f.json diff --git a/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json similarity index 100% rename from nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json diff --git a/nym-node-status-api/.sqlx/query-6d7967b831b355d5f2c77950abc56f816956b0824c66a25da611dce688105d36.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6d7967b831b355d5f2c77950abc56f816956b0824c66a25da611dce688105d36.json similarity index 100% rename from nym-node-status-api/.sqlx/query-6d7967b831b355d5f2c77950abc56f816956b0824c66a25da611dce688105d36.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-6d7967b831b355d5f2c77950abc56f816956b0824c66a25da611dce688105d36.json diff --git a/nym-node-status-api/.sqlx/query-6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1.json similarity index 100% rename from nym-node-status-api/.sqlx/query-6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1.json diff --git a/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json similarity index 100% rename from nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json diff --git a/nym-node-status-api/.sqlx/query-71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1.json similarity index 100% rename from nym-node-status-api/.sqlx/query-71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1.json diff --git a/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json similarity index 100% rename from nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json diff --git a/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json similarity index 100% rename from nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json diff --git a/nym-node-status-api/.sqlx/query-8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c.json similarity index 100% rename from nym-node-status-api/.sqlx/query-8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c.json diff --git a/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json similarity index 100% rename from nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json diff --git a/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json similarity index 100% rename from nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json diff --git a/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json similarity index 100% rename from nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json diff --git a/nym-node-status-api/.sqlx/query-c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351.json similarity index 100% rename from nym-node-status-api/.sqlx/query-c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351.json diff --git a/nym-node-status-api/.sqlx/query-c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc.json similarity index 100% rename from nym-node-status-api/.sqlx/query-c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc.json diff --git a/nym-node-status-api/.sqlx/query-d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9.json similarity index 100% rename from nym-node-status-api/.sqlx/query-d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9.json diff --git a/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json similarity index 100% rename from nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json diff --git a/nym-node-status-api/.sqlx/query-f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35.json similarity index 100% rename from nym-node-status-api/.sqlx/query-f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35.json diff --git a/nym-node-status-api/.sqlx/query-f5048d9926a5f5329f7f3b96d43b925e033ceec4f8112258feb4ac9e96fc5924.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-f5048d9926a5f5329f7f3b96d43b925e033ceec4f8112258feb4ac9e96fc5924.json similarity index 100% rename from nym-node-status-api/.sqlx/query-f5048d9926a5f5329f7f3b96d43b925e033ceec4f8112258feb4ac9e96fc5924.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-f5048d9926a5f5329f7f3b96d43b925e033ceec4f8112258feb4ac9e96fc5924.json diff --git a/nym-node-status-api/.sqlx/query-ff9334ba7b670b218b2f9100e9ab5d2f2d08b2e53203aab9f07ea9b52acbd407.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-ff9334ba7b670b218b2f9100e9ab5d2f2d08b2e53203aab9f07ea9b52acbd407.json similarity index 100% rename from nym-node-status-api/.sqlx/query-ff9334ba7b670b218b2f9100e9ab5d2f2d08b2e53203aab9f07ea9b52acbd407.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-ff9334ba7b670b218b2f9100e9ab5d2f2d08b2e53203aab9f07ea9b52acbd407.json diff --git a/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml similarity index 77% rename from nym-node-status-api/Cargo.toml rename to nym-node-status-api/nym-node-status-api/Cargo.toml index b7951996b8..cc95396f86 100644 --- a/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -15,21 +15,20 @@ rust-version.workspace = true [dependencies] anyhow = { workspace = true } axum = { workspace = true, features = ["tokio", "macros"] } -bincode = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = ["cargo", "derive", "env", "string"] } cosmwasm-std = { workspace = true } envy = { workspace = true } futures-util = { workspace = true } moka = { workspace = true, features = ["future"] } -nym-bin-common = { path = "../common/bin-common", features = ["models"]} -nym-common-models = { path = "../common/models" } -nym-crypto = { path = "../common/crypto", features = ["asymmetric", "serde"] } -nym-explorer-client = { path = "../explorer-api/explorer-client" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-task = { path = "../common/task" } -nym-node-requests = { path = "../nym-node/nym-node-requests", features = ["openapi"] } +nym-bin-common = { path = "../../common/bin-common", features = ["models"]} +nym-node-status-client = { path = "../nym-node-status-client" } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } +nym-explorer-client = { path = "../../explorer-api/explorer-client" } +nym-network-defaults = { path = "../../common/network-defaults" } +nym-validator-client = { path = "../../common/client-libs/validator-client" } +nym-task = { path = "../../common/task" } +nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } regex = { workspace = true } reqwest = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/nym-node-status-api/Dockerfile b/nym-node-status-api/nym-node-status-api/Dockerfile similarity index 100% rename from nym-node-status-api/Dockerfile rename to nym-node-status-api/nym-node-status-api/Dockerfile diff --git a/nym-node-status-api/Dockerfile.dev b/nym-node-status-api/nym-node-status-api/Dockerfile.dev similarity index 100% rename from nym-node-status-api/Dockerfile.dev rename to nym-node-status-api/nym-node-status-api/Dockerfile.dev diff --git a/nym-node-status-api/build.rs b/nym-node-status-api/nym-node-status-api/build.rs similarity index 100% rename from nym-node-status-api/build.rs rename to nym-node-status-api/nym-node-status-api/build.rs diff --git a/nym-node-status-api/launch_node_status_api.sh b/nym-node-status-api/nym-node-status-api/launch_node_status_api.sh similarity index 62% rename from nym-node-status-api/launch_node_status_api.sh rename to nym-node-status-api/nym-node-status-api/launch_node_status_api.sh index 26a97fcbab..24b9a68ae8 100755 --- a/nym-node-status-api/launch_node_status_api.sh +++ b/nym-node-status-api/nym-node-status-api/launch_node_status_api.sh @@ -2,29 +2,23 @@ set -e -export RUST_LOG=${RUST_LOG:-debug} - export NYM_API_CLIENT_TIMEOUT=60 export EXPLORER_CLIENT_TIMEOUT=60 export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120 -# public keys corresponding to the agents NS API is expecting to be contacted from -export NODE_STATUS_API_AGENT_KEY_LIST="H4z8kx5Kkf5JMQHhxaW1MwYndjKCDHC7HsVhHTFfBZ4J, -5c2GW61135DEr73DxGrR4DR22BLEujvm1k8GYEjRB9at, -3PSFDH2iSJ61KoDNyJpAiw42xS5smV5iBXWnRGTmk2du, -2AH7pJL5PErbSFhZdu3uH8cKa1h1tyCUfSRUm6E5EBz8, -6wQ9ifPFm2EB73BrwpGSd3Ek7GFA5kiAMQDP2ox6JKZw, -G1tevJBnzaQ6zCUsFsxtGJf45BqCTDgzpEz6Sgxks8EH, -FwjL2nGrtgQQ48fPqAUzUZ8UkQZtMtgehqTqj4PQopvh, -Eujj4GmvwQBgHZaNSyqUbjMFSsnXWPSjEYUPgAsKmx1A, -5ZnfSGxW6EKcFxB8jftb9V3f897VpwpZtf7kCPYzB595, -H9kuRd8BGjEUD8Grh5U9YUPN5ZaQmSYz8U44R72AffKM" + +# public counterpart of the agent's private key. +# For TESTING only. NOT used in any other environment +export NODE_STATUS_API_AGENT_KEY_LIST="H4z8kx5Kkf5JMQHhxaW1MwYndjKCDHC7HsVhHTFfBZ4J" export ENVIRONMENT=${ENVIRONMENT:-"sandbox"} +script_dir=$(dirname $(realpath "$0")) +monorepo_root=$(realpath "${script_dir}/../..") + function run_bare() { # export necessary env vars set -a - source ../envs/${ENVIRONMENT}.env + source "${monorepo_root}/envs/${ENVIRONMENT}.env" set +a export RUST_LOG=debug diff --git a/nym-node-status-api/migrations/000_init.sql b/nym-node-status-api/nym-node-status-api/migrations/000_init.sql similarity index 100% rename from nym-node-status-api/migrations/000_init.sql rename to nym-node-status-api/nym-node-status-api/migrations/000_init.sql diff --git a/nym-node-status-api/migrations/001_last_assigned_utc.sql b/nym-node-status-api/nym-node-status-api/migrations/001_last_assigned_utc.sql similarity index 100% rename from nym-node-status-api/migrations/001_last_assigned_utc.sql rename to nym-node-status-api/nym-node-status-api/migrations/001_last_assigned_utc.sql diff --git a/nym-node-status-api/src/cli/mod.rs b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs similarity index 100% rename from nym-node-status-api/src/cli/mod.rs rename to nym-node-status-api/nym-node-status-api/src/cli/mod.rs diff --git a/nym-node-status-api/src/db/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/mod.rs similarity index 100% rename from nym-node-status-api/src/db/mod.rs rename to nym-node-status-api/nym-node-status-api/src/db/mod.rs diff --git a/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs similarity index 100% rename from nym-node-status-api/src/db/models.rs rename to nym-node-status-api/nym-node-status-api/src/db/models.rs diff --git a/nym-node-status-api/src/db/queries/gateways.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs similarity index 100% rename from nym-node-status-api/src/db/queries/gateways.rs rename to nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs diff --git a/nym-node-status-api/src/db/queries/misc.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs similarity index 100% rename from nym-node-status-api/src/db/queries/misc.rs rename to nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs diff --git a/nym-node-status-api/src/db/queries/mixnodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs similarity index 100% rename from nym-node-status-api/src/db/queries/mixnodes.rs rename to nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs diff --git a/nym-node-status-api/src/db/queries/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs similarity index 100% rename from nym-node-status-api/src/db/queries/mod.rs rename to nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs diff --git a/nym-node-status-api/src/db/queries/summary.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs similarity index 100% rename from nym-node-status-api/src/db/queries/summary.rs rename to nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs diff --git a/nym-node-status-api/src/db/queries/testruns.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs similarity index 100% rename from nym-node-status-api/src/db/queries/testruns.rs rename to nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs diff --git a/nym-node-status-api/src/http/api/gateways.rs b/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs similarity index 100% rename from nym-node-status-api/src/http/api/gateways.rs rename to nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs diff --git a/nym-node-status-api/src/http/api/mixnodes.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs similarity index 100% rename from nym-node-status-api/src/http/api/mixnodes.rs rename to nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs diff --git a/nym-node-status-api/src/http/api/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs similarity index 100% rename from nym-node-status-api/src/http/api/mod.rs rename to nym-node-status-api/nym-node-status-api/src/http/api/mod.rs diff --git a/nym-node-status-api/src/http/api/services/json_path.rs b/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs similarity index 100% rename from nym-node-status-api/src/http/api/services/json_path.rs rename to nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs diff --git a/nym-node-status-api/src/http/api/services/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs similarity index 100% rename from nym-node-status-api/src/http/api/services/mod.rs rename to nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs diff --git a/nym-node-status-api/src/http/api/summary.rs b/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs similarity index 100% rename from nym-node-status-api/src/http/api/summary.rs rename to nym-node-status-api/nym-node-status-api/src/http/api/summary.rs diff --git a/nym-node-status-api/src/http/api/testruns.rs b/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs similarity index 98% rename from nym-node-status-api/src/http/api/testruns.rs rename to nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs index 6f5ab862ea..3e34c09ed6 100644 --- a/nym-node-status-api/src/http/api/testruns.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs @@ -1,12 +1,3 @@ -use axum::extract::DefaultBodyLimit; -use axum::Json; -use axum::{ - extract::{Path, State}, - Router, -}; -use nym_common_models::ns_api::{get_testrun, submit_results, VerifiableRequest}; -use reqwest::StatusCode; - use crate::db::models::TestRunStatus; use crate::db::queries; use crate::testruns::now_utc; @@ -18,6 +9,17 @@ use crate::{ state::AppState, }, }; +use axum::extract::DefaultBodyLimit; +use axum::Json; +use axum::{ + extract::{Path, State}, + Router, +}; +use nym_node_status_client::{ + auth::VerifiableRequest, + models::{get_testrun, submit_results}, +}; +use reqwest::StatusCode; // TODO dz consider adding endpoint to trigger testrun scan for a given gateway_id // like in H< src/http/testruns.rs diff --git a/nym-node-status-api/src/http/api_docs.rs b/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs similarity index 87% rename from nym-node-status-api/src/http/api_docs.rs rename to nym-node-status-api/nym-node-status-api/src/http/api_docs.rs index 9ac5238fc4..aa86a56ab0 100644 --- a/nym-node-status-api/src/http/api_docs.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs @@ -5,7 +5,7 @@ use utoipauto::utoipauto; // manually import external structs which are behind feature flags because they // can't be automatically discovered // https://github.com/ProbablyClem/utoipauto/issues/13#issuecomment-1974911829 -#[utoipauto(paths = "./nym-node-status-api/src")] +#[utoipauto(paths = "./nym-node-status-api/nym-node-status-api/src")] #[derive(OpenApi)] #[openapi( info(title = "Node Status API"), diff --git a/nym-node-status-api/src/http/error.rs b/nym-node-status-api/nym-node-status-api/src/http/error.rs similarity index 100% rename from nym-node-status-api/src/http/error.rs rename to nym-node-status-api/nym-node-status-api/src/http/error.rs diff --git a/nym-node-status-api/src/http/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/mod.rs similarity index 100% rename from nym-node-status-api/src/http/mod.rs rename to nym-node-status-api/nym-node-status-api/src/http/mod.rs diff --git a/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs similarity index 97% rename from nym-node-status-api/src/http/models.rs rename to nym-node-status-api/nym-node-status-api/src/http/models.rs index 82011fc286..aee2124b6c 100644 --- a/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -2,7 +2,7 @@ use nym_node_requests::api::v1::node::models::NodeDescription; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -pub(crate) use nym_common_models::ns_api::TestrunAssignment; +pub(crate) use nym_node_status_client::models::TestrunAssignment; #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct Gateway { diff --git a/nym-node-status-api/src/http/server.rs b/nym-node-status-api/nym-node-status-api/src/http/server.rs similarity index 100% rename from nym-node-status-api/src/http/server.rs rename to nym-node-status-api/nym-node-status-api/src/http/server.rs diff --git a/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs similarity index 100% rename from nym-node-status-api/src/http/state.rs rename to nym-node-status-api/nym-node-status-api/src/http/state.rs diff --git a/nym-node-status-api/src/logging.rs b/nym-node-status-api/nym-node-status-api/src/logging.rs similarity index 100% rename from nym-node-status-api/src/logging.rs rename to nym-node-status-api/nym-node-status-api/src/logging.rs diff --git a/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs similarity index 100% rename from nym-node-status-api/src/main.rs rename to nym-node-status-api/nym-node-status-api/src/main.rs diff --git a/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs similarity index 100% rename from nym-node-status-api/src/monitor/mod.rs rename to nym-node-status-api/nym-node-status-api/src/monitor/mod.rs diff --git a/nym-node-status-api/src/testruns/mod.rs b/nym-node-status-api/nym-node-status-api/src/testruns/mod.rs similarity index 100% rename from nym-node-status-api/src/testruns/mod.rs rename to nym-node-status-api/nym-node-status-api/src/testruns/mod.rs diff --git a/nym-node-status-api/src/testruns/models.rs b/nym-node-status-api/nym-node-status-api/src/testruns/models.rs similarity index 100% rename from nym-node-status-api/src/testruns/models.rs rename to nym-node-status-api/nym-node-status-api/src/testruns/models.rs diff --git a/nym-node-status-api/src/testruns/queue.rs b/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs similarity index 100% rename from nym-node-status-api/src/testruns/queue.rs rename to nym-node-status-api/nym-node-status-api/src/testruns/queue.rs diff --git a/nym-node-status-api/nym-node-status-client/Cargo.toml b/nym-node-status-api/nym-node-status-client/Cargo.toml new file mode 100644 index 0000000000..a7dc5899fb --- /dev/null +++ b/nym-node-status-api/nym-node-status-client/Cargo.toml @@ -0,0 +1,25 @@ +# Copyright 2024 - Nym Technologies SA +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "nym-node-status-client" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +[dependencies] +anyhow = { workspace = true } +chrono = { workspace = true } +bincode = { workspace = true } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } +nym-http-api-client = { path = "../../common/http-api-client" } +reqwest = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tracing = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-client/src/api.rs b/nym-node-status-api/nym-node-status-client/src/api.rs new file mode 100644 index 0000000000..46d2fb7cd4 --- /dev/null +++ b/nym-node-status-api/nym-node-status-client/src/api.rs @@ -0,0 +1,18 @@ +use std::fmt::Display; + +pub(super) struct ApiPaths { + server_address: String, +} + +impl ApiPaths { + pub(super) fn new(server_address: String) -> Self { + Self { server_address } + } + pub(super) fn request_testrun(&self) -> String { + format!("{}/internal/testruns", self.server_address) + } + + pub(super) fn submit_results(&self, testrun_id: impl Display) -> String { + format!("{}/internal/testruns/{}", self.server_address, testrun_id) + } +} diff --git a/nym-node-status-api/nym-node-status-client/src/auth.rs b/nym-node-status-api/nym-node-status-client/src/auth.rs new file mode 100644 index 0000000000..263079dcc6 --- /dev/null +++ b/nym-node-status-api/nym-node-status-client/src/auth.rs @@ -0,0 +1,30 @@ +use nym_crypto::asymmetric::ed25519::{PublicKey, Signature, SignatureError}; + +pub trait SignedRequest { + type Payload: serde::Serialize; + + fn public_key(&self) -> &PublicKey; + fn signature(&self) -> &Signature; + fn payload(&self) -> &Self::Payload; +} + +pub trait VerifiableRequest: SignedRequest { + type Error: From + From; + + fn verify_signature(&self) -> Result<(), Self::Error> { + bincode::serialize(self.payload()) + .map_err(Self::Error::from) + .and_then(|serialized| { + self.public_key() + .verify(serialized, self.signature()) + .map_err(Self::Error::from) + }) + } +} + +impl VerifiableRequest for T +where + T: SignedRequest, +{ + type Error = anyhow::Error; +} diff --git a/nym-node-status-agent/src/cli/run_probe.rs b/nym-node-status-api/nym-node-status-client/src/lib.rs similarity index 61% rename from nym-node-status-agent/src/cli/run_probe.rs rename to nym-node-status-api/nym-node-status-client/src/lib.rs index 3ffd90d46c..260b1b510d 100644 --- a/nym-node-status-agent/src/cli/run_probe.rs +++ b/nym-node-status-api/nym-node-status-client/src/lib.rs @@ -1,62 +1,35 @@ -use anyhow::{bail, Context}; -use nym_common_models::ns_api::{get_testrun, submit_results, TestrunAssignment}; +use crate::models::{get_testrun, submit_results, TestrunAssignment}; +use anyhow::bail; +use api::ApiPaths; use nym_crypto::asymmetric::ed25519::{PrivateKey, Signature}; -use std::fmt::Display; use tracing::instrument; -use crate::cli::GwProbe; +mod api; +pub mod auth; +pub mod models; -const INTERNAL_TESTRUNS: &str = "internal/testruns"; - -pub(crate) async fn run_probe( - server_ip: &str, - server_port: u16, - ns_api_auth_key: &str, - probe_path: &str, -) -> anyhow::Result<()> { - let auth_key = PrivateKey::from_base58_string(ns_api_auth_key) - .context("Couldn't parse auth key, exiting")?; - let ns_api_client = Client::new(server_ip, server_port, auth_key); - - let probe = GwProbe::new(probe_path.to_string()); - - let version = probe.version().await; - tracing::info!("Probe version:\n{}", version); - - if let Some(testrun) = ns_api_client.request_testrun().await? { - let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key)); - - ns_api_client - .submit_results(testrun.testrun_id, log, testrun.assigned_at_utc) - .await?; - } else { - tracing::info!("No testruns available, exiting") - } - - Ok(()) -} - -struct Client { - server_address: String, +pub struct NsApiClient { + api: ApiPaths, client: reqwest::Client, auth_key: PrivateKey, } -impl Client { +impl NsApiClient { pub fn new(server_ip: &str, server_port: u16, auth_key: PrivateKey) -> Self { let server_address = format!("{}:{}", server_ip, server_port); + let api = ApiPaths::new(server_address); let client = reqwest::Client::new(); Self { - server_address, + api, client, auth_key, } } #[instrument(level = "debug", skip_all)] - pub(crate) async fn request_testrun(&self) -> anyhow::Result> { - let target_url = self.api_with_subpath(None::); + pub async fn request_testrun(&self) -> anyhow::Result> { + let target_url = self.api.request_testrun(); let payload = get_testrun::Payload { agent_public_key: self.auth_key.public_key(), @@ -93,13 +66,13 @@ impl Client { } #[instrument(level = "debug", skip(self, probe_result))] - pub(crate) async fn submit_results( + pub async fn submit_results( &self, testrun_id: i64, probe_result: String, assigned_at_utc: i64, ) -> anyhow::Result<()> { - let target_url = self.api_with_subpath(Some(testrun_id)); + let target_url = self.api.submit_results(testrun_id); let payload = submit_results::Payload { probe_result, @@ -129,12 +102,4 @@ impl Client { let signed = self.auth_key.sign(&serialized); Ok(signed) } - - fn api_with_subpath(&self, subpath: Option) -> String { - if let Some(subpath) = subpath { - format!("{}/{}/{}", self.server_address, INTERNAL_TESTRUNS, subpath) - } else { - format!("{}/{}", self.server_address, INTERNAL_TESTRUNS) - } - } } diff --git a/common/models/src/ns_api.rs b/nym-node-status-api/nym-node-status-client/src/models.rs similarity index 67% rename from common/models/src/ns_api.rs rename to nym-node-status-api/nym-node-status-client/src/models.rs index 6f4b00a888..05860259ba 100644 --- a/common/models/src/ns_api.rs +++ b/nym-node-status-api/nym-node-status-client/src/models.rs @@ -1,7 +1,9 @@ -use nym_crypto::asymmetric::ed25519::{PublicKey, Signature, SignatureError}; +use nym_crypto::asymmetric::ed25519::{PublicKey, Signature}; use serde::{Deserialize, Serialize}; pub mod get_testrun { + use crate::auth::SignedRequest; + use super::*; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Payload { @@ -40,6 +42,8 @@ pub struct TestrunAssignment { } pub mod submit_results { + use crate::auth::SignedRequest; + use super::*; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Payload { @@ -70,32 +74,3 @@ pub mod submit_results { } } } - -pub trait SignedRequest { - type Payload: serde::Serialize; - - fn public_key(&self) -> &PublicKey; - fn signature(&self) -> &Signature; - fn payload(&self) -> &Self::Payload; -} - -pub trait VerifiableRequest: SignedRequest { - type Error: From + From; - - fn verify_signature(&self) -> Result<(), Self::Error> { - bincode::serialize(self.payload()) - .map_err(Self::Error::from) - .and_then(|serialized| { - self.public_key() - .verify(serialized, self.signature()) - .map_err(Self::Error::from) - }) - } -} - -impl VerifiableRequest for T -where - T: SignedRequest, -{ - type Error = anyhow::Error; -} From 96ebe3fc4fd7b0cefcfc07653b6f96e4c2c03ad4 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:07:01 +0100 Subject: [PATCH 11/80] Fix overflow (#5184) --- Cargo.lock | 2 +- nym-node-status-api/nym-node-status-api/Cargo.toml | 2 +- nym-node-status-api/nym-node-status-api/src/monitor/mod.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7c3b1f622..ca7a56a4d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "1.0.0-rc.2" +version = "1.0.0-rc.3" dependencies = [ "anyhow", "axum 0.7.7", diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index cc95396f86..0dfb0caffb 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "1.0.0-rc.2" +version = "1.0.0-rc.3" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index d0d1c5f638..7b6c85b6d5 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -219,7 +219,8 @@ async fn run( let recently_unbonded_mixnodes = queries::ensure_mixnodes_still_bonded(pool, &mixnodes).await?; let count_bonded_mixnodes_reserve = 0; // TODO: NymAPI doesn't report the reserve set size - let count_bonded_mixnodes_inactive = count_bonded_mixnodes - count_bonded_mixnodes_active; + let count_bonded_mixnodes_inactive = + count_bonded_mixnodes.saturating_sub(count_bonded_mixnodes_active); let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(pool).await?; From 0b52224917c99b18a9954d43ffff9a581ccd8c4f Mon Sep 17 00:00:00 2001 From: benedetta davico <46782255+benedettadavico@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:26:37 +0100 Subject: [PATCH 12/80] Update network_tunnel_manager.sh --- scripts/network_tunnel_manager.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 6bb7d1275d..e54e561bf6 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -33,9 +33,11 @@ fetch_and_display_ipv6() { } adjust_ip_forwarding() { - echo "adjusting IP forwarding settings..." - sudo sysctl -w net.ipv6.conf.all.forwarding=1 - sudo sysctl -w net.ipv4.ip_forward=1 + ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1" + ipv4_forwarding_setting="net.ipv4.ip_forward=1" + echo "$ipv6_forwarding_setting" | sudo tee -a /etc/sysctl.conf + echo "$ipv4_forwarding_setting" | sudo tee -a /etc/sysctl.conf + sysctl -p /etc/sysctl.conf } apply_iptables_rules() { From cd2ad0adbbc3623f29403a8e86793e66243cb7c2 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:50:55 +0100 Subject: [PATCH 13/80] Update dir in workflow (#5185) --- .github/workflows/push-node-status-agent.yaml | 4 ++-- .github/workflows/push-node-status-api.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push-node-status-agent.yaml b/.github/workflows/push-node-status-agent.yaml index 9f88f5c162..a30ef58f77 100644 --- a/.github/workflows/push-node-status-agent.yaml +++ b/.github/workflows/push-node-status-agent.yaml @@ -8,7 +8,7 @@ on: description: Which gateway probe git ref to build the image with env: - WORKING_DIRECTORY: "nym-node-status-agent" + WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-agent" CONTAINER_NAME: "node-status-agent" jobs: @@ -58,4 +58,4 @@ jobs: - name: BuildAndPushImageOnHarbor run: | docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} - docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags \ No newline at end of file + docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags diff --git a/.github/workflows/push-node-status-api.yaml b/.github/workflows/push-node-status-api.yaml index ed1bc660e3..acf857109d 100644 --- a/.github/workflows/push-node-status-api.yaml +++ b/.github/workflows/push-node-status-api.yaml @@ -3,7 +3,7 @@ on: workflow_dispatch: env: - WORKING_DIRECTORY: "nym-node-status-api" + WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api" CONTAINER_NAME: "node-status-api" jobs: From 09633dead134fc80f35c3deb0a479f25a440c378 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 10:38:13 +0100 Subject: [PATCH 14/80] add the enable ip forwarding method --- scripts/network_tunnel_manager.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index e54e561bf6..c135864d90 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -209,6 +209,9 @@ joke_through_wg_tunnel) configure_dns_and_icmp_wg) configure_dns_and_icmp_wg ;; +adjust_ip_forwarding) + adjust_ip_forwarding + ;; *) echo "Usage: $0 [command]" echo "Commands:" @@ -224,6 +227,7 @@ configure_dns_and_icmp_wg) echo " joke_through_the_mixnet - Fetch a joke via nymtun0." echo " joke_through_wg_tunnel - Fetch a joke via nymwg." echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns" + echo " adjust_ip_forwarding - Enable IPV6 and IPV4 forwarding" exit 1 ;; esac From 9ac0595a35cd4982ad63468ad9c8ed3f36c17b1f Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 11:49:29 +0100 Subject: [PATCH 15/80] remove duplicate iptable rules --- scripts/network_tunnel_manager.sh | 83 +++++++++++++++---------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index c135864d90..81020047d0 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -27,11 +27,44 @@ fetch_and_display_ipv6() { ipv6_address=$(ip -6 addr show "$network_device" scope global | grep inet6 | awk '{print $2}') if [[ -z "$ipv6_address" ]]; then echo "no global IPv6 address found on $network_device." - elsen + else echo "IPv6 address on $network_device: $ipv6_address" fi } +remove_duplicate_rules() { + local interface=$1 + local script_name=$(basename "$0") + + if [[ -z "$interface" ]]; then + echo "error: no interface specified. please enter the interface (nymwg or nymtun0):" + read -r interface + fi + + if [[ "$interface" != "nymwg" && "$interface" != "nymtun0" ]]; then + echo "error: invalid interface '$interface'. allowed values are 'nymwg' or 'nymtun0'." >&2 + exit 1 + fi + + echo "removing duplicate rules for $interface..." + + iptables-save | grep "$interface" | while read -r line; do + sudo iptables -D ${line#-A } || echo "Failed to delete rule: $line" + done + + ip6tables-save | grep "$interface" | while read -r line; do + sudo ip6tables -D ${line#-A } || echo "Failed to delete rule: $line" + done + + echo "duplicates removed for $interface." + echo "!!-important-!! you need to now reapply the iptables rules for $interface." + if [ "$interface" == "nymwg" ]; then + echo "run: ./$script_name apply_iptables_rules_wg" + else + echo "run: ./$script_name apply_iptables_rules" + fi +} + adjust_ip_forwarding() { ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1" ipv4_forwarding_setting="net.ipv4.ip_forward=1" @@ -45,22 +78,10 @@ apply_iptables_rules() { echo "applying IPtables rules for $interface..." sleep 2 - # remove duplicates for IPv4 - sudo iptables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo iptables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo iptables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # remove duplicates for IPv6 - sudo ip6tables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo ip6tables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # add new rules for IPv4 sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - # add new rules for IPv6 sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT @@ -69,36 +90,6 @@ apply_iptables_rules() { sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 } -apply_iptables_rules_wg() { - local interface=$wg_tunnel_interface - echo "applying IPtables rules for WireGuard ($interface)..." - sleep 2 - - # remove duplicates for IPv4 - sudo iptables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo iptables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo iptables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # remove duplicates for IPv6 - sudo ip6tables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo ip6tables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # add new rules for IPv4 - sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE - sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT - sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - - # add new rules for IPv6 - sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE - sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT - sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - - sudo iptables-save | sudo tee /etc/iptables/rules.v4 - sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 -} - - check_tunnel_iptables() { local interface=$1 echo "inspecting IPtables rules for $interface..." @@ -167,7 +158,7 @@ configure_dns_and_icmp_wg() { sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT echo "saving iptables rules..." - sudo iptables-save > /etc/iptables/rules.v4 + sudo iptables-save >/etc/iptables/rules.v4 echo "dns and icmp configuration completed." } @@ -212,6 +203,9 @@ configure_dns_and_icmp_wg) adjust_ip_forwarding) adjust_ip_forwarding ;; +remove_duplicate_rules) + remove_duplicate_rules "$2" + ;; *) echo "Usage: $0 [command]" echo "Commands:" @@ -228,6 +222,7 @@ adjust_ip_forwarding) echo " joke_through_wg_tunnel - Fetch a joke via nymwg." echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns" echo " adjust_ip_forwarding - Enable IPV6 and IPV4 forwarding" + echo " remove_duplicate_rules - Remove duplicate iptables rules. Valid interfaces: nymwg, nymtun0" exit 1 ;; esac From 7c1ad7d20ce08bda9f0235b76aca65dc02129ce3 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:02:13 +0100 Subject: [PATCH 16/80] add more output on joke commands this should help the end users debug their machines further --- scripts/network_tunnel_manager.sh | 51 ++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 81020047d0..df86a57ac6 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -127,23 +127,58 @@ perform_pings() { joke_through_tunnel() { local interface=$1 echo "checking tunnel connectivity and fetching a joke for $interface..." - ipv4_address=$(ip addr show "$interface" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) - ipv6_address=$(ip addr show "$interface" | grep 'inet6 ' | awk '{print $2}' | grep -v '^fe80:' | cut -d'/' -f1) + + ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1) + ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1) if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then - echo "no IP address found on $interface. Unable to fetch a joke." - return + echo "No IP address found on $interface. Unable to fetch a joke." + echo "Please verify your tunnel configuration and ensure the interface is up." + return 1 fi if [[ -n "$ipv4_address" ]]; then - joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv4 joke: $joke" || echo "Failed to fetch a joke via IPv4." + echo "detected IPv4 address: $ipv4_address" + echo "testing IPv4 connectivity..." + echo "if this test succeeds, it confirms your machine can reach the outside world via IPv4." + echo "however, probes and external clients may experience different connectivity to your probe." + + if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then + echo "IPv4 connectivity is working. Fetching a joke..." + joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke) + [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv4 joke: $joke" || echo "Failed to fetch a joke via IPv4." + else + echo "IPv4 connectivity is not working for $interface. verify your routing and NAT settings." + fi fi if [[ -n "$ipv6_address" ]]; then - joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv6 joke: $joke" || echo "Failed to fetch a joke via IPv6." + echo "detected IPv6 address: $ipv6_address" + echo "testing IPv6 connectivity..." + echo "if this test succeeds, it confirms your machine can reach the outside world via IPv6." + echo "however, probes and external clients may experience different connectivity to your nym-node." + + if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then + echo "IPv6 connectivity is working. Fetching a joke..." + joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke) + [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv6 joke: $joke" || echo "Failed to fetch a joke via IPv6." + else + echo "IPv6 connectivity is not working for $interface. verify your routing and NAT settings." + fi fi + + echo "joke fetching process completed for $interface." + sleep 1 + echo + echo "### Connectivity Testing Recommendations ###" + echo "- use the following command to test WebSocket connectivity from an external client:" + echo " wscat -c wss://:9001" + echo + echo "- test UDP connectivity on port 51822 (commonly used for nym wireguard):" + echo " From another machine, use tools like nc or socat to send UDP packets:" + echo " echo 'test message' | nc -u 51822" + echo + echo "if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured" } configure_dns_and_icmp_wg() { From af9f7b1c0f57476df66e82b761a5272b07fe7713 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:02:45 +0100 Subject: [PATCH 17/80] formatting --- scripts/network_tunnel_manager.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index df86a57ac6..7e922e42ee 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -132,8 +132,8 @@ joke_through_tunnel() { ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1) if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then - echo "No IP address found on $interface. Unable to fetch a joke." - echo "Please verify your tunnel configuration and ensure the interface is up." + echo "no IP address found on $interface. unable to fetch a joke." + echo "please verify your tunnel configuration and ensure the interface is up." return 1 fi @@ -170,7 +170,7 @@ joke_through_tunnel() { echo "joke fetching process completed for $interface." sleep 1 echo - echo "### Connectivity Testing Recommendations ###" + echo "### connectivity testing recommendations ###" echo "- use the following command to test WebSocket connectivity from an external client:" echo " wscat -c wss://:9001" echo From 42de6209511decd831ab9585606f3f58d860698b Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:06:03 +0100 Subject: [PATCH 18/80] typo --- scripts/network_tunnel_manager.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 7e922e42ee..70a8fbcd2a 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -172,10 +172,10 @@ joke_through_tunnel() { echo echo "### connectivity testing recommendations ###" echo "- use the following command to test WebSocket connectivity from an external client:" - echo " wscat -c wss://:9001" + echo " wscat -c wss://:9001" echo echo "- test UDP connectivity on port 51822 (commonly used for nym wireguard):" - echo " From another machine, use tools like nc or socat to send UDP packets:" + echo " from another machine, use tools like nc or socat to send UDP packets:" echo " echo 'test message' | nc -u 51822" echo echo "if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured" From f95f01959c168c6c860840518fe4987312fedb27 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:29:29 +0100 Subject: [PATCH 19/80] fix multiple forwarding calls also add more logging around joke section --- scripts/network_tunnel_manager.sh | 79 +++++++++++++++++++------------ 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 70a8fbcd2a..93e6f73ada 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -68,9 +68,16 @@ remove_duplicate_rules() { adjust_ip_forwarding() { ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1" ipv4_forwarding_setting="net.ipv4.ip_forward=1" + + # remove duplicate entries for these settings from the file + sudo sed -i "/^net.ipv6.conf.all.forwarding=/d" /etc/sysctl.conf + sudo sed -i "/^net.ipv4.ip_forward=/d" /etc/sysctl.conf + echo "$ipv6_forwarding_setting" | sudo tee -a /etc/sysctl.conf echo "$ipv4_forwarding_setting" | sudo tee -a /etc/sysctl.conf - sysctl -p /etc/sysctl.conf + + sudo sysctl -p /etc/sysctl.conf + } apply_iptables_rules() { @@ -126,61 +133,75 @@ perform_pings() { joke_through_tunnel() { local interface=$1 - echo "checking tunnel connectivity and fetching a joke for $interface..." + local green="\033[0;32m" + local reset="\033[0m" + local red="\033[0;31m" + local yellow="\033[0;33m" + + sleep 1 + echo + echo -e "${yellow}checking tunnel connectivity and fetching a joke for $interface...${reset}" + echo -e "${yellow}if these test succeeds, it confirms your machine can reach the outside world via IPv4 and IPv6.${reset}" + echo -e "${yellow}however, probes and external clients may experience different connectivity to your nym-node.${reset}" ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1) ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1) if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then - echo "no IP address found on $interface. unable to fetch a joke." - echo "please verify your tunnel configuration and ensure the interface is up." + echo -e "${red}no IP address found on $interface. unable to fetch a joke.${reset}" + echo -e "${red}please verify your tunnel configuration and ensure the interface is up.${reset}" return 1 fi - + if [[ -n "$ipv4_address" ]]; then - echo "detected IPv4 address: $ipv4_address" - echo "testing IPv4 connectivity..." - echo "if this test succeeds, it confirms your machine can reach the outside world via IPv4." - echo "however, probes and external clients may experience different connectivity to your probe." + echo + echo -e "------------------------------------" + echo -e "detected IPv4 address: $ipv4_address" + echo -e "testing IPv4 connectivity..." + echo if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then - echo "IPv4 connectivity is working. Fetching a joke..." + echo -e "${green}IPv4 connectivity is working. fetching a joke...${reset}" joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv4 joke: $joke" || echo "Failed to fetch a joke via IPv4." + [[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv4 joke: $joke${reset}" || echo -e "failed to fetch a joke via IPv4." else - echo "IPv4 connectivity is not working for $interface. verify your routing and NAT settings." + echo -e "${red}IPv4 connectivity is not working for $interface. verify your routing and NAT settings.${reset}" fi fi if [[ -n "$ipv6_address" ]]; then - echo "detected IPv6 address: $ipv6_address" - echo "testing IPv6 connectivity..." - echo "if this test succeeds, it confirms your machine can reach the outside world via IPv6." - echo "however, probes and external clients may experience different connectivity to your nym-node." + echo + echo -e "------------------------------------" + echo -e "detected IPv6 address: $ipv6_address" + echo -e "testing IPv6 connectivity..." + echo if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then - echo "IPv6 connectivity is working. Fetching a joke..." + echo -e "${green}IPv6 connectivity is working. fetching a joke...${reset}" joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv6 joke: $joke" || echo "Failed to fetch a joke via IPv6." + [[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv6 joke: $joke${reset}" || echo -e "${red}failed to fetch a joke via IPv6.${reset}" else - echo "IPv6 connectivity is not working for $interface. verify your routing and NAT settings." + echo -e "${red}IPv6 connectivity is not working for $interface. verify your routing and NAT settings.${reset}" fi fi - echo "joke fetching process completed for $interface." - sleep 1 + echo -e "${green}joke fetching processes completed for $interface.${reset}" + echo -e "------------------------------------" + + sleep 3 echo - echo "### connectivity testing recommendations ###" - echo "- use the following command to test WebSocket connectivity from an external client:" - echo " wscat -c wss://:9001" - echo - echo "- test UDP connectivity on port 51822 (commonly used for nym wireguard):" - echo " from another machine, use tools like nc or socat to send UDP packets:" - echo " echo 'test message' | nc -u 51822" echo - echo "if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured" + echo -e "${yellow}### connectivity testing recommendations ###${reset}" + echo -e "${yellow}- use the following command to test WebSocket connectivity from an external client:${reset}" + echo -e "${yellow} wscat -c wss://:9001 ${reset}" + echo -e "${yellow}- test UDP connectivity on port 51822 (commonly used for nym wireguard) ${reset}" + echo -e "${yellow} from another machine, use tools like nc or socat to send UDP packets ${reset}" + echo -e "${yellow} echo 'test message' | nc -u 51822 ${reset}" + echo -e "${yellow}if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured ${reset}" + echo } + configure_dns_and_icmp_wg() { echo "allowing icmp (ping)..." sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT From 074d705448f746c8f8dc2e5e519ea1c5d27bf755 Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:34:58 +0000 Subject: [PATCH 20/80] [DOCs/operators]: Magura-drift - second patch (#5194) * syntax edits * new version harsh * changelog info - ready to review --- .../docs/pages/operators/changelog.mdx | 25 +++++++++++++++++-- .../nodes/maintenance/manual-upgrade.mdx | 2 +- .../operators/nodes/nym-node/bonding.mdx | 2 +- .../pages/operators/nodes/nym-node/setup.mdx | 10 ++++---- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index b498b5156c..06ac73d2dc 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -34,6 +34,28 @@ This page displays a full list of all the changes during our release cycle from +## `magura-drift` + +Second patch to `v2024.13-magura` release version. + +- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.13-magura-drift) +- [`nym-node`](nodes/nym-node.mdx) version `1.1.12` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2024-11-29T13:10:51.813092288Z +Build Version: 1.1.12 +Commit SHA: 4a9a5579c40ad956163ea02e01d7b53aef2ac8ef +Commit Date: 2024-11-29T14:06:32.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.83.0 +rustc Channel: stable +cargo Profile: release +``` + +- This patch adds a peer storage manager to fix issues causing external clients to be blocked, ensuring they can successfully connect to different nodes. + ## `v2024.13-magura-patched` - [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.13-magura-patched) @@ -54,12 +76,11 @@ cargo Profile: release -After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Insstead of fixing this outdated explorer, we are working on a new one, coming out soon. +After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Instead of fixing this outdated explorer, we are working on a new one, coming out soon. [Nym Harbourmaster](https://harbourmaster.nymtech.net) has cache of 90min, expect your values to be updated with delay. We are aware of some issues with Nym Harbourmaster and working hard to resolve them in the upcoming explorer v2. To check your routing values in real time, you can use [`nym-gateway-probe`](nodes/performance-and-testing/gateway-probe). - ### Operators Updates & Tools - Updated [`network_tunnel_manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh) (moved to our monorepo) helps operators to configure their IP tables rules for `nymtun` and `wireguard` routing. diff --git a/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx b/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx index a68d01b1fd..8b3db6b61b 100644 --- a/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx +++ b/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx @@ -53,7 +53,7 @@ journalctl -f -u nym-node.service -After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Insstead of fixing this outdated explorer, we are working on a new one, coming out soon. +After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Instead of fixing this outdated explorer, we are working on a new one, coming out soon. [Nym Harbourmaster](https://harbourmaster.nymtech.net) has cache of 90min, expect your values to be updated with delay. We are aware of some issues with Nym Harbourmaster and working hard to resolve them in the upcoming explorer v2. To check your routing values in real time, you can use [`nym-gateway-probe`](../performance-and-testing/gateway-probe). diff --git a/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx b/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx index 44a46ac714..3c64fe9b29 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx @@ -120,7 +120,7 @@ From `nym-wallet` version `1.2.15` onward the application allows and prompts ope ###### 2. Verify the binary and extract it if needed -- Download [`hashes.json`]https://github.com/nymtech/nym/releases/download/nym-wallet-v1.2.15/hashes.json +- Download [`hashes.json`](https://github.com/nymtech/nym/releases/download/nym-wallet-v1.2.15/hashes.json) - Open it with your text editor or print it's content with `cat hashes.json` - Run `sha256sum ` for example `sha256sum ./nym-wallet_1.2.15_amd64.AppImage` - If your have to extract it (like `.tar.gz`) do it diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 31eb89308c..f2d6197fee 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -17,12 +17,12 @@ 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: 2024-11-22T14:30:48.067329245Z -Build Version: 1.1.11 -Commit SHA: 01c7b2819ee3d328deccd303b4113ff415d7e276 -Commit Date: 2024-11-22T10:50:59.000000000+01:00 +Build Timestamp: 2024-11-29T13:10:51.813092288Z +Build Version: 1.1.12 +Commit SHA: 4a9a5579c40ad956163ea02e01d7b53aef2ac8ef +Commit Date: 2024-11-29T14:06:32.000000000+01:00 Commit Branch: HEAD -rustc Version: 1.82.0 +rustc Version: 1.83.0 rustc Channel: stable cargo Profile: release ``` From a9e62889c3e7ce7c6810feff31c91f1824af42e0 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:45:55 +0100 Subject: [PATCH 21/80] Remove explorer dependency (#5190) * Move monitor code to a struct - to store state in a struct * explorer deprecation wip * Replace explorer with ipinfo calls * PR feedback * Fix clippy * Bump package version * Remove ipinfo crate due to openssl dep * Add remaining bandwidth log --- Cargo.lock | 6 +- .../src/client/topology_control/accessor.rs | 2 +- .../validator-client/src/nyxd/coin.rs | 2 +- .../src/nyxd/fee/gas_price.rs | 2 +- .../mixnet-contract/src/interval.rs | 2 +- common/dkg/src/bte/proof_chunking.rs | 3 +- common/dkg/src/interpolation/polynomial.rs | 4 +- .../src/registration/handshake/mod.rs | 2 +- .../src/scheme/coin_indices_signatures.rs | 12 - .../src/scheme/keygen.rs | 2 +- .../src/scheme/mod.rs | 2 +- common/nymcoconut/src/elgamal.rs | 2 +- common/nymcoconut/src/scheme/keygen.rs | 2 +- common/nymsphinx/addressing/src/clients.rs | 2 +- common/nymsphinx/addressing/src/nodes.rs | 14 +- .../anonymous-replies/src/reply_surb.rs | 2 +- common/socks5/requests/src/request.rs | 13 - explorer-api/src/geo_ip/location.rs | 2 +- explorer-api/src/guards/location.rs | 2 +- nym-api/nym-api-requests/src/helpers.rs | 2 +- nym-api/src/ecash/tests/helpers.rs | 2 +- .../monitor/gateway_clients_cache.rs | 2 +- .../src/http/state/mod.rs | 2 +- .../nym-node-status-api/Cargo.toml | 2 +- .../nym-node-status-api/src/cli/mod.rs | 13 +- .../nym-node-status-api/src/db/models.rs | 3 +- .../src/db/queries/summary.rs | 12 +- .../nym-node-status-api/src/main.rs | 6 +- .../src/monitor/geodata.rs | 112 +++ .../nym-node-status-api/src/monitor/mod.rs | 701 +++++++++--------- sdk/ffi/go/src/lib.rs | 3 + .../dkg-bypass-contract/src/contract.rs | 4 +- .../testnet-manager/src/manager/dkg_skip.rs | 6 +- .../testnet-manager/src/manager/local_apis.rs | 2 +- .../src/manager/local_client.rs | 2 +- .../src/manager/local_nodes.rs | 2 +- 36 files changed, 539 insertions(+), 415 deletions(-) create mode 100644 nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs diff --git a/Cargo.lock b/Cargo.lock index ca7a56a4d2..936dadfbd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "1.0.0-rc.3" +version = "1.0.0-rc.4" dependencies = [ "anyhow", "axum 0.7.7", @@ -7019,9 +7019,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", diff --git a/common/client-core/src/client/topology_control/accessor.rs b/common/client-core/src/client/topology_control/accessor.rs index fcb272c934..6b12d64562 100644 --- a/common/client-core/src/client/topology_control/accessor.rs +++ b/common/client-core/src/client/topology_control/accessor.rs @@ -38,7 +38,7 @@ pub struct TopologyReadPermit<'a> { permit: RwLockReadGuard<'a, Option>, } -impl<'a> Deref for TopologyReadPermit<'a> { +impl Deref for TopologyReadPermit<'_> { type Target = Option; fn deref(&self) -> &Self::Target { diff --git a/common/client-libs/validator-client/src/nyxd/coin.rs b/common/client-libs/validator-client/src/nyxd/coin.rs index 0fa175a243..8050273a51 100644 --- a/common/client-libs/validator-client/src/nyxd/coin.rs +++ b/common/client-libs/validator-client/src/nyxd/coin.rs @@ -32,7 +32,7 @@ impl Div for Coin { } } -impl<'a> Div for &'a Coin { +impl Div for &Coin { type Output = Gas; fn div(self, rhs: GasPrice) -> Self::Output { diff --git a/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs index 4f0f2fd0a8..3679cc7500 100644 --- a/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs @@ -22,7 +22,7 @@ pub struct GasPrice { pub denom: String, } -impl<'a> Mul for &'a GasPrice { +impl Mul for &GasPrice { type Output = Coin; fn mul(self, gas_limit: Gas) -> Self::Output { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index e36935d66c..2259af4c7a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -32,7 +32,7 @@ pub(crate) mod string_rfc3339_offset_date_time { struct Rfc3339OffsetDateTimeVisitor; - impl<'de> Visitor<'de> for Rfc3339OffsetDateTimeVisitor { + impl Visitor<'_> for Rfc3339OffsetDateTimeVisitor { type Value = OffsetDateTime; fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/common/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs index 6829a53660..1365aecbd7 100644 --- a/common/dkg/src/bte/proof_chunking.rs +++ b/common/dkg/src/bte/proof_chunking.rs @@ -26,9 +26,8 @@ const PARALLEL_RUNS: usize = 32; /// `lambda` ($\lambda$) in the DKG paper const SECURITY_PARAMETER: usize = 256; -// note: ceiling in integer division can be achieved via q = (x + y - 1) / y; /// ceil(SECURITY_PARAMETER / PARALLEL_RUNS) in the paper -const NUM_CHALLENGE_BITS: usize = (SECURITY_PARAMETER + PARALLEL_RUNS - 1) / PARALLEL_RUNS; +const NUM_CHALLENGE_BITS: usize = SECURITY_PARAMETER.div_ceil(PARALLEL_RUNS); // type alias for ease of use type FirstChallenge = Vec>>; diff --git a/common/dkg/src/interpolation/polynomial.rs b/common/dkg/src/interpolation/polynomial.rs index 33c7486a99..08f748b649 100644 --- a/common/dkg/src/interpolation/polynomial.rs +++ b/common/dkg/src/interpolation/polynomial.rs @@ -196,7 +196,7 @@ impl<'b> Add<&'b Polynomial> for Polynomial { } } -impl<'a> Add for &'a Polynomial { +impl Add for &Polynomial { type Output = Polynomial; fn add(self, rhs: Polynomial) -> Polynomial { @@ -212,7 +212,7 @@ impl Add for Polynomial { } } -impl<'a, 'b> Add<&'b Polynomial> for &'a Polynomial { +impl<'b> Add<&'b Polynomial> for &Polynomial { type Output = Polynomial; fn add(self, rhs: &'b Polynomial) -> Self::Output { diff --git a/common/gateway-requests/src/registration/handshake/mod.rs b/common/gateway-requests/src/registration/handshake/mod.rs index e5dc1dc59f..4a6f44b7d7 100644 --- a/common/gateway-requests/src/registration/handshake/mod.rs +++ b/common/gateway-requests/src/registration/handshake/mod.rs @@ -37,7 +37,7 @@ pub struct GatewayHandshake<'a> { handshake_future: BoxFuture<'a, Result>, } -impl<'a> Future for GatewayHandshake<'a> { +impl Future for GatewayHandshake<'_> { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { diff --git a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs index b08ab368dc..30c7fe5523 100644 --- a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs +++ b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs @@ -324,18 +324,6 @@ pub fn unchecked_aggregate_indices_signatures( _aggregate_indices_signatures(params, vk, signatures_shares, false) } -/// Generates parameters for the scheme setup. -/// -/// # Arguments -/// -/// * `total_coins` - it is the number of coins in a freshly generated wallet. It is the public parameter of the scheme. -/// -/// # Returns -/// -/// A `Parameters` struct containing group parameters, public key, the number of signatures (`total_coins`), -/// and a map of signatures for each index `l`. -/// - #[cfg(test)] mod tests { use super::*; diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index e49c6309df..8db1597e61 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -264,7 +264,7 @@ impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth { } } -impl<'a> Mul for &'a VerificationKeyAuth { +impl Mul for &VerificationKeyAuth { type Output = VerificationKeyAuth; #[inline] diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 8912bf91eb..7f9687a380 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -984,7 +984,7 @@ pub struct SerialNumberRef<'a> { pub(crate) inner: &'a [G1Projective], } -impl<'a> SerialNumberRef<'a> { +impl SerialNumberRef<'_> { pub fn to_bytes(&self) -> Vec { let ss_len = self.inner.len(); let mut bytes: Vec = Vec::with_capacity(ss_len * 48); diff --git a/common/nymcoconut/src/elgamal.rs b/common/nymcoconut/src/elgamal.rs index 55db7dcdd2..28943568ce 100644 --- a/common/nymcoconut/src/elgamal.rs +++ b/common/nymcoconut/src/elgamal.rs @@ -206,7 +206,7 @@ impl Deref for PublicKey { } } -impl<'a, 'b> Mul<&'b Scalar> for &'a PublicKey { +impl<'b> Mul<&'b Scalar> for &PublicKey { type Output = G1Projective; fn mul(self, rhs: &'b Scalar) -> Self::Output { diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index 85e057793d..f80c131143 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -305,7 +305,7 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey { } } -impl<'a> Mul for &'a VerificationKey { +impl Mul for &VerificationKey { type Output = VerificationKey; #[inline] diff --git a/common/nymsphinx/addressing/src/clients.rs b/common/nymsphinx/addressing/src/clients.rs index a739e7369e..743302edbe 100644 --- a/common/nymsphinx/addressing/src/clients.rs +++ b/common/nymsphinx/addressing/src/clients.rs @@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient { { struct RecipientVisitor; - impl<'de> Visitor<'de> for RecipientVisitor { + impl Visitor<'_> for RecipientVisitor { type Value = Recipient; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { diff --git a/common/nymsphinx/addressing/src/nodes.rs b/common/nymsphinx/addressing/src/nodes.rs index e0cd745ae9..1126cb7afd 100644 --- a/common/nymsphinx/addressing/src/nodes.rs +++ b/common/nymsphinx/addressing/src/nodes.rs @@ -1,6 +1,13 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +//! Encodoing and decoding node routing information. +//! +//! This module is responsible for encoding and decoding node routing information, so that +//! they could be later put into an appropriate field in a sphinx header. +//! Currently, that routing information is an IP address, but in principle it can be anything +//! for as long as it's going to fit in the field. + use nym_crypto::asymmetric::identity; use nym_sphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH}; @@ -12,13 +19,6 @@ use thiserror::Error; pub type NodeIdentity = identity::PublicKey; pub const NODE_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH; -/// Encodoing and decoding node routing information. -/// -/// This module is responsible for encoding and decoding node routing information, so that -/// they could be later put into an appropriate field in a sphinx header. -/// Currently, that routing information is an IP address, but in principle it can be anything -/// for as long as it's going to fit in the field. - /// MAX_UNPADDED_LEN represents maximum length an unpadded address could have. /// In this case it's an ipv6 socket address (with version prefix) pub const MAX_NODE_ADDRESS_UNPADDED_LEN: usize = 19; diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index deac5f532d..f25871ba0b 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -56,7 +56,7 @@ impl<'de> Deserialize<'de> for ReplySurb { { struct ReplySurbVisitor; - impl<'de> Visitor<'de> for ReplySurbVisitor { + impl Visitor<'_> for ReplySurbVisitor { type Value = ReplySurb; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 23e844db87..59163daf55 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -253,25 +253,12 @@ impl Socks5RequestContent { /// Deserialize the request type, connection id, destination address and port, /// and the request body from bytes. /// - // TODO: this was already inaccurate - // /// Serialized bytes looks like this: - // /// - // /// -------------------------------------------------------------------------------------- - // /// request_flag | connection_id | address_length | remote_address_bytes | request_data | - // /// 1 | 8 | 2 | address_length | ... | - // /// -------------------------------------------------------------------------------------- - /// - /// The request_flag tells us whether this is a new connection request (`new_connect`), - /// an already-established connection we should send up (`new_send`), or - /// a request to close an established connection (`new_close`). - // connect: // RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || // // send: // RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA // where DATA: SEQ || TRUE_DATA - pub fn try_from_bytes(b: &[u8]) -> Result { // each request needs to at least contain flag and ConnectionId if b.is_empty() { diff --git a/explorer-api/src/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs index 59bbb33ad7..263764943e 100644 --- a/explorer-api/src/geo_ip/location.rs +++ b/explorer-api/src/geo_ip/location.rs @@ -166,7 +166,7 @@ impl GeoIp { } } -impl<'a> TryFrom<&City<'a>> for Location { +impl TryFrom<&City<'_>> for Location { type Error = String; fn try_from(city: &City) -> Result { diff --git a/explorer-api/src/guards/location.rs b/explorer-api/src/guards/location.rs index 045b03f5b4..f5ce0b7ee2 100644 --- a/explorer-api/src/guards/location.rs +++ b/explorer-api/src/guards/location.rs @@ -65,7 +65,7 @@ impl<'r> FromRequest<'r> for Location { } } -impl<'a> OpenApiFromRequest<'a> for Location { +impl OpenApiFromRequest<'_> for Location { fn from_request_input( _gen: &mut OpenApiGenerator, _name: String, diff --git a/nym-api/nym-api-requests/src/helpers.rs b/nym-api/nym-api-requests/src/helpers.rs index 651030fc74..292ffb69f4 100644 --- a/nym-api/nym-api-requests/src/helpers.rs +++ b/nym-api/nym-api-requests/src/helpers.rs @@ -64,7 +64,7 @@ pub(crate) mod overengineered_offset_date_time_serde { ])), ]; - impl<'de> Visitor<'de> for OffsetDateTimeVisitor { + impl Visitor<'_> for OffsetDateTimeVisitor { type Value = OffsetDateTime; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { diff --git a/nym-api/src/ecash/tests/helpers.rs b/nym-api/src/ecash/tests/helpers.rs index 5cd410056f..73fac38606 100644 --- a/nym-api/src/ecash/tests/helpers.rs +++ b/nym-api/src/ecash/tests/helpers.rs @@ -76,7 +76,7 @@ pub(crate) async fn submit_public_keys(controllers: &mut [TestingDkgController], .unwrap(); } - let threshold = (2 * controllers.len() as u64 + 3 - 1) / 3; + let threshold = (2 * controllers.len() as u64).div_ceil(3); let mut guard = controllers[0].chain_state.lock().unwrap(); guard.dkg_contract.epoch.state = EpochState::DealingExchange { resharing }; diff --git a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs index 4a321d57cd..c791f9a6af 100644 --- a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs +++ b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs @@ -59,7 +59,7 @@ impl GatewayClientHandle { } } -impl<'a> UnlockedGatewayClientHandle<'a> { +impl UnlockedGatewayClientHandle<'_> { pub(crate) fn get_mut_unchecked( &mut self, ) -> &mut GatewayClient { diff --git a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs index c4c4a8ebe3..bcc62f9c7e 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs @@ -674,7 +674,7 @@ pub(crate) struct ChainWritePermit<'a> { inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>, } -impl<'a> Deref for ChainWritePermit<'a> { +impl Deref for ChainWritePermit<'_> { type Target = DirectSigningHttpRpcNyxdClient; fn deref(&self) -> &Self::Target { diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 0dfb0caffb..05d1586ae9 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "1.0.0-rc.3" +version = "1.0.0-rc.4" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs index f329e4a9a0..e9d76a1f75 100644 --- a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs @@ -45,11 +45,6 @@ pub(crate) struct Cli { #[arg(value_parser = parse_duration)] pub(crate) nym_api_client_timeout: Duration, - /// Explorer api client timeout. - #[clap(long, default_value = "15", env = "EXPLORER_CLIENT_TIMEOUT")] - #[arg(value_parser = parse_duration)] - pub(crate) explorer_client_timeout: Duration, - /// Connection url for the database. #[clap(long, env = "DATABASE_URL")] pub(crate) database_url: String, @@ -70,10 +65,18 @@ pub(crate) struct Cli { #[arg(value_parser = parse_duration)] pub(crate) testruns_refresh_interval: Duration, + #[clap(long, default_value = "86400", env = "NODE_STATUS_API_GEODATA_TTL")] + #[arg(value_parser = parse_duration)] + pub(crate) geodata_ttl: Duration, + #[clap(env = "NODE_STATUS_API_AGENT_KEY_LIST")] #[arg(value_delimiter = ',')] pub(crate) agent_key_list: Vec, + /// https://github.com/ipinfo/rust + #[clap(long, env = "IPINFO_API_TOKEN")] + pub(crate) ipinfo_api_token: String, + #[clap( long, default_value_t = 40, diff --git a/nym-node-status-api/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs index 596f634f2e..7d9e93ed24 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/models.rs @@ -12,6 +12,7 @@ pub(crate) struct GatewayRecord { pub(crate) bonded: bool, pub(crate) blacklisted: bool, pub(crate) self_described: String, + // TODO dz shouldn't be an option pub(crate) explorer_pretty_bond: Option, pub(crate) last_updated_utc: i64, pub(crate) performance: u8, @@ -215,7 +216,6 @@ pub(crate) const MIXNODES_BONDED_RESERVE: &str = "mixnodes.bonded.reserve"; pub(crate) const MIXNODES_BLACKLISTED_COUNT: &str = "mixnodes.blacklisted.count"; pub(crate) const GATEWAYS_BONDED_COUNT: &str = "gateways.bonded.count"; -pub(crate) const GATEWAYS_EXPLORER_COUNT: &str = "gateways.explorer.count"; pub(crate) const GATEWAYS_BLACKLISTED_COUNT: &str = "gateways.blacklisted.count"; pub(crate) const MIXNODES_HISTORICAL_COUNT: &str = "mixnodes.historical.count"; @@ -272,7 +272,6 @@ pub(crate) mod gateway { pub(crate) bonded: GatewaySummaryBonded, pub(crate) blacklisted: GatewaySummaryBlacklisted, pub(crate) historical: GatewaySummaryHistorical, - pub(crate) explorer: GatewaySummaryExplorer, } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs index 103712a9a4..4b2ecd22a5 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs @@ -8,7 +8,7 @@ use crate::{ models::{ gateway::{ GatewaySummary, GatewaySummaryBlacklisted, GatewaySummaryBonded, - GatewaySummaryExplorer, GatewaySummaryHistorical, + GatewaySummaryHistorical, }, mixnode::{ MixnodeSummary, MixnodeSummaryBlacklisted, MixnodeSummaryBonded, @@ -82,7 +82,6 @@ async fn from_summary_dto(items: Vec) -> HttpResult const MIXNODES_BONDED_RESERVE: &str = "mixnodes.bonded.reserve"; const MIXNODES_BLACKLISTED_COUNT: &str = "mixnodes.blacklisted.count"; const GATEWAYS_BONDED_COUNT: &str = "gateways.bonded.count"; - const GATEWAYS_EXPLORER_COUNT: &str = "gateways.explorer.count"; const GATEWAYS_BLACKLISTED_COUNT: &str = "gateways.blacklisted.count"; const MIXNODES_HISTORICAL_COUNT: &str = "mixnodes.historical.count"; const GATEWAYS_HISTORICAL_COUNT: &str = "gateways.historical.count"; @@ -96,7 +95,6 @@ async fn from_summary_dto(items: Vec) -> HttpResult // check we have all the keys we are expecting, and build up a map of errors for missing one let keys = [ GATEWAYS_BONDED_COUNT, - GATEWAYS_EXPLORER_COUNT, GATEWAYS_HISTORICAL_COUNT, GATEWAYS_BLACKLISTED_COUNT, MIXNODES_BLACKLISTED_COUNT, @@ -139,10 +137,6 @@ async fn from_summary_dto(items: Vec) -> HttpResult .unwrap_or_default(); let gateways_bonded_count: SummaryDto = map.get(GATEWAYS_BONDED_COUNT).cloned().unwrap_or_default(); - let gateways_explorer_count: SummaryDto = map - .get(GATEWAYS_EXPLORER_COUNT) - .cloned() - .unwrap_or_default(); let mixnodes_historical_count: SummaryDto = map .get(MIXNODES_HISTORICAL_COUNT) .cloned() @@ -187,10 +181,6 @@ async fn from_summary_dto(items: Vec) -> HttpResult count: to_count_i32(&gateways_historical_count), last_updated_utc: to_timestamp(&gateways_historical_count), }, - explorer: GatewaySummaryExplorer { - count: to_count_i32(&gateways_explorer_count), - last_updated_utc: to_timestamp(&gateways_explorer_count), - }, }, }) } diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index 6fcdf34473..6274a01fe5 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -28,13 +28,15 @@ async fn main() -> anyhow::Result<()> { let storage = db::Storage::init(connection_url).await?; let db_pool = storage.pool_owned(); let args_clone = args.clone(); + tokio::spawn(async move { monitor::spawn_in_background( db_pool, - args_clone.explorer_client_timeout, args_clone.nym_api_client_timeout, - &args_clone.nyxd_addr, + args_clone.nyxd_addr, args_clone.monitor_refresh_interval, + args_clone.ipinfo_api_token, + args_clone.geodata_ttl, ) .await; tracing::info!("Started monitor task"); diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs new file mode 100644 index 0000000000..1042b15cce --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs @@ -0,0 +1,112 @@ +use cosmwasm_std::{Addr, Coin}; +use serde::{Deserialize, Serialize}; + +pub(crate) struct IpInfoClient { + client: reqwest::Client, + token: String, +} + +impl IpInfoClient { + pub(crate) fn new(token: impl Into) -> Self { + let client = reqwest::Client::new(); + let token = token.into(); + + Self { client, token } + } + + pub(crate) async fn locate_ip(&self, ip: impl AsRef) -> anyhow::Result { + let url = format!( + "https://ipinfo.io/{}/country?token={}", + ip.as_ref(), + &self.token + ); + let response = self + .client + .get(url) + .send() + .await + // map non 2xx responses to error + .and_then(|res| res.error_for_status()) + .map_err(|err| { + if matches!(err.status(), Some(reqwest::StatusCode::TOO_MANY_REQUESTS)) { + tracing::error!("ipinfo rate limit exceeded"); + } + anyhow::Error::from(err) + })?; + let response_text = response.text().await?.trim().to_string(); + + Ok(Location { + two_letter_iso_country_code: response_text, + }) + } + + /// check DOESN'T consume bandwidth allowance + pub(crate) async fn check_remaining_bandwidth( + &self, + ) -> anyhow::Result { + let url = format!("https://ipinfo.io/me?token={}", &self.token); + let response = self + .client + .get(url) + .send() + .await + // map non 2xx responses to error + .and_then(|res| res.error_for_status()) + .map_err(|err| { + if matches!(err.status(), Some(reqwest::StatusCode::TOO_MANY_REQUESTS)) { + tracing::error!("ipinfo rate limit exceeded"); + } + anyhow::Error::from(err) + })?; + let response: ipinfo::MeResponse = response.json().await?; + + Ok(response.requests) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct NodeGeoData { + pub(crate) identity_key: String, + pub(crate) owner: Addr, + pub(crate) pledge_amount: Coin, + pub(crate) location: Location, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub(crate) struct Location { + pub(crate) two_letter_iso_country_code: String, +} + +impl Location { + pub(crate) fn empty() -> Self { + Self { + two_letter_iso_country_code: String::new(), + } + } +} + +pub(crate) mod ipinfo { + use super::*; + + // clippy doesn't understand it's used for typed deserialization + #[allow(dead_code)] + #[derive(Debug, Clone, Deserialize)] + /// `/me` is undocumented in their developers page + /// https://ipinfo.io/developers/responses + /// but explained here + /// https://community.ipinfo.io/t/easy-way-to-check-allowance-usage/5755/2 + pub(crate) struct MeResponse { + token: String, + pub(crate) requests: MeResponseRequests, + } + + // clippy doesn't understand it's used for typed deserialization + #[allow(dead_code)] + #[derive(Debug, Clone, Deserialize)] + pub(crate) struct MeResponseRequests { + pub(crate) day: u64, + pub(crate) month: u64, + pub(crate) limit: u64, + pub(crate) remaining: u64, + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index 7b6c85b6d5..459992b0b3 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -2,16 +2,17 @@ use crate::db::models::{ gateway, mixnode, GatewayRecord, MixnodeRecord, NetworkSummary, GATEWAYS_BLACKLISTED_COUNT, - GATEWAYS_BONDED_COUNT, GATEWAYS_EXPLORER_COUNT, GATEWAYS_HISTORICAL_COUNT, - MIXNODES_BLACKLISTED_COUNT, MIXNODES_BONDED_ACTIVE, MIXNODES_BONDED_COUNT, - MIXNODES_BONDED_INACTIVE, MIXNODES_BONDED_RESERVE, MIXNODES_HISTORICAL_COUNT, + GATEWAYS_BONDED_COUNT, GATEWAYS_HISTORICAL_COUNT, MIXNODES_BLACKLISTED_COUNT, + MIXNODES_BONDED_ACTIVE, MIXNODES_BONDED_COUNT, MIXNODES_BONDED_INACTIVE, + MIXNODES_BONDED_RESERVE, MIXNODES_HISTORICAL_COUNT, }; use crate::db::{queries, DbPool}; +use crate::monitor::geodata::{Location, NodeGeoData}; use anyhow::anyhow; use cosmwasm_std::Decimal; -use nym_explorer_client::{ExplorerClient, PrettyDetailedGatewayBond}; +use moka::future::Cache; use nym_network_defaults::NymNetworkDetails; -use nym_validator_client::client::NymApiClientExt; +use nym_validator_client::client::{NodeId, NymApiClientExt}; use nym_validator_client::models::{ LegacyDescribedMixNode, MixNodeBondAnnotated, NymNodeDescription, }; @@ -20,40 +21,55 @@ use nym_validator_client::nyxd::contract_traits::PagedMixnetQueryClient; use nym_validator_client::nyxd::{AccountId, NyxdClient}; use nym_validator_client::NymApiClient; use reqwest::Url; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::str::FromStr; use tokio::time::Duration; use tracing::instrument; +pub(crate) use geodata::IpInfoClient; + +mod geodata; + // TODO dz should be configurable const FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60); static DELEGATION_PROGRAM_WALLET: &str = "n1rnxpdpx3kldygsklfft0gech7fhfcux4zst5lw"; +struct Monitor { + db_pool: DbPool, + network_details: NymNetworkDetails, + nym_api_client_timeout: Duration, + nyxd_addr: Url, + ipinfo: IpInfoClient, + geocache: Cache, +} + // TODO dz: query many NYM APIs: // multiple instances running directory cache, ask sachin #[instrument(level = "debug", name = "data_monitor", skip_all)] pub(crate) async fn spawn_in_background( db_pool: DbPool, - explorer_client_timeout: Duration, nym_api_client_timeout: Duration, - nyxd_addr: &Url, + nyxd_addr: Url, refresh_interval: Duration, + ipinfo_api_token: String, + geodata_ttl: Duration, ) { - let network_defaults = nym_network_defaults::NymNetworkDetails::new_from_env(); + let geocache = Cache::builder().time_to_live(geodata_ttl).build(); + let ipinfo = IpInfoClient::new(ipinfo_api_token.clone()); + let mut monitor = Monitor { + db_pool, + network_details: nym_network_defaults::NymNetworkDetails::new_from_env(), + nym_api_client_timeout, + nyxd_addr, + ipinfo, + geocache, + }; loop { tracing::info!("Refreshing node info..."); - if let Err(e) = run( - &db_pool, - &network_defaults, - explorer_client_timeout, - nym_api_client_timeout, - nyxd_addr, - ) - .await - { + if let Err(e) = monitor.run().await { tracing::error!( "Monitor run failed: {e}, retrying in {}s...", FAILURE_RETRY_DELAY.as_secs() @@ -70,345 +86,370 @@ pub(crate) async fn spawn_in_background( } } -async fn run( - pool: &DbPool, - network_details: &NymNetworkDetails, - explorer_client_timeout: Duration, - nym_api_client_timeout: Duration, - nyxd_addr: &Url, -) -> anyhow::Result<()> { - let default_api_url = network_details - .endpoints - .first() - .expect("rust sdk mainnet default incorrectly configured") - .api_url() - .clone() - .expect("rust sdk mainnet default missing api_url"); - let default_explorer_url = network_details.explorer_api.clone().map(|url| { - url.parse() - .expect("rust sdk mainnet default explorer url not parseable") - }); +impl Monitor { + async fn run(&mut self) -> anyhow::Result<()> { + self.check_ipinfo_bandwidth().await; - // TODO dz replace explorer api with ipinfo.io - let default_explorer_url = - default_explorer_url.expect("explorer url missing in network config"); - let explorer_client = - ExplorerClient::new_with_timeout(default_explorer_url, explorer_client_timeout)?; - let explorer_gateways = explorer_client - .unstable_get_gateways() - .await - .log_error("unstable_get_gateways")?; + let default_api_url = self + .network_details + .endpoints + .first() + .expect("rust sdk mainnet default incorrectly configured") + .api_url() + .clone() + .expect("rust sdk mainnet default missing api_url"); - let api_client = NymApiClient::new_with_timeout(default_api_url, nym_api_client_timeout); + let api_client = + NymApiClient::new_with_timeout(default_api_url, self.nym_api_client_timeout); - let all_nodes = api_client - .get_all_described_nodes() - .await - .log_error("get_all_described_nodes")?; - tracing::debug!("Fetched {} total nodes", all_nodes.len()); + let all_nodes = api_client + .get_all_described_nodes() + .await + .log_error("get_all_described_nodes")?; + tracing::debug!("Fetched {} total nodes", all_nodes.len()); - let gateways = all_nodes - .iter() - .filter(|node| node.description.declared_role.entry) - .collect::>(); - tracing::debug!("Of those, {} gateways", gateways.len()); - for gw in gateways.iter() { - tracing::debug!("{}", gw.ed25519_identity_key().to_base58_string()); - } + let gateways = all_nodes + .iter() + .filter(|node| node.description.declared_role.entry) + .collect::>(); + tracing::debug!( + "{}/{} with declared entry gateway capability", + gateways.len(), + all_nodes.len() + ); - let mixnodes = all_nodes - .iter() - .filter(|node| node.description.declared_role.mixnode) - .collect::>(); - tracing::debug!("Of those, {} mixnodes", mixnodes.len()); + let mixnodes = all_nodes + .iter() + .filter(|node| node.description.declared_role.mixnode) + .collect::>(); + tracing::debug!( + "{}/{} with declared mixnode capability", + mixnodes.len(), + all_nodes.len() + ); - log_gw_in_explorer_not_api(explorer_gateways.as_slice(), gateways.as_slice()); + let bonded_node_info = api_client + .get_all_bonded_nym_nodes() + .await? + .into_iter() + .map(|node| (node.bond_information.node_id, node.bond_information)) + // for faster reads + .collect::>(); - let all_skimmed_nodes = api_client - .get_all_basic_nodes(None) - .await - .log_error("get_all_basic_nodes")?; - - let mixnodes = api_client - .get_cached_mixnodes() - .await - .log_error("get_cached_mixnodes")?; - tracing::debug!("Fetched {} mixnodes", mixnodes.len()); - - // let gateways_blacklisted = gateways.iter().filter(|gw|gw.) - let gateways_blacklisted = all_skimmed_nodes - .iter() - .filter_map(|node| { - if node.performance.round_to_integer() <= 50 && node.supported_roles.entry { - Some(node.ed25519_identity_pubkey.to_base58_string()) - } else { - None + let mut gateway_geodata = Vec::new(); + for gateway in gateways.iter() { + if let Some(node_info) = bonded_node_info.get(&gateway.node_id) { + let gw_geodata = NodeGeoData { + identity_key: node_info.node.identity_key.to_owned(), + owner: node_info.owner.to_owned(), + pledge_amount: node_info.original_pledge.to_owned(), + location: self.location_cached(gateway).await, + }; + gateway_geodata.push(gw_geodata); } - }) - .collect::>(); + } - // Cached mixnodes don't include blacklisted nodes - // We need that to calculate the total locked tokens later - let mixnodes = api_client - .nym_api - .get_mixnodes_detailed_unfiltered() - .await - .log_error("get_mixnodes_detailed_unfiltered")?; - let mixnodes_described = api_client - .nym_api - .get_mixnodes_described() - .await - .log_error("get_mixnodes_described")?; - let mixnodes_active = api_client - .nym_api - .get_active_mixnodes() - .await - .log_error("get_active_mixnodes")?; - let delegation_program_members = - get_delegation_program_details(network_details, nyxd_addr).await?; + // contains performance data + let all_skimmed_nodes = api_client + .get_all_basic_nodes(None) + .await + .log_error("get_all_basic_nodes")?; - // keep stats for later - let count_bonded_mixnodes = mixnodes.len(); - let count_bonded_gateways = gateways.len(); - let count_explorer_gateways = explorer_gateways.len(); - let count_bonded_mixnodes_active = mixnodes_active.len(); + let gateways_blacklisted = all_skimmed_nodes + .iter() + .filter_map(|node| { + if node.performance.round_to_integer() <= 50 && node.supported_roles.entry { + Some(node.ed25519_identity_pubkey.to_base58_string()) + } else { + None + } + }) + .collect::>(); - let gateway_records = prepare_gateway_data( - &gateways, - &gateways_blacklisted, - explorer_gateways, - all_skimmed_nodes, - )?; - queries::insert_gateways(pool, gateway_records) - .await - .map(|_| { - tracing::debug!("Gateway info written to DB!"); - })?; + // Cached mixnodes don't include blacklisted nodes + // We need that to calculate the total locked tokens later + // TODO dz deprecated API, remove + let legacy_mixnodes = api_client + .nym_api + .get_mixnodes_detailed_unfiltered() + .await + .log_error("get_mixnodes_detailed_unfiltered")?; + let mixnodes_described = api_client + .nym_api + .get_mixnodes_described() + .await + .log_error("get_mixnodes_described")?; + let mixnodes_active = api_client + .nym_api + .get_basic_active_mixing_assigned_nodes(None, false, None, None) + .await + .log_error("get_active_mixnodes")? + .nodes + .data; + let delegation_program_members = + get_delegation_program_details(&self.network_details, &self.nyxd_addr).await?; - // instead of counting blacklisted GWs returned from API cache, count from the active set - let count_gateways_blacklisted = gateways - .iter() - .filter(|gw| { - let gw_identity = gw.ed25519_identity_key().to_base58_string(); - gateways_blacklisted.contains(&gw_identity) - }) - .count(); + // keep stats for later + let count_bonded_mixnodes = mixnodes.len(); + let count_bonded_gateways = gateways.len(); + let count_bonded_mixnodes_active = mixnodes_active.len(); - if count_gateways_blacklisted > 0 { - queries::write_blacklisted_gateways_to_db(pool, gateways_blacklisted.iter()) + let gateway_records = self.prepare_gateway_data( + &gateways, + &gateways_blacklisted, + gateway_geodata, + all_skimmed_nodes, + )?; + + let pool = self.db_pool.clone(); + queries::insert_gateways(&pool, gateway_records) .await .map(|_| { - tracing::debug!( - "Gateway blacklist info written to DB! {} blacklisted by Nym API", - count_gateways_blacklisted - ) + tracing::debug!("Gateway info written to DB!"); })?; - } - let mixnode_records = - prepare_mixnode_data(&mixnodes, mixnodes_described, delegation_program_members)?; - queries::insert_mixnodes(pool, mixnode_records) - .await - .map(|_| { - tracing::debug!("Mixnode info written to DB!"); - })?; - - let count_mixnodes_blacklisted = mixnodes.iter().filter(|elem| elem.blacklisted).count(); - - let recently_unbonded_gateways = queries::ensure_gateways_still_bonded(pool, &gateways).await?; - let recently_unbonded_mixnodes = queries::ensure_mixnodes_still_bonded(pool, &mixnodes).await?; - - let count_bonded_mixnodes_reserve = 0; // TODO: NymAPI doesn't report the reserve set size - let count_bonded_mixnodes_inactive = - count_bonded_mixnodes.saturating_sub(count_bonded_mixnodes_active); - - let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(pool).await?; - - // - // write summary keys and values to table - // - - let nodes_summary = vec![ - (MIXNODES_BONDED_COUNT, &count_bonded_mixnodes), - (MIXNODES_BONDED_ACTIVE, &count_bonded_mixnodes_active), - (MIXNODES_BONDED_INACTIVE, &count_bonded_mixnodes_inactive), - (MIXNODES_BONDED_RESERVE, &count_bonded_mixnodes_reserve), - (MIXNODES_BLACKLISTED_COUNT, &count_mixnodes_blacklisted), - (GATEWAYS_BONDED_COUNT, &count_bonded_gateways), - (GATEWAYS_EXPLORER_COUNT, &count_explorer_gateways), - (MIXNODES_HISTORICAL_COUNT, &all_historical_mixnodes), - (GATEWAYS_HISTORICAL_COUNT, &all_historical_gateways), - (GATEWAYS_BLACKLISTED_COUNT, &count_gateways_blacklisted), - ]; - - let last_updated = chrono::offset::Utc::now(); - let last_updated_utc = last_updated.timestamp().to_string(); - let network_summary = NetworkSummary { - mixnodes: mixnode::MixnodeSummary { - bonded: mixnode::MixnodeSummaryBonded { - count: count_bonded_mixnodes.cast_checked()?, - active: count_bonded_mixnodes_active.cast_checked()?, - inactive: count_bonded_mixnodes_inactive.cast_checked()?, - reserve: count_bonded_mixnodes_reserve.cast_checked()?, - last_updated_utc: last_updated_utc.to_owned(), - }, - blacklisted: mixnode::MixnodeSummaryBlacklisted { - count: count_mixnodes_blacklisted.cast_checked()?, - last_updated_utc: last_updated_utc.to_owned(), - }, - historical: mixnode::MixnodeSummaryHistorical { - count: all_historical_mixnodes.cast_checked()?, - last_updated_utc: last_updated_utc.to_owned(), - }, - }, - gateways: gateway::GatewaySummary { - bonded: gateway::GatewaySummaryBonded { - count: count_bonded_gateways.cast_checked()?, - last_updated_utc: last_updated_utc.to_owned(), - }, - blacklisted: gateway::GatewaySummaryBlacklisted { - count: count_gateways_blacklisted.cast_checked()?, - last_updated_utc: last_updated_utc.to_owned(), - }, - historical: gateway::GatewaySummaryHistorical { - count: all_historical_gateways.cast_checked()?, - last_updated_utc: last_updated_utc.to_owned(), - }, - explorer: gateway::GatewaySummaryExplorer { - count: count_explorer_gateways.cast_checked()?, - last_updated_utc: last_updated_utc.to_owned(), - }, - }, - }; - - queries::insert_summaries(pool, &nodes_summary, &network_summary, last_updated).await?; - - let mut log_lines: Vec = vec![]; - for (key, value) in nodes_summary.iter() { - log_lines.push(format!("{} = {}", key, value)); - } - log_lines.push(format!( - "recently_unbonded_mixnodes = {}", - recently_unbonded_mixnodes - )); - log_lines.push(format!( - "recently_unbonded_gateways = {}", - recently_unbonded_gateways - )); - - tracing::info!("Directory summary: \n{}", log_lines.join("\n")); - - Ok(()) -} - -fn prepare_gateway_data( - gateways: &[&NymNodeDescription], - gateways_blacklisted: &HashSet, - explorer_gateways: Vec, - skimmed_gateways: Vec, -) -> anyhow::Result> { - let mut gateway_records = Vec::new(); - - for gateway in gateways { - let identity_key = gateway.ed25519_identity_key().to_base58_string(); - let bonded = true; - let last_updated_utc = chrono::offset::Utc::now().timestamp(); - let blacklisted = gateways_blacklisted.contains(&identity_key); - - let self_described = serde_json::to_string(&gateway.description)?; - - let explorer_pretty_bond = explorer_gateways + let count_gateways_blacklisted = gateways .iter() - .find(|g| g.gateway.identity_key.eq(&identity_key)); - let explorer_pretty_bond = explorer_pretty_bond.and_then(|g| serde_json::to_string(g).ok()); - - let performance = skimmed_gateways - .iter() - .find(|g| { - g.ed25519_identity_pubkey - .to_base58_string() - .eq(&identity_key) + .filter(|gw| { + let gw_identity = gw.ed25519_identity_key().to_base58_string(); + gateways_blacklisted.contains(&gw_identity) }) - .map(|g| g.performance) - .unwrap_or_default() - .round_to_integer(); + .count(); - gateway_records.push(GatewayRecord { - identity_key: identity_key.to_owned(), - bonded, - blacklisted, - self_described, - explorer_pretty_bond, - last_updated_utc, - performance, - }); + if count_gateways_blacklisted > 0 { + queries::write_blacklisted_gateways_to_db(&pool, gateways_blacklisted.iter()) + .await + .map(|_| { + tracing::debug!( + "Gateway blacklist info written to DB! {} blacklisted by Nym API", + count_gateways_blacklisted + ) + })?; + } + + let mixnode_records = self.prepare_mixnode_data( + &legacy_mixnodes, + mixnodes_described, + delegation_program_members, + )?; + queries::insert_mixnodes(&pool, mixnode_records) + .await + .map(|_| { + tracing::debug!("Mixnode info written to DB!"); + })?; + + let count_mixnodes_blacklisted = legacy_mixnodes + .iter() + .filter(|elem| elem.blacklisted) + .count(); + + let recently_unbonded_gateways = + queries::ensure_gateways_still_bonded(&pool, &gateways).await?; + let recently_unbonded_mixnodes = + queries::ensure_mixnodes_still_bonded(&pool, &legacy_mixnodes).await?; + + let count_bonded_mixnodes_reserve = 0; // TODO: NymAPI doesn't report the reserve set size + let count_bonded_mixnodes_inactive = count_bonded_mixnodes - count_bonded_mixnodes_active; + + let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(&pool).await?; + + // + // write summary keys and values to table + // + + let nodes_summary = vec![ + (MIXNODES_BONDED_COUNT, &count_bonded_mixnodes), + (MIXNODES_BONDED_ACTIVE, &count_bonded_mixnodes_active), + (MIXNODES_BONDED_INACTIVE, &count_bonded_mixnodes_inactive), + (MIXNODES_BONDED_RESERVE, &count_bonded_mixnodes_reserve), + (MIXNODES_BLACKLISTED_COUNT, &count_mixnodes_blacklisted), + (GATEWAYS_BONDED_COUNT, &count_bonded_gateways), + (MIXNODES_HISTORICAL_COUNT, &all_historical_mixnodes), + (GATEWAYS_HISTORICAL_COUNT, &all_historical_gateways), + (GATEWAYS_BLACKLISTED_COUNT, &count_gateways_blacklisted), + ]; + + let last_updated = chrono::offset::Utc::now(); + let last_updated_utc = last_updated.timestamp().to_string(); + let network_summary = NetworkSummary { + mixnodes: mixnode::MixnodeSummary { + bonded: mixnode::MixnodeSummaryBonded { + count: count_bonded_mixnodes.cast_checked()?, + active: count_bonded_mixnodes_active.cast_checked()?, + inactive: count_bonded_mixnodes_inactive.cast_checked()?, + reserve: count_bonded_mixnodes_reserve.cast_checked()?, + last_updated_utc: last_updated_utc.to_owned(), + }, + blacklisted: mixnode::MixnodeSummaryBlacklisted { + count: count_mixnodes_blacklisted.cast_checked()?, + last_updated_utc: last_updated_utc.to_owned(), + }, + historical: mixnode::MixnodeSummaryHistorical { + count: all_historical_mixnodes.cast_checked()?, + last_updated_utc: last_updated_utc.to_owned(), + }, + }, + gateways: gateway::GatewaySummary { + bonded: gateway::GatewaySummaryBonded { + count: count_bonded_gateways.cast_checked()?, + last_updated_utc: last_updated_utc.to_owned(), + }, + blacklisted: gateway::GatewaySummaryBlacklisted { + count: count_gateways_blacklisted.cast_checked()?, + last_updated_utc: last_updated_utc.to_owned(), + }, + historical: gateway::GatewaySummaryHistorical { + count: all_historical_gateways.cast_checked()?, + last_updated_utc: last_updated_utc.to_owned(), + }, + }, + }; + + queries::insert_summaries(&pool, &nodes_summary, &network_summary, last_updated).await?; + + let mut log_lines: Vec = vec![]; + for (key, value) in nodes_summary.iter() { + log_lines.push(format!("{} = {}", key, value)); + } + log_lines.push(format!( + "recently_unbonded_mixnodes = {}", + recently_unbonded_mixnodes + )); + log_lines.push(format!( + "recently_unbonded_gateways = {}", + recently_unbonded_gateways + )); + + tracing::info!("Directory summary: \n{}", log_lines.join("\n")); + + Ok(()) } - Ok(gateway_records) -} + #[instrument(level = "debug", skip_all)] + async fn location_cached(&mut self, node: &NymNodeDescription) -> Location { + let node_id = node.node_id; -fn prepare_mixnode_data( - mixnodes: &[MixNodeBondAnnotated], - mixnodes_described: Vec, - delegation_program_members: Vec, -) -> anyhow::Result> { - let mut mixnode_records = Vec::new(); - - for mixnode in mixnodes { - let mix_id = mixnode.mix_id(); - let identity_key = mixnode.identity_key(); - let bonded = true; - let total_stake = decimal_to_i64(mixnode.mixnode_details.total_stake()); - let blacklisted = mixnode.blacklisted; - let node_info = mixnode.mix_node(); - let host = node_info.host.clone(); - let http_port = node_info.http_api_port; - // Contains all the information including what's above - let full_details = serde_json::to_string(&mixnode)?; - - let mixnode_described = mixnodes_described.iter().find(|m| m.bond.mix_id == mix_id); - let self_described = mixnode_described.and_then(|v| serde_json::to_string(v).ok()); - let is_dp_delegatee = delegation_program_members.contains(&mix_id); - - let last_updated_utc = chrono::offset::Utc::now().timestamp(); - - mixnode_records.push(MixnodeRecord { - mix_id, - identity_key: identity_key.to_owned(), - bonded, - total_stake, - host, - http_port, - blacklisted, - full_details, - self_described, - last_updated_utc, - is_dp_delegatee, - }); + match self.geocache.get(&node_id).await { + Some(location) => return location, + None => { + for ip in node.description.host_information.ip_address.iter() { + if let Ok(location) = self.ipinfo.locate_ip(ip.to_string()).await { + self.geocache.insert(node_id, location.clone()).await; + return location; + } + } + // if no data could be retrieved + tracing::debug!("No geodata could be retrieved for {}", node_id); + Location::empty() + } + } } - Ok(mixnode_records) -} + fn prepare_gateway_data( + &self, + gateways: &[&NymNodeDescription], + gateways_blacklisted: &HashSet, + gateway_geodata: Vec, + skimmed_gateways: Vec, + ) -> anyhow::Result> { + let mut gateway_records = Vec::new(); -fn log_gw_in_explorer_not_api( - explorer: &[PrettyDetailedGatewayBond], - api_gateways: &[&NymNodeDescription], -) { - let api_gateways = api_gateways - .iter() - .map(|gw| gw.ed25519_identity_key().to_base58_string()) - .collect::>(); - let explorer_only = explorer - .iter() - .filter(|gw| !api_gateways.contains(&gw.gateway.identity_key.to_string())) - .collect::>(); + for gateway in gateways { + let identity_key = gateway.ed25519_identity_key().to_base58_string(); + let bonded = true; + let last_updated_utc = chrono::offset::Utc::now().timestamp(); + let blacklisted = gateways_blacklisted.contains(&identity_key); - tracing::debug!( - "Gateways listed by explorer but not by Nym API: {}", - explorer_only.len() - ); - for gw in explorer_only.iter() { - tracing::debug!("{}", gw.gateway.identity_key.to_string()); + let self_described = serde_json::to_string(&gateway.description)?; + + let explorer_pretty_bond = gateway_geodata + .iter() + .find(|g| g.identity_key.eq(&identity_key)); + let explorer_pretty_bond = + explorer_pretty_bond.and_then(|g| serde_json::to_string(g).ok()); + + let performance = skimmed_gateways + .iter() + .find(|g| { + g.ed25519_identity_pubkey + .to_base58_string() + .eq(&identity_key) + }) + .map(|g| g.performance) + .unwrap_or_default() + .round_to_integer(); + + gateway_records.push(GatewayRecord { + identity_key: identity_key.to_owned(), + bonded, + blacklisted, + self_described, + explorer_pretty_bond, + last_updated_utc, + performance, + }); + } + + Ok(gateway_records) + } + + fn prepare_mixnode_data( + &self, + mixnodes: &[MixNodeBondAnnotated], + mixnodes_described: Vec, + delegation_program_members: Vec, + ) -> anyhow::Result> { + let mut mixnode_records = Vec::new(); + + for mixnode in mixnodes { + let mix_id = mixnode.mix_id(); + let identity_key = mixnode.identity_key(); + let bonded = true; + let total_stake = decimal_to_i64(mixnode.mixnode_details.total_stake()); + let blacklisted = mixnode.blacklisted; + let node_info = mixnode.mix_node(); + let host = node_info.host.clone(); + let http_port = node_info.http_api_port; + // Contains all the information including what's above + let full_details = serde_json::to_string(&mixnode)?; + + let mixnode_described = mixnodes_described.iter().find(|m| m.bond.mix_id == mix_id); + let self_described = mixnode_described.and_then(|v| serde_json::to_string(v).ok()); + let is_dp_delegatee = delegation_program_members.contains(&mix_id); + + let last_updated_utc = chrono::offset::Utc::now().timestamp(); + + mixnode_records.push(MixnodeRecord { + mix_id, + identity_key: identity_key.to_owned(), + bonded, + total_stake, + host, + http_port, + blacklisted, + full_details, + self_described, + last_updated_utc, + is_dp_delegatee, + }); + } + + Ok(mixnode_records) + } + + async fn check_ipinfo_bandwidth(&self) { + match self.ipinfo.check_remaining_bandwidth().await { + Ok(bandwidth) => { + tracing::info!( + "ipinfo monthly bandwidth: {}/{} spent", + bandwidth.month, + bandwidth.limit + ); + } + Err(err) => { + tracing::debug!("Couldn't check ipinfo bandwidth: {}", err); + } + } } } diff --git a/sdk/ffi/go/src/lib.rs b/sdk/ffi/go/src/lib.rs index a5c930a2ec..a0a690c691 100644 --- a/sdk/ffi/go/src/lib.rs +++ b/sdk/ffi/go/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +// due to autogenerated code +#![allow(clippy::empty_line_after_doc_comments)] + use nym_sdk::mixnet::Recipient; use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag; uniffi::include_scaffolding!("bindings"); diff --git a/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs b/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs index c04fae8ef2..df70e604f4 100644 --- a/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs +++ b/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs @@ -33,7 +33,7 @@ pub(crate) struct VkShareIndex<'a> { pub(crate) epoch_id: MultiIndex<'a, EpochId, ContractVKShare, VKShareKey<'a>>, } -impl<'a> IndexList for VkShareIndex<'a> { +impl IndexList for VkShareIndex<'_> { fn get_indexes(&'_ self) -> Box> + '_> { let v: Vec<&dyn Index> = vec![&self.epoch_id]; Box::new(v.into_iter()) @@ -87,7 +87,7 @@ pub fn query(_: Deps<'_>, _: Env, _: EmptyMessage) -> Result, env: Env, msg: MigrateMsg) -> Result { // on migration immediately attempt to rewrite the storage - let threshold = (2 * msg.dealers.len() as u64 + 3 - 1) / 3; + let threshold = (2 * msg.dealers.len() as u64).div_ceil(3); let epoch = CURRENT_EPOCH.load(deps.storage)?; assert_eq!(0, epoch.epoch_id); diff --git a/tools/internal/testnet-manager/src/manager/dkg_skip.rs b/tools/internal/testnet-manager/src/manager/dkg_skip.rs index a132ea6950..7720202665 100644 --- a/tools/internal/testnet-manager/src/manager/dkg_skip.rs +++ b/tools/internal/testnet-manager/src/manager/dkg_skip.rs @@ -58,7 +58,7 @@ impl<'a> FakeDkgKey<'a> { } } -impl<'a> PemStorableKey for FakeDkgKey<'a> { +impl PemStorableKey for FakeDkgKey<'_> { type Error = NetworkManagerError; fn pem_type() -> &'static str { @@ -84,7 +84,7 @@ struct DkgSkipCtx<'a> { ecash_signers: Vec, } -impl<'a> ProgressCtx for DkgSkipCtx<'a> { +impl ProgressCtx for DkgSkipCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } @@ -138,7 +138,7 @@ impl NetworkManager { // generate required materials let n = api_endpoints.len(); - let threshold = (2 * n + 3 - 1) / 3; + let threshold = (2 * n).div_ceil(3); let ecash_keys = ttp_keygen(threshold as u64, n as u64)?; diff --git a/tools/internal/testnet-manager/src/manager/local_apis.rs b/tools/internal/testnet-manager/src/manager/local_apis.rs index 631f6117f2..124b2a324f 100644 --- a/tools/internal/testnet-manager/src/manager/local_apis.rs +++ b/tools/internal/testnet-manager/src/manager/local_apis.rs @@ -24,7 +24,7 @@ struct LocalApisCtx<'a> { signers: Vec, } -impl<'a> ProgressCtx for LocalApisCtx<'a> { +impl ProgressCtx for LocalApisCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } diff --git a/tools/internal/testnet-manager/src/manager/local_client.rs b/tools/internal/testnet-manager/src/manager/local_client.rs index c037f53719..fac5e6828a 100644 --- a/tools/internal/testnet-manager/src/manager/local_client.rs +++ b/tools/internal/testnet-manager/src/manager/local_client.rs @@ -30,7 +30,7 @@ struct LocalClientCtx<'a> { network: &'a LoadedNetwork, } -impl<'a> ProgressCtx for LocalClientCtx<'a> { +impl ProgressCtx for LocalClientCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } diff --git a/tools/internal/testnet-manager/src/manager/local_nodes.rs b/tools/internal/testnet-manager/src/manager/local_nodes.rs index dc19d44436..75edc76b7d 100644 --- a/tools/internal/testnet-manager/src/manager/local_nodes.rs +++ b/tools/internal/testnet-manager/src/manager/local_nodes.rs @@ -30,7 +30,7 @@ struct LocalNodesCtx<'a> { gateways: Vec, } -impl<'a> ProgressCtx for LocalNodesCtx<'a> { +impl ProgressCtx for LocalNodesCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } From 841fb81d24ca36918af152226a74543bfb19cbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Walther?= Date: Fri, 29 Nov 2024 16:54:17 +0100 Subject: [PATCH 22/80] Update Security disclosure email, public key and policy --- SECURITY.md | 124 ++++++++++++++++++++++++++++------------------------ 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index c3b9bb9748..23091d1cbc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,37 +3,23 @@ Critical bug or security issue 💥 If you're here because you're trying to figure out how to notify us of a security issue, send us a PGP encrypted email to: ``` -security@nymte.ch +security@nym.com ``` Encrypted with our public key which is available below in plain text and also on keyservers: ``` -pub rsa4096 2023-10-30 [SC] [expire : 2026-10-29] +sec rsa4096/7C3C727F05090550 2023-10-30 [SC] [expire : 2026-10-29] 24B2592E801A5AAA8666C8BA7C3C727F05090550 -uid [ ultime ] Security Nym Technologies -sub rsa4096 2023-10-30 [E] [expire : 2026-10-29] +uid [ ultime ] Security Nym Technologies +ssb rsa4096/ACD0FBD79DC70ACC 2023-10-30 [E] [expire : 2026-10-29] ``` The fingerprint of the key is on the second line above. -If you need to chat __urgently__ to our team for a __critical__ security issue: - -go to Matrix, and alert the core engineers with a private direct message: - - Jedrzej Stuczynski @jstuczyn:nymtech.chat - Mark Sinclair @mark:nymtech.chat - Raphaël Walther @raphael:nymtech.chat - Please avoid opening public issues on GitHub that contain information about a potential security vulnerability as this makes it difficult to reduce the impact and harm of valid security issues. -If you don't know what Matrix is, you can follow this documentation to create an account on this federation of instant messaging servers: - -[Matrix for Instant Messaging](https://matrix.org/docs/chat_basics/matrix-for-im/) - - - ``` -----BEGIN PGP PUBLIC KEY BLOCK----- @@ -48,43 +34,69 @@ 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 -iC2pMbWdE0ZTQaFq6tPIg058pjqi -=nqgX +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 +=QPTf -----END PGP PUBLIC KEY BLOCK----- + ``` From 4851614375ea8b8eb55cb60885ecfcc33172affb Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Mon, 2 Dec 2024 12:15:30 +0100 Subject: [PATCH 23/80] NS API - Gateway stats scraping (#5180) * squashed commit before rebasing * removed blank lines --- Cargo.lock | 3 + common/gateway-stats-storage/Cargo.toml | 1 + common/gateway-stats-storage/src/lib.rs | 3 +- common/gateway-stats-storage/src/models.rs | 37 +-- common/statistics/src/gateways.rs | 36 +++ .../nym-node-status-api/Cargo.toml | 4 +- .../migrations/002_session_stats.sql | 17 ++ .../nym-node-status-api/src/db/models.rs | 43 +++ .../src/db/queries/gateways_stats.rs | 75 +++++ .../nym-node-status-api/src/db/queries/mod.rs | 3 + .../src/http/api/metrics/mod.rs | 10 + .../src/http/api/metrics/sessions.rs | 83 ++++++ .../nym-node-status-api/src/http/api/mod.rs | 4 +- .../nym-node-status-api/src/http/api_docs.rs | 2 +- .../nym-node-status-api/src/http/mod.rs | 3 +- .../nym-node-status-api/src/http/models.rs | 13 + .../nym-node-status-api/src/http/state.rs | 43 +++ .../nym-node-status-api/src/main.rs | 7 + .../src/node_scraper/error.rs | 18 ++ .../src/node_scraper/mod.rs | 279 ++++++++++++++++++ nym-node/nym-node-requests/src/api/client.rs | 6 + .../src/api/v1/metrics/models.rs | 1 + 22 files changed, 650 insertions(+), 41 deletions(-) create mode 100644 nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql create mode 100644 nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 936dadfbd6..cd9744933a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5536,6 +5536,7 @@ version = "0.1.0" dependencies = [ "nym-credentials-interface", "nym-sphinx", + "nym-statistics-common", "sqlx", "thiserror", "time", @@ -6077,6 +6078,7 @@ dependencies = [ "nym-network-defaults", "nym-node-requests", "nym-node-status-client", + "nym-statistics-common", "nym-task", "nym-validator-client", "regex", @@ -6088,6 +6090,7 @@ dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", "thiserror", + "time", "tokio", "tokio-util", "tower-http", diff --git a/common/gateway-stats-storage/Cargo.toml b/common/gateway-stats-storage/Cargo.toml index d439b34a16..1e816e0233 100644 --- a/common/gateway-stats-storage/Cargo.toml +++ b/common/gateway-stats-storage/Cargo.toml @@ -22,6 +22,7 @@ tracing = { workspace = true } nym-sphinx = { path = "../nymsphinx" } nym-credentials-interface = { path = "../credentials-interface" } +nym-statistics-common = { path = "../statistics" } [build-dependencies] diff --git a/common/gateway-stats-storage/src/lib.rs b/common/gateway-stats-storage/src/lib.rs index 453258b606..3f94b40a87 100644 --- a/common/gateway-stats-storage/src/lib.rs +++ b/common/gateway-stats-storage/src/lib.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use error::StatsStorageError; -use models::{ActiveSession, FinishedSession, SessionType, StoredFinishedSession}; +use models::{ActiveSession, FinishedSession, StoredFinishedSession}; use nym_sphinx::DestinationAddressBytes; +use nym_statistics_common::gateways::SessionType; use sessions::SessionManager; use sqlx::ConnectOptions; use std::path::Path; diff --git a/common/gateway-stats-storage/src/models.rs b/common/gateway-stats-storage/src/models.rs index 6f53cf429a..f35883d28e 100644 --- a/common/gateway-stats-storage/src/models.rs +++ b/common/gateway-stats-storage/src/models.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_credentials_interface::TicketType; +use nym_statistics_common::gateways::SessionType; use sqlx::prelude::FromRow; use time::{Duration, OffsetDateTime}; @@ -25,42 +26,6 @@ pub struct FinishedSession { pub typ: SessionType, } -#[derive(PartialEq)] -pub enum SessionType { - Vpn, - Mixnet, - Unknown, -} - -impl SessionType { - pub fn to_string(&self) -> &str { - match self { - Self::Vpn => "vpn", - Self::Mixnet => "mixnet", - Self::Unknown => "unknown", - } - } - - pub fn from_string(s: &str) -> Self { - match s { - "vpn" => Self::Vpn, - "mixnet" => Self::Mixnet, - _ => Self::Unknown, - } - } -} - -impl From for SessionType { - fn from(value: TicketType) -> Self { - match value { - TicketType::V1MixnetEntry => Self::Mixnet, - TicketType::V1MixnetExit => Self::Mixnet, - TicketType::V1WireguardEntry => Self::Vpn, - TicketType::V1WireguardExit => Self::Vpn, - } - } -} - #[derive(FromRow)] pub(crate) struct StoredActiveSession { start_time: OffsetDateTime, diff --git a/common/statistics/src/gateways.rs b/common/statistics/src/gateways.rs index 398fda559a..97d75334f1 100644 --- a/common/statistics/src/gateways.rs +++ b/common/statistics/src/gateways.rs @@ -87,3 +87,39 @@ pub enum SessionEvent { client: DestinationAddressBytes, }, } + +#[derive(PartialEq)] +pub enum SessionType { + Vpn, + Mixnet, + Unknown, +} + +impl SessionType { + pub fn to_string(&self) -> &str { + match self { + Self::Vpn => "vpn", + Self::Mixnet => "mixnet", + Self::Unknown => "unknown", + } + } + + pub fn from_string(s: &str) -> Self { + match s { + "vpn" => Self::Vpn, + "mixnet" => Self::Mixnet, + _ => Self::Unknown, + } + } +} + +impl From for SessionType { + fn from(value: TicketType) -> Self { + match value { + TicketType::V1MixnetEntry => Self::Mixnet, + TicketType::V1MixnetExit => Self::Mixnet, + TicketType::V1WireguardEntry => Self::Vpn, + TicketType::V1WireguardExit => Self::Vpn, + } + } +} diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 05d1586ae9..a915d31555 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -26,6 +26,7 @@ nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } nym-explorer-client = { path = "../../explorer-api/explorer-client" } nym-network-defaults = { path = "../../common/network-defaults" } +nym-statistics-common = { path = "../../common/statistics"} nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-task = { path = "../../common/task" } nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } @@ -36,8 +37,9 @@ serde_json = { workspace = true } serde_json_path = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } -sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "time"] } thiserror = { workspace = true } +time = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } tokio-util = { workspace = true } tracing = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql b/nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql new file mode 100644 index 0000000000..d29fe986a3 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql @@ -0,0 +1,17 @@ + +CREATE TABLE gateway_session_stats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + gateway_identity_key VARCHAR NOT NULL, + node_id INTEGER NOT NULL, + day DATE NOT NULL, + unique_active_clients INTEGER NOT NULL, + session_started INTEGER NOT NULL, + users_hashes VARCHAR, + vpn_sessions VARCHAR, + mixnet_sessions VARCHAR, + unknown_sessions VARCHAR, + UNIQUE (node_id, day) -- This constraint automatically creates an index + ); +CREATE INDEX idx_gateway_session_stats_identity_key ON gateway_session_stats (gateway_identity_key); +CREATE INDEX idx_gateway_session_stats_day ON gateway_session_stats (day); + diff --git a/nym-node-status-api/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs index 7d9e93ed24..9de09271bf 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/models.rs @@ -4,7 +4,9 @@ use crate::{ }; use nym_node_requests::api::v1::node::models::NodeDescription; use serde::{Deserialize, Serialize}; +use sqlx::FromRow; use strum_macros::{EnumString, FromRepr}; +use time::Date; use utoipa::ToSchema; pub(crate) struct GatewayRecord { @@ -333,3 +335,44 @@ pub struct GatewayInfoDto { pub self_described: Option, pub explorer_pretty_bond: Option, } + +#[derive(Debug, Clone, FromRow)] +pub struct GatewaySessionsRecord { + pub gateway_identity_key: String, + pub node_id: i64, + pub day: Date, + pub unique_active_clients: i64, + pub session_started: i64, + pub users_hashes: Option, + pub vpn_sessions: Option, + pub mixnet_sessions: Option, + pub unknown_sessions: Option, +} + +impl TryFrom for http::models::SessionStats { + type Error = anyhow::Error; + + fn try_from(value: GatewaySessionsRecord) -> Result { + let users_hashes = value.users_hashes.clone().unwrap_or("null".to_string()); + let vpn_sessions = value.vpn_sessions.clone().unwrap_or("null".to_string()); + let mixnet_sessions = value.mixnet_sessions.clone().unwrap_or("null".to_string()); + let unknown_sessions = value.unknown_sessions.clone().unwrap_or("null".to_string()); + + let users_hashes = serde_json::from_str(&users_hashes).unwrap_or(None); + let vpn_sessions = serde_json::from_str(&vpn_sessions).unwrap_or(None); + let mixnet_sessions = serde_json::from_str(&mixnet_sessions).unwrap_or(None); + let unknown_sessions = serde_json::from_str(&unknown_sessions).unwrap_or(None); + + Ok(http::models::SessionStats { + gateway_identity_key: value.gateway_identity_key.clone(), + node_id: value.node_id as u32, + day: value.day, + unique_active_clients: value.unique_active_clients, + session_started: value.session_started, + users_hashes, + vpn_sessions, + mixnet_sessions, + unknown_sessions, + }) + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs new file mode 100644 index 0000000000..8ba813ee1f --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs @@ -0,0 +1,75 @@ +use crate::{ + db::{models::GatewaySessionsRecord, DbPool}, + http::models::SessionStats, +}; +use futures_util::TryStreamExt; +use time::Date; +use tracing::error; + +pub(crate) async fn insert_session_records( + pool: &DbPool, + records: Vec, +) -> anyhow::Result<()> { + let mut tx = pool.begin().await?; + for record in records { + sqlx::query!( + "INSERT OR IGNORE INTO gateway_session_stats + (gateway_identity_key, node_id, day, + unique_active_clients, session_started, users_hashes, + vpn_sessions, mixnet_sessions, unknown_sessions) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + record.gateway_identity_key, + record.node_id, + record.day, + record.unique_active_clients, + record.session_started, + record.users_hashes, + record.vpn_sessions, + record.mixnet_sessions, + record.unknown_sessions, + ) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + + Ok(()) +} + +pub(crate) async fn get_sessions_stats(pool: &DbPool) -> anyhow::Result> { + let mut conn = pool.acquire().await?; + let items = sqlx::query_as( + "SELECT gateway_identity_key, + node_id, + day, + unique_active_clients, + session_started, + users_hashes, + vpn_sessions, + mixnet_sessions, + unknown_sessions + FROM gateway_session_stats", + ) + .fetch(&mut *conn) + .try_collect::>() + .await?; + + let items: Vec = items + .into_iter() + .map(|item| item.try_into()) + .collect::>>() + .map_err(|e| { + error!("Conversion from database failed: {e}. Invalidly stored data?"); + e + })?; + + Ok(items) +} + +pub(crate) async fn delete_old_records(pool: &DbPool, cut_off: Date) -> anyhow::Result<()> { + let mut conn = pool.acquire().await?; + sqlx::query!("DELETE FROM gateway_session_stats WHERE day <= ?", cut_off) + .execute(&mut *conn) + .await?; + Ok(()) +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs index fe22ec27aa..8c87ab2fc0 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs @@ -1,4 +1,5 @@ mod gateways; +mod gateways_stats; mod misc; mod mixnodes; mod summary; @@ -13,3 +14,5 @@ pub(crate) use mixnodes::{ ensure_mixnodes_still_bonded, get_all_mixnodes, get_daily_stats, insert_mixnodes, }; pub(crate) use summary::{get_summary, get_summary_history}; + +pub(crate) use gateways_stats::{delete_old_records, get_sessions_stats, insert_session_records}; diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs new file mode 100644 index 0000000000..8703f92830 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs @@ -0,0 +1,10 @@ +use axum::Router; + +use crate::http::state::AppState; + +pub(crate) mod sessions; + +pub(crate) fn routes() -> Router { + Router::new().nest("/sessions", sessions::routes()) + //eventually add other metrics type +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs new file mode 100644 index 0000000000..e2e4cd9ced --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs @@ -0,0 +1,83 @@ +use axum::{ + extract::{Query, State}, + Json, Router, +}; +use time::Date; +use tracing::instrument; + +use crate::http::{ + error::{HttpError, HttpResult}, + models::SessionStats, + state::AppState, + PagedResult, Pagination, +}; + +pub(crate) fn routes() -> Router { + Router::new().route("/", axum::routing::get(get_all_sessions)) + // .route("/:node_id", axum::routing::get(get_node_sessions)) + // .route("/:day", axum::routing::get(get_daily_sessions)) +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::IntoParams)] +#[into_params(parameter_in = Query)] +pub(crate) struct SessionQueryParams { + size: Option, + page: Option, + node_id: Option, + day: Option, +} + +#[utoipa::path( + tag = "Sessions", + get, + params( + SessionQueryParams + ), + path = "/v2/metrics/sessions", + responses( + (status = 200, body = PagedSessionStats) + ) +)] +#[instrument(level = tracing::Level::DEBUG, skip(state))] +async fn get_all_sessions( + Query(SessionQueryParams { + size, + page, + node_id, + day, + }): Query, + State(state): State, +) -> HttpResult>> { + let db = state.db_pool(); + let res = state.cache().get_sessions_stats(db).await; + + let day_filtered = if let Some(day) = day { + if let Ok(parsed_day) = + Date::parse(&day, &time::format_description::well_known::Iso8601::DATE) + { + res.into_iter().filter(|s| s.day == parsed_day).collect() + } else { + return Err(HttpError::invalid_input(day)); + } + } else { + res + }; + + let day_and_node_filtered = if let Some(node_id) = node_id { + if let Ok(parsed_node_id) = node_id.parse::() { + day_filtered + .into_iter() + .filter(|s| s.node_id == parsed_node_id) + .collect() + } else { + return Err(HttpError::invalid_input(node_id)); + } + } else { + day_filtered + }; + + Ok(Json(PagedResult::paginate( + Pagination { size, page }, + day_and_node_filtered, + ))) +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs index ed24fa80f5..6483a44519 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs @@ -8,6 +8,7 @@ use utoipa_swagger_ui::SwaggerUi; use crate::http::{server::HttpServer, state::AppState}; pub(crate) mod gateways; +pub(crate) mod metrics; pub(crate) mod mixnodes; pub(crate) mod services; pub(crate) mod summary; @@ -34,7 +35,8 @@ impl RouterBuilder { .nest("/gateways", gateways::routes()) .nest("/mixnodes", mixnodes::routes()) .nest("/services", services::routes()) - .nest("/summary", summary::routes()), + .nest("/summary", summary::routes()) + .nest("/metrics", metrics::routes()), ) .nest( "/internal", diff --git a/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs b/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs index aa86a56ab0..fec4d25cd5 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs @@ -1,4 +1,4 @@ -use crate::http::{Gateway, GatewaySkinny, Mixnode, Service}; +use crate::http::{Gateway, GatewaySkinny, Mixnode, Service, SessionStats}; use utoipa::OpenApi; use utoipauto::utoipauto; diff --git a/nym-node-status-api/nym-node-status-api/src/http/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/mod.rs index 1cc317337f..b1a7bf742d 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/mod.rs @@ -1,4 +1,4 @@ -use models::{Gateway, GatewaySkinny, Mixnode, Service}; +use models::{Gateway, GatewaySkinny, Mixnode, Service, SessionStats}; pub(crate) mod api; pub(crate) mod api_docs; @@ -20,6 +20,7 @@ pub(crate) mod state; PagedGatewaySkinny = PagedResult, PagedMixnode = PagedResult, PagedService = PagedResult, + PagedSessionStats = PagedResult )] pub struct PagedResult { pub page: usize, diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs index aee2124b6c..19060d1ce8 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -74,3 +74,16 @@ pub(crate) struct SummaryHistory { pub value_json: serde_json::Value, pub timestamp_utc: String, } + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct SessionStats { + pub gateway_identity_key: String, + pub node_id: u32, + pub day: time::Date, + pub unique_active_clients: i64, + pub session_started: i64, + pub users_hashes: Option, + pub vpn_sessions: Option, + pub mixnet_sessions: Option, + pub unknown_sessions: Option, +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs index 393da5c0b8..0c79c0e16f 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/state.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/state.rs @@ -9,6 +9,8 @@ use crate::{ http::models::{DailyStats, Gateway, Mixnode, SummaryHistory}, }; +use super::models::SessionStats; + #[derive(Debug, Clone)] pub(crate) struct AppState { db_pool: DbPool, @@ -53,6 +55,7 @@ static GATEWAYS_LIST_KEY: &str = "gateways"; static MIXNODES_LIST_KEY: &str = "mixnodes"; static MIXSTATS_LIST_KEY: &str = "mixstats"; static SUMMARY_HISTORY_LIST_KEY: &str = "summary-history"; +static SESSION_STATS_LIST_KEY: &str = "session-stats"; #[derive(Debug, Clone)] pub(crate) struct HttpCache { @@ -60,6 +63,7 @@ pub(crate) struct HttpCache { mixnodes: Cache>>>, mixstats: Cache>>>, history: Cache>>>, + session_stats: Cache>>>, } impl HttpCache { @@ -81,6 +85,10 @@ impl HttpCache { .max_capacity(2) .time_to_live(Duration::from_secs(ttl_seconds)) .build(), + session_stats: Cache::builder() + .max_capacity(2) + .time_to_live(Duration::from_secs(ttl_seconds)) + .build(), } } @@ -238,4 +246,39 @@ impl HttpCache { }) .await } + + pub async fn get_sessions_stats(&self, db: &DbPool) -> Vec { + match self.session_stats.get(SESSION_STATS_LIST_KEY).await { + Some(guard) => { + let read_lock = guard.read().await; + read_lock.to_vec() + } + None => { + let session_stats = crate::db::queries::get_sessions_stats(db) + .await + .unwrap_or_default(); + self.upsert_sessions_stats(session_stats.clone()).await; + session_stats + } + } + } + + pub async fn upsert_sessions_stats( + &self, + session_stats: Vec, + ) -> Entry>>> { + self.session_stats + .entry_by_ref(SESSION_STATS_LIST_KEY) + .and_upsert_with(|maybe_entry| async { + if let Some(entry) = maybe_entry { + let v = entry.into_value(); + let mut guard = v.write().await; + *guard = session_stats; + v.clone() + } else { + Arc::new(RwLock::new(session_stats)) + } + }) + .await + } } diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index 6274a01fe5..4fd01d7960 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -7,6 +7,7 @@ mod db; mod http; mod logging; mod monitor; +mod node_scraper; mod testruns; #[tokio::main] @@ -44,6 +45,12 @@ async fn main() -> anyhow::Result<()> { testruns::spawn(storage.pool_owned(), args.testruns_refresh_interval).await; + let db_pool_scraper = storage.pool_owned(); + tokio::spawn(async move { + node_scraper::spawn_in_background(db_pool_scraper, args_clone.nym_api_client_timeout).await; + tracing::info!("Started metrics scraper task"); + }); + let shutdown_handles = http::server::start_http_api( storage.pool_owned(), args.http_port, diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs new file mode 100644 index 0000000000..42f96647e3 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs @@ -0,0 +1,18 @@ +use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; +use nym_node_requests::api::client::NymNodeApiClientError; +use nym_validator_client::client::NodeId; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NodeScraperError { + #[error("node {node_id} has provided malformed host information ({host}: {source}")] + MalformedHost { + host: String, + node_id: NodeId, + #[source] + source: NymNodeApiClientError, + }, + + #[error("node {node_id} with host '{host}' doesn't seem to expose its declared http port nor any of the standard API ports, i.e.: 80, 443 or {}", DEFAULT_NYM_NODE_HTTP_PORT)] + NoHttpPortsAvailable { host: String, node_id: NodeId }, +} diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs new file mode 100644 index 0000000000..fb8cca4c29 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs @@ -0,0 +1,279 @@ +use crate::db::{models::GatewaySessionsRecord, queries, DbPool}; +use error::NodeScraperError; + +use nym_network_defaults::{NymNetworkDetails, DEFAULT_NYM_NODE_HTTP_PORT}; +use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::SessionStats}; +use nym_statistics_common::gateways::SessionType; +use nym_validator_client::{ + client::{NodeId, NymNodeDetails}, + models::{DescribedNodeType, NymNodeDescription}, + NymApiClient, +}; +use time::OffsetDateTime; + +use std::collections::HashMap; +use tokio::time::Duration; +use tracing::instrument; + +mod error; + +const FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60); +const REFRESH_INTERVAL: Duration = Duration::from_secs(60 * 60 * 6); //6h, data only update once a day +const STALE_DURATION: Duration = Duration::from_secs(86400 * 365); //one year + +#[instrument(level = "debug", name = "node_scraper", skip_all)] +pub(crate) async fn spawn_in_background(db_pool: DbPool, nym_api_client_timeout: Duration) { + let network_defaults = nym_network_defaults::NymNetworkDetails::new_from_env(); + + loop { + //No graceful shutdown? + tracing::info!("Refreshing node self-described metrics..."); + + if let Err(e) = run(&db_pool, &network_defaults, nym_api_client_timeout).await { + tracing::error!( + "Metrics collection failed: {e}, retrying in {}s...", + FAILURE_RETRY_DELAY.as_secs() + ); + + tokio::time::sleep(FAILURE_RETRY_DELAY).await; + } else { + tracing::info!( + "Metrics successfully collected, sleeping for {}s...", + REFRESH_INTERVAL.as_secs() + ); + tokio::time::sleep(REFRESH_INTERVAL).await; + } + } +} + +async fn run( + pool: &DbPool, + network_details: &NymNetworkDetails, + nym_api_client_timeout: Duration, +) -> anyhow::Result<()> { + let default_api_url = network_details + .endpoints + .first() + .expect("rust sdk mainnet default incorrectly configured") + .api_url() + .clone() + .expect("rust sdk mainnet default missing api_url"); + + let api_client = NymApiClient::new_with_timeout(default_api_url, nym_api_client_timeout); + + //SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes + let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?; + let all_nodes = api_client.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet + tracing::debug!("Fetched {} total nodes", all_nodes.len()); + + let mut nodes_to_scrape: HashMap = bonded_nodes + .into_iter() + .map(|n| (n.node_id(), n.into())) + .collect(); + + all_nodes + .into_iter() + .filter(|n| n.contract_node_type != DescribedNodeType::LegacyMixnode) + .for_each(|n| { + nodes_to_scrape.entry(n.node_id).or_insert_with(|| n.into()); + }); + tracing::debug!("Will try to scrape {} nodes", nodes_to_scrape.len()); + + let mut session_records = Vec::new(); + for n in nodes_to_scrape.into_values() { + if let Some(stat) = n.try_scrape_metrics().await { + session_records.push(prepare_session_data(stat, &n)); + } + } + + queries::insert_session_records(pool, session_records) + .await + .map(|_| { + tracing::debug!("Session info written to DB!"); + })?; + let cut_off_date = (OffsetDateTime::now_utc() - STALE_DURATION).date(); + queries::delete_old_records(pool, cut_off_date) + .await + .map(|_| { + tracing::debug!("Cleared old data before {}", cut_off_date); + })?; + + Ok(()) +} + +#[derive(Debug)] +struct MetricsScrapingData { + host: String, + node_id: NodeId, + id_key: String, + port: Option, +} + +impl MetricsScrapingData { + pub fn new( + host: impl Into, + node_id: NodeId, + id_key: String, + port: Option, + ) -> Self { + MetricsScrapingData { + host: host.into(), + node_id, + id_key, + port, + } + } + + async fn try_scrape_metrics(&self) -> Option { + match self.try_get_client().await { + Ok(client) => { + match client.get_sessions_metrics().await { + Ok(session_stats) => { + if session_stats.update_time != OffsetDateTime::UNIX_EPOCH { + Some(session_stats) + } else { + //means no data + None + } + } + Err(e) => { + tracing::error!("[metrics scraper]: {e}"); + None + } + } + } + Err(e) => { + tracing::error!("[metrics scraper]: {e}"); + None + } + } + } + + async fn try_get_client(&self) -> Result { + // first try the standard port in case the operator didn't put the node behind the proxy, + // then default https (443) + // finally default http (80) + let mut addresses_to_try = vec![ + format!("http://{0}:{DEFAULT_NYM_NODE_HTTP_PORT}", self.host), // 'standard' nym-node + format!("https://{0}", self.host), // node behind https proxy (443) + format!("http://{0}", self.host), // node behind http proxy (80) + ]; + + // note: I removed 'standard' legacy mixnode port because it should now be automatically pulled via + // the 'custom_port' since it should have been present in the contract. + + if let Some(port) = self.port { + addresses_to_try.insert(0, format!("http://{0}:{port}", self.host)); + } + + for address in addresses_to_try { + // if provided host was malformed, no point in continuing + let client = match nym_node_requests::api::Client::builder(address).and_then(|b| { + b.with_timeout(Duration::from_secs(5)) + .with_user_agent("node-status-api-metrics-scraper") + .build() + }) { + Ok(client) => client, + Err(err) => { + return Err(NodeScraperError::MalformedHost { + host: self.host.to_string(), + node_id: self.node_id, + source: err, + }); + } + }; + + if let Ok(health) = client.get_health().await { + if health.status.is_up() { + return Ok(client); + } + } + } + + Err(NodeScraperError::NoHttpPortsAvailable { + host: self.host.to_string(), + node_id: self.node_id, + }) + } +} + +impl From for MetricsScrapingData { + fn from(value: NymNodeDetails) -> Self { + MetricsScrapingData::new( + value.bond_information.node.host.clone(), + value.node_id(), + value.bond_information.node.identity_key, + value.bond_information.node.custom_http_port, + ) + } +} + +impl From for MetricsScrapingData { + fn from(value: NymNodeDescription) -> Self { + MetricsScrapingData::new( + value.description.host_information.ip_address[0].to_string(), + value.node_id, + value.ed25519_identity_key().to_base58_string(), + None, + ) + } +} + +fn prepare_session_data( + stat: SessionStats, + node_data: &MetricsScrapingData, +) -> GatewaySessionsRecord { + let users_hashes = if !stat.unique_active_users_hashes.is_empty() { + Some(serde_json::to_string(&stat.unique_active_users_hashes).unwrap()) + } else { + None + }; + let vpn_durations = stat + .sessions + .iter() + .filter(|s| SessionType::from_string(&s.typ) == SessionType::Vpn) + .map(|s| s.duration_ms) + .collect::>(); + + let mixnet_durations = stat + .sessions + .iter() + .filter(|s| SessionType::from_string(&s.typ) == SessionType::Mixnet) + .map(|s| s.duration_ms) + .collect::>(); + + let unkown_durations = stat + .sessions + .iter() + .filter(|s| SessionType::from_string(&s.typ) == SessionType::Unknown) + .map(|s| s.duration_ms) + .collect::>(); + + let vpn_sessions = if !vpn_durations.is_empty() { + Some(serde_json::to_string(&vpn_durations).unwrap()) + } else { + None + }; + let mixnet_sessions = if !mixnet_durations.is_empty() { + Some(serde_json::to_string(&mixnet_durations).unwrap()) + } else { + None + }; + let unknown_sessions = if !unkown_durations.is_empty() { + Some(serde_json::to_string(&unkown_durations).unwrap()) + } else { + None + }; + + GatewaySessionsRecord { + gateway_identity_key: node_data.id_key.clone(), + node_id: node_data.node_id as i64, + day: stat.update_time.date(), + unique_active_clients: stat.unique_active_users as i64, + session_started: stat.sessions_started as i64, + users_hashes, + vpn_sessions, + mixnet_sessions, + unknown_sessions, + } +} diff --git a/nym-node/nym-node-requests/src/api/client.rs b/nym-node/nym-node-requests/src/api/client.rs index 11ec66a4b9..068b128ef2 100644 --- a/nym-node/nym-node-requests/src/api/client.rs +++ b/nym-node/nym-node-requests/src/api/client.rs @@ -19,6 +19,7 @@ use crate::api::v1::network_requester::models::NetworkRequester; pub use nym_http_api_client::Client; use super::v1::gateway::models::Wireguard; +use super::v1::metrics::models::SessionStats; pub type NymNodeApiClientError = HttpClientError; @@ -87,6 +88,11 @@ pub trait NymNodeApiClientExt: ApiClient { self.get_json_from(routes::api::v1::gateway::client_interfaces::wireguard_absolute()) .await } + + async fn get_sessions_metrics(&self) -> Result { + self.get_json_from(routes::api::v1::metrics::sessions_absolute()) + .await + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs index a32506db2d..4620db05f9 100644 --- a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs @@ -50,6 +50,7 @@ pub struct SessionStats { pub unique_active_users: u32, + #[serde(default = "Vec::new")] // field was added later pub unique_active_users_hashes: Vec, pub sessions: Vec, From fc79f739d4e6c71df2b376b408e374beef4c3f01 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:20:28 +0100 Subject: [PATCH 24/80] Fix overflow (#5204) --- Cargo.lock | 2 +- nym-node-status-api/nym-node-status-api/Cargo.toml | 2 +- nym-node-status-api/nym-node-status-api/src/monitor/mod.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc4eb9d37a..ff0b3f7cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6062,7 +6062,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "1.0.0-rc.4" +version = "1.0.0-rc.5" dependencies = [ "anyhow", "axum 0.7.7", diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index a915d31555..1698cbb858 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "1.0.0-rc.4" +version = "1.0.0-rc.5" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index 459992b0b3..c9205c84c4 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -249,7 +249,8 @@ impl Monitor { queries::ensure_mixnodes_still_bonded(&pool, &legacy_mixnodes).await?; let count_bonded_mixnodes_reserve = 0; // TODO: NymAPI doesn't report the reserve set size - let count_bonded_mixnodes_inactive = count_bonded_mixnodes - count_bonded_mixnodes_active; + let count_bonded_mixnodes_inactive = + count_bonded_mixnodes.saturating_sub(count_bonded_mixnodes_active); let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(&pool).await?; From e06d442e95b2291a4354cdb75ef04ead63dec280 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:35:56 +0100 Subject: [PATCH 25/80] Restore Location fields (#5208) * Add latitude/longitude fields to Location * Add regression test * Bump package version * Load secret during workflow --- .github/workflows/ci-build.yml | 1 + Cargo.lock | 2 +- .../nym-node-status-api/Cargo.toml | 2 +- .../src/monitor/geodata.rs | 96 ++++++++++++++++--- 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index d7a76f2673..56fd0d15eb 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -34,6 +34,7 @@ jobs: runs-on: ${{ matrix.os }} env: CARGO_TERM_COLOR: always + IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }} steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler diff --git a/Cargo.lock b/Cargo.lock index ff0b3f7cd2..468c8ce88a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6062,7 +6062,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "1.0.0-rc.5" +version = "1.0.0-rc.6" dependencies = [ "anyhow", "axum 0.7.7", diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 1698cbb858..3408d1e4ef 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "1.0.0-rc.5" +version = "1.0.0-rc.6" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs index 1042b15cce..369acee1a7 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, Coin}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; pub(crate) struct IpInfoClient { client: reqwest::Client, @@ -15,11 +15,7 @@ impl IpInfoClient { } pub(crate) async fn locate_ip(&self, ip: impl AsRef) -> anyhow::Result { - let url = format!( - "https://ipinfo.io/{}/country?token={}", - ip.as_ref(), - &self.token - ); + let url = format!("https://ipinfo.io/{}?token={}", ip.as_ref(), &self.token); let response = self .client .get(url) @@ -33,11 +29,12 @@ impl IpInfoClient { } anyhow::Error::from(err) })?; - let response_text = response.text().await?.trim().to_string(); + let raw_response = response.text().await?; + let response: LocationResponse = + serde_json::from_str(&raw_response).inspect_err(|e| tracing::error!("{e}"))?; + let location = response.into(); - Ok(Location { - two_letter_iso_country_code: response_text, - }) + Ok(location) } /// check DOESN'T consume bandwidth allowance @@ -64,7 +61,7 @@ impl IpInfoClient { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] pub(crate) struct NodeGeoData { pub(crate) identity_key: String, pub(crate) owner: Addr, @@ -72,15 +69,55 @@ pub(crate) struct NodeGeoData { pub(crate) location: Location, } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize)] pub(crate) struct Location { pub(crate) two_letter_iso_country_code: String, + #[serde(flatten)] + pub(crate) location: Coordinates, +} + +impl From for Location { + fn from(value: LocationResponse) -> Self { + Self { + two_letter_iso_country_code: value.two_letter_iso_country_code, + location: value.loc, + } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub(crate) struct LocationResponse { + #[serde(rename = "country")] + pub(crate) two_letter_iso_country_code: String, + #[serde(deserialize_with = "deserialize_loc")] + pub(crate) loc: Coordinates, +} + +fn deserialize_loc<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let loc_raw = String::deserialize(deserializer)?; + match loc_raw.split_once(',') { + Some((lat, long)) => Ok(Coordinates { + latitude: lat.parse().map_err(serde::de::Error::custom)?, + longitude: long.parse().map_err(serde::de::Error::custom)?, + }), + None => Err(serde::de::Error::custom("coordinates")), + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub(crate) struct Coordinates { + pub(crate) latitude: f64, + pub(crate) longitude: f64, } impl Location { pub(crate) fn empty() -> Self { Self { two_letter_iso_country_code: String::new(), + location: Coordinates::default(), } } } @@ -110,3 +147,38 @@ pub(crate) mod ipinfo { pub(crate) remaining: u64, } } + +#[cfg(test)] +mod api_regression { + + use super::*; + use std::{env::var, sync::LazyLock}; + + static IPINFO_TOKEN: LazyLock = LazyLock::new(|| var("IPINFO_API_TOKEN").unwrap()); + + #[tokio::test] + async fn should_parse_response() { + let client = IpInfoClient::new(&(*IPINFO_TOKEN)); + let my_ip = reqwest::get("https://api.ipify.org") + .await + .expect("Couldn't get own IP") + .text() + .await + .unwrap(); + + let location_result = client.locate_ip(my_ip).await; + assert!(location_result.is_ok(), "Did ipinfo response change?"); + + assert!( + client.check_remaining_bandwidth().await.is_ok(), + "Failed to check remaining bandwidth?" + ); + + // when serialized, these fields should be present because they're exposed over API + let location_result = location_result.unwrap(); + let json = serde_json::to_value(&location_result).unwrap(); + assert!(json.get("two_letter_iso_country_code").is_some()); + assert!(json.get("latitude").is_some()); + assert!(json.get("longitude").is_some()); + } +} From f3bf5d080b4515eee8aac52323239d9c7d13a65a Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Wed, 4 Dec 2024 11:11:51 +0100 Subject: [PATCH 26/80] better date serilization (#5207) --- Cargo.lock | 1 + nym-node-status-api/nym-node-status-api/Cargo.toml | 1 + nym-node-status-api/nym-node-status-api/src/http/models.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 468c8ce88a..dcb18bf97d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6078,6 +6078,7 @@ dependencies = [ "nym-network-defaults", "nym-node-requests", "nym-node-status-client", + "nym-serde-helpers", "nym-statistics-common", "nym-task", "nym-validator-client", diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 3408d1e4ef..f5b4c40202 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -26,6 +26,7 @@ nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } nym-explorer-client = { path = "../../explorer-api/explorer-client" } nym-network-defaults = { path = "../../common/network-defaults" } +nym-serde-helpers = { path = "../../common/serde-helpers"} nym-statistics-common = { path = "../../common/statistics"} nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-task = { path = "../../common/task" } diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs index 19060d1ce8..276424d2b0 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -79,6 +79,7 @@ pub(crate) struct SummaryHistory { pub struct SessionStats { pub gateway_identity_key: String, pub node_id: u32, + #[serde(with = "nym_serde_helpers::date")] pub day: time::Date, pub unique_active_clients: i64, pub session_started: i64, From b06091e54894b887175c7baf8f13d0673e24f70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 Dec 2024 11:21:33 +0100 Subject: [PATCH 27/80] Derive serialize for UserAgent (#5210) --- common/http-api-client/src/user_agent.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/http-api-client/src/user_agent.rs b/common/http-api-client/src/user_agent.rs index 1cdbf45825..d47cb570c4 100644 --- a/common/http-api-client/src/user_agent.rs +++ b/common/http-api-client/src/user_agent.rs @@ -5,8 +5,9 @@ use std::fmt; use http::HeaderValue; use nym_bin_common::build_information::{BinaryBuildInformation, BinaryBuildInformationOwned}; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct UserAgent { pub application: String, pub version: String, From d1f702c4aa18f86dfd1116548898ce2f11374d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 5 Dec 2024 14:48:33 +0200 Subject: [PATCH 28/80] Extend raw ws fd for gateway client (#5218) --- common/client-libs/gateway-client/src/socket_state.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index 942f650614..6dcd055c9e 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -46,7 +46,8 @@ pub(crate) fn ws_fd(_conn: &WsConn) -> Option { #[cfg(unix)] match _conn.get_ref() { MaybeTlsStream::Plain(stream) => Some(stream.as_raw_fd()), - &_ => None, + MaybeTlsStream::Rustls(tls_stream) => Some(tls_stream.as_raw_fd()), + _ => None, } #[cfg(not(unix))] None From 5a07b73375d18f03f0cc2c53909de18973635604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 5 Dec 2024 17:21:36 +0000 Subject: [PATCH 29/80] =?UTF-8?q?feature:=20hopefully=20final=20steps=20of?= =?UTF-8?q?=20the=20smoosh=E2=84=A2=EF=B8=8F=20(#5201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * removed mnemonic from gateway config struct scaffolding for common mixnet listener running verloc unconditionally in a nym-node remove filtering by mixnode extracted verloc to separate crate integrated nym-node-http-server more tightly with the binary most logic for handling forward packets running all mixnode-related tasks natively inside nymnode removed gateway storage trait in favour of the only concrete implementation most logic for handling final hop packets using nym-node owned socket listener for gateways utility for sending plain message through mixnet + gateway fix using common packet forwarding in both modes nifying nym-node metrics reproduce behaviour of the console logger cleaned up cli args redesigned gateway tasks startup procedure removing dead code scaffolding for old config v6 config migration implemented MixnetMetricsCleaner * clippy * require entry/exit for wireguard * removed dead code in migration code * updated config template * use custom user agent for verloc queries * fixed premature shutdown of gateway tasks * hidden nym-api flag to allow illegal node ips * experiment: final hop handing with wireguard * added additional startup logs * typo * fixed legacy stats endpoint data * additional logs * apply review comments * fixed local testnet manager --- Cargo.lock | 262 ++-- Cargo.toml | 8 +- common/client-core/Cargo.toml | 1 + .../src/client/mix_traffic/transceiver.rs | 9 +- common/client-libs/mixnet-client/Cargo.toml | 12 +- .../client-libs/mixnet-client/src/client.rs | 12 +- .../mixnet-client/src/forwarder.rs | 115 +- common/client-libs/mixnet-client/src/lib.rs | 2 + .../src/bandwidth_storage_manager.rs | 10 +- .../src/ecash/credential_sender.rs | 16 +- .../src/ecash/error.rs | 4 +- .../credential-verification/src/ecash/mod.rs | 15 +- .../src/ecash/state.rs | 13 +- common/credential-verification/src/error.rs | 2 +- common/credential-verification/src/lib.rs | 22 +- common/gateway-stats-storage/Cargo.toml | 2 + common/gateway-stats-storage/src/lib.rs | 15 +- common/gateway-stats-storage/src/models.rs | 67 +- common/gateway-storage/Cargo.toml | 1 - common/gateway-storage/src/error.rs | 2 +- common/gateway-storage/src/lib.rs | 382 ++--- common/gateway-storage/src/models.rs | 16 +- common/mixnode-common/Cargo.toml | 6 +- common/mixnode-common/src/lib.rs | 2 +- common/mixnode-common/src/verloc/error.rs | 81 -- .../mixnode-common/src/verloc/measurement.rs | 35 - common/mixnode-common/src/verloc/mod.rs | 383 ----- common/nymsphinx/framing/src/packet.rs | 10 +- common/nymsphinx/params/src/packet_version.rs | 8 +- common/statistics/Cargo.toml | 1 + common/statistics/src/gateways.rs | 79 +- common/statistics/src/lib.rs | 6 +- common/task/src/manager.rs | 14 +- common/verloc/Cargo.toml | 27 + common/verloc/src/error.rs | 72 + common/verloc/src/lib.rs | 11 + common/verloc/src/measurements/config.rs | 135 ++ .../src/measurements}/listener.rs | 55 +- common/verloc/src/measurements/measurer.rs | 225 +++ common/verloc/src/measurements/metrics.rs | 50 + common/verloc/src/measurements/mod.rs | 16 + .../src/measurements}/packet.rs | 48 +- .../src/measurements}/sender.rs | 96 +- common/verloc/src/models.rs | 225 +++ common/wireguard/Cargo.toml | 1 + common/wireguard/src/error.rs | 2 +- common/wireguard/src/lib.rs | 21 +- common/wireguard/src/peer_controller.rs | 20 +- common/wireguard/src/peer_handle.rs | 15 +- common/wireguard/src/peer_storage_manager.rs | 10 +- gateway/Cargo.toml | 3 +- gateway/src/config.rs | 160 +-- gateway/src/error.rs | 14 +- gateway/src/lib.rs | 2 +- .../node/client_handling/active_clients.rs | 22 +- .../client_handling/embedded_clients/mod.rs | 7 +- .../client_handling/websocket/common_state.rs | 15 +- .../connection_handler/authenticated.rs | 72 +- .../websocket/connection_handler/fresh.rs | 65 +- .../websocket/connection_handler/mod.rs | 4 +- .../client_handling/websocket/listener.rs | 48 +- .../src/node/internal_service_providers.rs | 226 +++ .../receiver/connection_handler.rs | 23 +- .../node/mixnet_handling/receiver/listener.rs | 11 +- gateway/src/node/mod.rs | 934 +++++------- gateway/src/node/statistics/mod.rs | 86 -- mixnode/Cargo.toml | 54 - mixnode/README.md | 75 - mixnode/src/config.rs | 237 ---- mixnode/src/lib.rs | 7 - .../node/listener/connection_handler/mod.rs | 129 -- .../connection_handler/packet_processing.rs | 46 - mixnode/src/node/listener/mod.rs | 60 - mixnode/src/node/mod.rs | 217 --- mixnode/src/node/node_statistics.rs | 435 ------ mixnode/src/node/packet_delayforwarder.rs | 319 ----- nym-api/src/support/cli/init.rs | 3 + nym-api/src/support/cli/run.rs | 3 + nym-api/src/support/config/mod.rs | 3 + nym-api/src/support/config/override.rs | 4 + .../nym-node-status-api/Cargo.toml | 15 +- .../src/node_scraper/mod.rs | 3 +- nym-node/Cargo.toml | 36 +- nym-node/nym-node-http-api/Cargo.toml | 47 - nym-node/nym-node-http-api/src/lib.rs | 67 - .../src/middleware/logging.rs | 51 - nym-node/nym-node-http-api/src/mod.rs | 64 - .../src/router/api/v1/metrics/mixing.rs | 33 - .../src/router/api/v1/metrics/sessions.rs | 33 - .../src/router/api/v1/metrics/verloc.rs | 32 - .../nym-node-http-api/src/state/metrics.rs | 214 --- nym-node/nym-node-http-api/src/state/mod.rs | 54 - nym-node/nym-node-metrics/Cargo.toml | 20 + nym-node/nym-node-metrics/src/entry.rs | 112 ++ nym-node/nym-node-metrics/src/events.rs | 53 + nym-node/nym-node-metrics/src/lib.rs | 39 + nym-node/nym-node-metrics/src/mixnet.rs | 292 ++++ nym-node/nym-node-metrics/src/network.rs | 27 + .../src/api/v1/metrics/models.rs | 427 ++---- nym-node/nym-node-requests/src/lib.rs | 6 +- .../src/cli/commands/bonding_information.rs | 20 +- nym-node/src/cli/commands/build_info.rs | 2 +- nym-node/src/cli/commands/migrate.rs | 2 +- nym-node/src/cli/commands/node_details.rs | 6 +- nym-node/src/cli/commands/run/args.rs | 73 +- nym-node/src/cli/commands/run/mod.rs | 20 +- nym-node/src/cli/commands/sign.rs | 4 +- nym-node/src/cli/helpers.rs | 66 +- nym-node/src/cli/mod.rs | 2 +- nym-node/src/config/exit_gateway.rs | 297 ---- .../{entry_gateway.rs => gateway_tasks.rs} | 73 +- nym-node/src/config/helpers.rs | 243 ++-- nym-node/src/config/metrics.rs | 60 + nym-node/src/config/mixnode.rs | 221 --- nym-node/src/config/mod.rs | 284 +++- nym-node/src/config/old_configs/mod.rs | 2 + .../src/config/old_configs/old_config_v1.rs | 93 -- .../src/config/old_configs/old_config_v2.rs | 36 - .../src/config/old_configs/old_config_v3.rs | 144 +- .../src/config/old_configs/old_config_v4.rs | 146 +- .../src/config/old_configs/old_config_v5.rs | 219 +-- .../src/config/old_configs/old_config_v6.rs | 1256 +++++++++++++++++ nym-node/src/config/persistence.rs | 23 +- nym-node/src/config/service_providers.rs | 155 ++ nym-node/src/config/template.rs | 151 +- nym-node/src/config/upgrade_helpers.rs | 3 +- nym-node/src/env.rs | 6 +- nym-node/src/error.rs | 40 +- nym-node/src/lib.rs | 11 - nym-node/src/main.rs | 3 + nym-node/src/node/bonding_information.rs | 177 +-- nym-node/src/node/description.rs | 4 +- nym-node/src/node/helpers.rs | 30 +- .../src => src/node/http}/error.rs | 0 nym-node/src/node/http/helpers/mod.rs | 38 + .../node/http/{ => helpers}/system_info.rs | 4 +- .../src => src/node/http}/middleware/mod.rs | 2 - nym-node/src/node/http/mod.rs | 90 +- .../src => src/node/http}/router/api/mod.rs | 4 +- .../http}/router/api/v1/authenticator/mod.rs | 0 .../http}/router/api/v1/authenticator/root.rs | 2 +- .../api/v1/gateway/client_interfaces/mod.rs | 2 +- .../node/http}/router/api/v1/gateway/mod.rs | 0 .../node/http}/router/api/v1/gateway/root.rs | 2 +- .../node/http}/router/api/v1/health.rs | 4 +- .../router/api/v1/ip_packet_router/mod.rs | 0 .../router/api/v1/ip_packet_router/root.rs | 2 +- .../router/api/v1/metrics/legacy_mixing.rs | 47 + .../node/http}/router/api/v1/metrics/mod.rs | 20 +- .../router/api/v1/metrics/packets_stats.rs | 53 + .../http}/router/api/v1/metrics/prometheus.rs | 2 +- .../http/router/api/v1/metrics/sessions.rs | 54 + .../node/http/router/api/v1/metrics/verloc.rs | 73 + .../node/http}/router/api/v1/mixnode/mod.rs | 0 .../node/http}/router/api/v1/mixnode/root.rs | 2 +- .../node/http}/router/api/v1/mod.rs | 2 +- .../api/v1/network_requester/exit_policy.rs | 2 +- .../router/api/v1/network_requester/mod.rs | 2 +- .../router/api/v1/network_requester/root.rs | 2 +- .../http}/router/api/v1/node/auxiliary.rs | 4 +- .../router/api/v1/node/build_information.rs | 2 +- .../http}/router/api/v1/node/description.rs | 4 +- .../node/http}/router/api/v1/node/hardware.rs | 4 +- .../router/api/v1/node/host_information.rs | 2 +- .../node/http}/router/api/v1/node/mod.rs | 12 +- .../node/http}/router/api/v1/node/roles.rs | 2 +- .../node/http}/router/api/v1/openapi.rs | 13 +- .../node/http}/router/landing_page.rs | 0 .../src => src/node/http}/router/mod.rs | 88 +- .../src => src/node/http}/router/types.rs | 0 nym-node/src/node/http/state/metrics.rs | 23 + nym-node/src/node/http/state/mod.rs | 40 + nym-node/src/node/metrics/aggregator.rs | 147 ++ nym-node/src/node/metrics/console_logger.rs | 120 ++ nym-node/src/node/metrics/events_listener.rs | 2 + .../node/metrics/handler/client_sessions.rs | 193 +-- .../metrics/handler/legacy_packet_data.rs | 68 + .../metrics/handler/mixnet_data_cleaner.rs | 107 ++ nym-node/src/node/metrics/handler/mod.rs | 122 ++ nym-node/src/node/metrics/mod.rs | 7 + nym-node/src/node/mixnet/handler.rs | 184 +++ nym-node/src/node/mixnet/listener.rs | 57 + nym-node/src/node/mixnet/mod.rs | 10 + nym-node/src/node/mixnet/packet_forwarding.rs | 143 ++ nym-node/src/node/mixnet/shared/final_hop.rs | 51 + nym-node/src/node/mixnet/shared/mod.rs | 181 +++ nym-node/src/node/mod.rs | 766 ++++++---- .../src/node/shared_topology.rs | 24 +- nym-node/src/wireguard/error.rs | 8 +- sdk/rust/nym-sdk/src/lib.rs | 13 +- .../authenticator/src/authenticator.rs | 9 +- .../authenticator/src/cli/run.rs | 10 +- service-providers/authenticator/src/error.rs | 2 +- .../authenticator/src/mixnet_listener.rs | 11 +- .../ip-packet-router/src/ip_packet_router.rs | 2 +- .../mixnet-connectivity-check/Cargo.toml | 23 + .../mixnet-connectivity-check/src/main.rs | 156 ++ .../testnet-manager/src/manager/local_apis.rs | 2 +- .../src/manager/local_client.rs | 10 +- .../src/manager/local_nodes.rs | 47 +- .../testnet-manager/src/manager/node.rs | 4 - 201 files changed, 7434 insertions(+), 7071 deletions(-) delete mode 100644 common/mixnode-common/src/verloc/error.rs delete mode 100644 common/mixnode-common/src/verloc/measurement.rs delete mode 100644 common/mixnode-common/src/verloc/mod.rs create mode 100644 common/verloc/Cargo.toml create mode 100644 common/verloc/src/error.rs create mode 100644 common/verloc/src/lib.rs create mode 100644 common/verloc/src/measurements/config.rs rename common/{mixnode-common/src/verloc => verloc/src/measurements}/listener.rs (78%) create mode 100644 common/verloc/src/measurements/measurer.rs create mode 100644 common/verloc/src/measurements/metrics.rs create mode 100644 common/verloc/src/measurements/mod.rs rename common/{mixnode-common/src/verloc => verloc/src/measurements}/packet.rs (61%) rename common/{mixnode-common/src/verloc => verloc/src/measurements}/sender.rs (68%) create mode 100644 common/verloc/src/models.rs create mode 100644 gateway/src/node/internal_service_providers.rs delete mode 100644 gateway/src/node/statistics/mod.rs delete mode 100644 mixnode/Cargo.toml delete mode 100644 mixnode/README.md delete mode 100644 mixnode/src/config.rs delete mode 100644 mixnode/src/lib.rs delete mode 100644 mixnode/src/node/listener/connection_handler/mod.rs delete mode 100644 mixnode/src/node/listener/connection_handler/packet_processing.rs delete mode 100644 mixnode/src/node/listener/mod.rs delete mode 100644 mixnode/src/node/mod.rs delete mode 100644 mixnode/src/node/node_statistics.rs delete mode 100644 mixnode/src/node/packet_delayforwarder.rs delete mode 100644 nym-node/nym-node-http-api/Cargo.toml delete mode 100644 nym-node/nym-node-http-api/src/lib.rs delete mode 100644 nym-node/nym-node-http-api/src/middleware/logging.rs delete mode 100644 nym-node/nym-node-http-api/src/mod.rs delete mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs delete mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs delete mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs delete mode 100644 nym-node/nym-node-http-api/src/state/metrics.rs delete mode 100644 nym-node/nym-node-http-api/src/state/mod.rs create mode 100644 nym-node/nym-node-metrics/Cargo.toml create mode 100644 nym-node/nym-node-metrics/src/entry.rs create mode 100644 nym-node/nym-node-metrics/src/events.rs create mode 100644 nym-node/nym-node-metrics/src/lib.rs create mode 100644 nym-node/nym-node-metrics/src/mixnet.rs create mode 100644 nym-node/nym-node-metrics/src/network.rs delete mode 100644 nym-node/src/config/exit_gateway.rs rename nym-node/src/config/{entry_gateway.rs => gateway_tasks.rs} (66%) create mode 100644 nym-node/src/config/metrics.rs delete mode 100644 nym-node/src/config/mixnode.rs create mode 100644 nym-node/src/config/old_configs/old_config_v6.rs create mode 100644 nym-node/src/config/service_providers.rs delete mode 100644 nym-node/src/lib.rs rename nym-node/{nym-node-http-api/src => src/node/http}/error.rs (100%) create mode 100644 nym-node/src/node/http/helpers/mod.rs rename nym-node/src/node/http/{ => helpers}/system_info.rs (94%) rename nym-node/{nym-node-http-api/src => src/node/http}/middleware/mod.rs (85%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/mod.rs (79%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/authenticator/mod.rs (100%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/authenticator/root.rs (94%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/gateway/client_interfaces/mod.rs (98%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/gateway/mod.rs (100%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/gateway/root.rs (94%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/health.rs (90%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/ip_packet_router/mod.rs (100%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/ip_packet_router/root.rs (94%) create mode 100644 nym-node/src/node/http/router/api/v1/metrics/legacy_mixing.rs rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/metrics/mod.rs (53%) create mode 100644 nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/metrics/prometheus.rs (96%) create mode 100644 nym-node/src/node/http/router/api/v1/metrics/sessions.rs create mode 100644 nym-node/src/node/http/router/api/v1/metrics/verloc.rs rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/mixnode/mod.rs (100%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/mixnode/root.rs (94%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/mod.rs (97%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/network_requester/exit_policy.rs (93%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/network_requester/mod.rs (93%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/network_requester/root.rs (94%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/node/auxiliary.rs (88%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/node/build_information.rs (93%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/node/description.rs (88%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/node/hardware.rs (91%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/node/host_information.rs (93%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/node/mod.rs (84%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/node/roles.rs (92%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/api/v1/openapi.rs (91%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/landing_page.rs (100%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/mod.rs (67%) rename nym-node/{nym-node-http-api/src => src/node/http}/router/types.rs (100%) create mode 100644 nym-node/src/node/http/state/metrics.rs create mode 100644 nym-node/src/node/http/state/mod.rs create mode 100644 nym-node/src/node/metrics/aggregator.rs create mode 100644 nym-node/src/node/metrics/console_logger.rs create mode 100644 nym-node/src/node/metrics/events_listener.rs rename gateway/src/node/statistics/sessions.rs => nym-node/src/node/metrics/handler/client_sessions.rs (55%) create mode 100644 nym-node/src/node/metrics/handler/legacy_packet_data.rs create mode 100644 nym-node/src/node/metrics/handler/mixnet_data_cleaner.rs create mode 100644 nym-node/src/node/metrics/handler/mod.rs create mode 100644 nym-node/src/node/metrics/mod.rs create mode 100644 nym-node/src/node/mixnet/handler.rs create mode 100644 nym-node/src/node/mixnet/listener.rs create mode 100644 nym-node/src/node/mixnet/mod.rs create mode 100644 nym-node/src/node/mixnet/packet_forwarding.rs create mode 100644 nym-node/src/node/mixnet/shared/final_hop.rs create mode 100644 nym-node/src/node/mixnet/shared/mod.rs rename gateway/src/node/helpers.rs => nym-node/src/node/shared_topology.rs (80%) create mode 100644 tools/internal/mixnet-connectivity-check/Cargo.toml create mode 100644 tools/internal/mixnet-connectivity-check/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index dcb18bf97d..0b2959b750 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -283,7 +283,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -352,7 +352,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -363,7 +363,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -573,7 +573,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1140,7 +1140,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1790,7 +1790,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1948,7 +1948,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1970,7 +1970,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core 0.20.9", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2021,7 +2021,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2064,7 +2064,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2097,7 +2097,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2165,7 +2165,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2531,7 +2531,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2765,7 +2765,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2860,7 +2860,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3248,6 +3248,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "human-repr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" + [[package]] name = "humantime" version = "1.3.0" @@ -3822,9 +3828,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libm" @@ -3950,7 +3956,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3961,7 +3967,7 @@ checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3974,7 +3980,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4128,6 +4134,21 @@ dependencies = [ "wasm-utils", ] +[[package]] +name = "mixnet-connectivity-check" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.5.20", + "futures", + "nym-bin-common", + "nym-crypto", + "nym-network-defaults", + "nym-sdk", + "tokio", + "tracing", +] + [[package]] name = "moka" version = "0.12.8" @@ -4431,7 +4452,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4851,6 +4872,7 @@ dependencies = [ "nym-gateway-requests", "nym-id", "nym-metrics", + "nym-mixnet-client", "nym-network-defaults", "nym-nonexhaustive-delayqueue", "nym-pemstore", @@ -5443,7 +5465,7 @@ dependencies = [ "nym-mixnode-common", "nym-network-defaults", "nym-network-requester", - "nym-node-http-api", + "nym-node-metrics", "nym-sdk", "nym-sphinx", "nym-statistics-common", @@ -5535,9 +5557,11 @@ name = "nym-gateway-stats-storage" version = "0.1.0" dependencies = [ "nym-credentials-interface", + "nym-node-metrics", "nym-sphinx", "nym-statistics-common", "sqlx", + "strum 0.26.3", "thiserror", "time", "tokio", @@ -5548,7 +5572,6 @@ dependencies = [ name = "nym-gateway-storage" version = "0.1.0" dependencies = [ - "async-trait", "bincode", "defguard_wireguard_rs", "log", @@ -5741,11 +5764,11 @@ name = "nym-mixnet-client" version = "0.1.0" dependencies = [ "futures", - "log", "nym-sphinx", "nym-task", "tokio", "tokio-util", + "tracing", ] [[package]] @@ -5772,35 +5795,6 @@ dependencies = [ "utoipa", ] -[[package]] -name = "nym-mixnode" -version = "1.1.37" -dependencies = [ - "colored", - "futures", - "nym-contracts-common", - "nym-crypto", - "nym-http-api-common", - "nym-metrics", - "nym-mixnet-client", - "nym-mixnode-common", - "nym-node-http-api", - "nym-nonexhaustive-delayqueue", - "nym-sphinx", - "nym-sphinx-params", - "nym-sphinx-types", - "nym-task", - "nym-topology", - "nym-types", - "nym-validator-client", - "thiserror", - "time", - "tokio", - "tokio-util", - "tracing", - "url", -] - [[package]] name = "nym-mixnode-common" version = "0.1.0" @@ -5809,11 +5803,9 @@ dependencies = [ "futures", "humantime-serde", "log", - "nym-bin-common", "nym-crypto", "nym-metrics", "nym-network-defaults", - "nym-node-http-api", "nym-sphinx-acknowledgements", "nym-sphinx-addressing", "nym-sphinx-forwarding", @@ -5821,7 +5813,6 @@ dependencies = [ "nym-sphinx-params", "nym-sphinx-types", "nym-task", - "nym-validator-client", "rand", "serde", "thiserror", @@ -5946,6 +5937,9 @@ name = "nym-node" version = "1.1.12" dependencies = [ "anyhow", + "async-trait", + "axum 0.7.7", + "axum-extra", "bip39", "bs58", "cargo_metadata 0.18.1", @@ -5953,6 +5947,10 @@ dependencies = [ "clap 4.5.20", "colored", "cupid", + "dashmap", + "futures", + "headers", + "human-repr", "humantime-serde", "ipnetwork 0.20.0", "nym-authenticator", @@ -5961,63 +5959,60 @@ dependencies = [ "nym-config", "nym-crypto", "nym-gateway", + "nym-gateway-stats-storage", + "nym-http-api-common", "nym-ip-packet-router", - "nym-mixnode", + "nym-metrics", + "nym-mixnet-client", "nym-network-requester", - "nym-node-http-api", + "nym-node-metrics", + "nym-node-requests", + "nym-nonexhaustive-delayqueue", "nym-pemstore", "nym-sphinx-acknowledgements", "nym-sphinx-addressing", + "nym-sphinx-forwarding", + "nym-sphinx-framing", + "nym-sphinx-types", "nym-task", + "nym-topology", "nym-types", "nym-validator-client", + "nym-verloc", "nym-wireguard", "nym-wireguard-types", "rand", "semver 1.0.23", "serde", "serde_json", + "si-scale", "sysinfo", "thiserror", + "time", "tokio", + "tokio-util", "toml 0.8.14", + "tower-http", "tracing", "tracing-subscriber", "url", + "utoipa", + "utoipa-swagger-ui", "zeroize", ] [[package]] -name = "nym-node-http-api" +name = "nym-node-metrics" version = "0.1.0" dependencies = [ - "axum 0.7.7", - "axum-extra", - "base64 0.22.1", - "colored", "dashmap", - "fastrand 2.1.1", - "headers", - "hmac", - "hyper 1.4.1", - "ipnetwork 0.20.0", - "nym-crypto", - "nym-http-api-common", + "futures", "nym-metrics", - "nym-node-requests", - "nym-task", - "nym-wireguard", - "rand", - "serde_json", - "thiserror", + "nym-statistics-common", + "strum 0.26.3", "time", "tokio", - "tower 0.4.13", - "tower-http", "tracing", - "utoipa", - "utoipa-swagger-ui", - "x25519-dalek", ] [[package]] @@ -6076,6 +6071,7 @@ dependencies = [ "nym-crypto", "nym-explorer-client", "nym-network-defaults", + "nym-node-metrics", "nym-node-requests", "nym-node-status-client", "nym-serde-helpers", @@ -6812,6 +6808,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-verloc" +version = "0.1.0" +dependencies = [ + "bytes", + "futures", + "humantime 2.1.0", + "nym-crypto", + "nym-task", + "nym-validator-client", + "rand", + "thiserror", + "time", + "tokio", + "tokio-util", + "tracing", + "url", +] + [[package]] name = "nym-vesting-contract-common" version = "0.7.0" @@ -6893,6 +6908,7 @@ dependencies = [ "time", "tokio", "tokio-stream", + "tracing", "x25519-dalek", ] @@ -7024,9 +7040,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -7226,7 +7242,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7313,7 +7329,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7354,7 +7370,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7569,14 +7585,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -7589,7 +7605,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "version_check", "yansi", ] @@ -7652,7 +7668,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7885,7 +7901,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8112,7 +8128,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn 2.0.87", + "syn 2.0.90", "unicode-xid", "version_check", ] @@ -8238,7 +8254,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.87", + "syn 2.0.90", "walkdir", ] @@ -8294,9 +8310,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.5.0", "errno", @@ -8487,7 +8503,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8519,7 +8535,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8651,7 +8667,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8662,7 +8678,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8673,7 +8689,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8735,7 +8751,7 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8756,7 +8772,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8807,7 +8823,7 @@ dependencies = [ "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9406,7 +9422,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9449,9 +9465,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -9757,7 +9773,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9888,7 +9904,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10167,7 +10183,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10331,7 +10347,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "termcolor", ] @@ -10358,7 +10374,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10561,7 +10577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10593,7 +10609,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn 2.0.90", "toml 0.5.11", "uniffi_build", "uniffi_meta", @@ -10725,7 +10741,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.87", + "syn 2.0.90", "uuid", ] @@ -10764,7 +10780,7 @@ checksum = "17e82ab96c5a55263b5bed151b8426410d93aa909a453acdbd4b6792b5af7d64" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10775,7 +10791,7 @@ checksum = "86b8338dc3c9526011ffaa2aa6bd60ddfda9d49d2123108690755c6e34844212" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "utoipauto-core", ] @@ -10882,7 +10898,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -10916,7 +10932,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10950,7 +10966,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11478,7 +11494,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11498,7 +11514,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ae3f32e389..7fcf6070d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,6 @@ members = [ "explorer-api/explorer-client", "gateway", "integrations/bity", - "mixnode", "sdk/ffi/cpp", "sdk/ffi/go", "sdk/ffi/shared", @@ -123,8 +122,8 @@ members = [ "nym-data-observatory", "nym-network-monitor", "nym-node", - "nym-node/nym-node-http-api", "nym-node/nym-node-requests", + "nym-node/nym-node-metrics", "nym-node-status-api/nym-node-status-agent", "nym-node-status-api/nym-node-status-api", "nym-node-status-api/nym-node-status-client", @@ -149,15 +148,13 @@ members = [ "tools/internal/contract-state-importer/importer-cli", "tools/internal/contract-state-importer/importer-contract", "tools/internal/testnet-manager", - "tools/internal/testnet-manager/dkg-bypass-contract", + "tools/internal/testnet-manager/dkg-bypass-contract", "common/verloc", "tools/internal/mixnet-connectivity-check", ] default-members = [ "clients/native", "clients/socks5", "explorer-api", - "gateway", - "mixnode", "nym-api", "nym-credential-proxy/nym-credential-proxy", "nym-data-observatory", @@ -263,6 +260,7 @@ http-body-util = "0.1" httpcodec = "0.2.3" humantime = "2.1.0" humantime-serde = "1.1.1" +human-repr = "1.1.0" hyper = "1.4.1" hyper-util = "0.1" indicatif = "0.17.8" diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index 754eb61440..b5191eef80 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -46,6 +46,7 @@ nym-sphinx = { path = "../nymsphinx" } nym-statistics-common = { path = "../statistics" } nym-pemstore = { path = "../pemstore" } nym-topology = { path = "../topology", features = ["serializable"] } +nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false } nym-validator-client = { path = "../client-libs/validator-client", default-features = false } nym-task = { path = "../task" } nym-credentials-interface = { path = "../credentials-interface" } diff --git a/common/client-core/src/client/mix_traffic/transceiver.rs b/common/client-core/src/client/mix_traffic/transceiver.rs index 0862911d93..6d9b4fa6de 100644 --- a/common/client-core/src/client/mix_traffic/transceiver.rs +++ b/common/client-core/src/client/mix_traffic/transceiver.rs @@ -14,7 +14,7 @@ use std::os::raw::c_int as RawFd; use thiserror::Error; #[cfg(not(target_arch = "wasm32"))] -use futures::channel::{mpsc, oneshot}; +use futures::channel::oneshot; // we need to type erase the error type since we can't have dynamic associated types alongside dynamic dispatch #[derive(Debug, Error)] @@ -170,7 +170,7 @@ pub struct LocalGateway { // 'sender' part /// Channel responsible for taking mix packets and forwarding them further into the further mixnet layers. - packet_forwarder: mpsc::UnboundedSender, + packet_forwarder: nym_mixnet_client::forwarder::MixForwardingSender, // 'receiver' part packet_router_tx: Option>, @@ -180,7 +180,7 @@ pub struct LocalGateway { impl LocalGateway { pub fn new( local_identity: identity::PublicKey, - packet_forwarder: mpsc::UnboundedSender, + packet_forwarder: nym_mixnet_client::forwarder::MixForwardingSender, packet_router_tx: oneshot::Sender, ) -> Self { LocalGateway { @@ -208,8 +208,7 @@ mod nonwasm_sealed { impl GatewaySender for LocalGateway { async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> { self.packet_forwarder - .unbounded_send(packet) - .map_err(|err| err.into_send_error()) + .forward_packet(packet) .map_err(erase_err) } } diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 68e048d1f0..25dd62f702 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -9,10 +9,14 @@ license.workspace = true [dependencies] futures = { workspace = true } -log = { workspace = true } -tokio = { workspace = true, features = ["time", "net", "rt"] } -tokio-util = { workspace = true, features = ["codec"] } +tracing = { workspace = true } +tokio = { workspace = true, features = ["time"] } +tokio-util = { workspace = true, features = ["codec"], optional = true } # internal nym-sphinx = { path = "../../nymsphinx" } -nym-task = { path = "../../task" } +nym-task = { path = "../../task", optional = true } + +[features] +default = ["client"] +client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"] \ No newline at end of file diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs index 4da308144f..b8eebcdcc5 100644 --- a/common/client-libs/mixnet-client/src/client.rs +++ b/common/client-libs/mixnet-client/src/client.rs @@ -3,7 +3,6 @@ use futures::channel::mpsc; use futures::StreamExt; -use log::*; use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; use nym_sphinx::framing::codec::NymCodec; use nym_sphinx::framing::packet::FramedNymPacket; @@ -18,13 +17,14 @@ use std::time::Duration; use tokio::net::TcpStream; use tokio::time::sleep; use tokio_util::codec::Framed; +use tracing::*; +#[derive(Clone, Copy)] pub struct Config { initial_reconnection_backoff: Duration, maximum_reconnection_backoff: Duration, initial_connection_timeout: Duration, maximum_connection_buffer_size: usize, - use_legacy_version: bool, } impl Config { @@ -33,14 +33,12 @@ impl Config { maximum_reconnection_backoff: Duration, initial_connection_timeout: Duration, maximum_connection_buffer_size: usize, - use_legacy_version: bool, ) -> Self { Config { initial_reconnection_backoff, maximum_reconnection_backoff, initial_connection_timeout, maximum_connection_buffer_size, - use_legacy_version, } } } @@ -200,9 +198,8 @@ impl SendWithoutResponse for Client { packet: NymPacket, packet_type: PacketType, ) -> io::Result<()> { - trace!("Sending packet to {:?}", address); - let framed_packet = - FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version); + trace!("Sending packet to {address:?}"); + let framed_packet = FramedNymPacket::new(packet, packet_type); if let Some(sender) = self.conn_new.get_mut(&address) { if let Err(err) = sender.channel.try_send(framed_packet) { @@ -260,7 +257,6 @@ mod tests { maximum_reconnection_backoff: Duration::from_millis(300_000), initial_connection_timeout: Duration::from_millis(1_500), maximum_connection_buffer_size: 128, - use_legacy_version: false, }) } diff --git a/common/client-libs/mixnet-client/src/forwarder.rs b/common/client-libs/mixnet-client/src/forwarder.rs index 630cc95663..d7815be92f 100644 --- a/common/client-libs/mixnet-client/src/forwarder.rs +++ b/common/client-libs/mixnet-client/src/forwarder.rs @@ -1,77 +1,72 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::{Client, Config, SendWithoutResponse}; use futures::channel::mpsc; -use futures::StreamExt; -use log::*; +use futures::channel::mpsc::SendError; use nym_sphinx::forwarding::packet::MixPacket; -use std::time::Duration; +use tokio::time::Instant; -pub type MixForwardingSender = mpsc::UnboundedSender; -type MixForwardingReceiver = mpsc::UnboundedReceiver; - -/// A specialisation of client such that it forwards any received packets on the channel into the -/// mix network immediately, i.e. will not try to listen for any responses. -pub struct PacketForwarder { - mixnet_client: Client, - packet_receiver: MixForwardingReceiver, - shutdown: nym_task::TaskClient, +pub fn mix_forwarding_channels() -> (MixForwardingSender, MixForwardingReceiver) { + let (tx, rx) = mpsc::unbounded(); + (tx.into(), rx) } -impl PacketForwarder { - pub fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, - maximum_connection_buffer_size: usize, - use_legacy_version: bool, - shutdown: nym_task::TaskClient, - ) -> (PacketForwarder, MixForwardingSender) { - let client_config = Config::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - maximum_connection_buffer_size, - use_legacy_version, - ); +#[derive(Clone)] +pub struct MixForwardingSender(mpsc::UnboundedSender); - let (packet_sender, packet_receiver) = mpsc::unbounded(); +impl From> for MixForwardingSender { + fn from(tx: mpsc::UnboundedSender) -> Self { + MixForwardingSender(tx) + } +} - ( - PacketForwarder { - mixnet_client: Client::new(client_config), - packet_receiver, - shutdown, - }, - packet_sender, - ) +impl MixForwardingSender { + pub fn forward_packet(&self, packet: impl Into) -> Result<(), SendError> { + self.0 + .unbounded_send(packet.into()) + .map_err(|err| err.into_send_error()) } - pub async fn run(&mut self) { - while !self.shutdown.is_shutdown() { - tokio::select! { - biased; - _ = self.shutdown.recv() => { - log::trace!("PacketForwarder: Received shutdown"); - } - Some(mix_packet) = self.packet_receiver.next() => { - trace!("Going to forward packet to {}", mix_packet.next_hop()); + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.0.len() + } +} - let next_hop = mix_packet.next_hop(); - let packet_type = mix_packet.packet_type(); - let packet = mix_packet.into_packet(); - // we don't care about responses, we just want to fire packets - // as quickly as possible +pub type MixForwardingReceiver = mpsc::UnboundedReceiver; - if let Err(err) = - self.mixnet_client - .send_without_response(next_hop, packet, packet_type) - { - debug!("failed to forward the packet - {err}") - } - } - } +pub struct PacketToForward { + pub packet: MixPacket, + pub forward_delay_target: Option, +} + +impl From for PacketToForward { + fn from(packet: MixPacket) -> Self { + PacketToForward::new_no_delay(packet) + } +} + +impl From<(MixPacket, Option)> for PacketToForward { + fn from((packet, delay_until): (MixPacket, Option)) -> Self { + PacketToForward::new(packet, delay_until) + } +} + +impl From<(MixPacket, Instant)> for PacketToForward { + fn from((packet, delay_until): (MixPacket, Instant)) -> Self { + PacketToForward::new(packet, Some(delay_until)) + } +} + +impl PacketToForward { + pub fn new(packet: MixPacket, forward_delay_target: Option) -> Self { + PacketToForward { + packet, + forward_delay_target, } } + + pub fn new_no_delay(packet: MixPacket) -> Self { + Self::new(packet, None) + } } diff --git a/common/client-libs/mixnet-client/src/lib.rs b/common/client-libs/mixnet-client/src/lib.rs index a63eb5ca03..5f967e0330 100644 --- a/common/client-libs/mixnet-client/src/lib.rs +++ b/common/client-libs/mixnet-client/src/lib.rs @@ -1,7 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "client")] pub mod client; pub mod forwarder; +#[cfg(feature = "client")] pub use client::{Client, Config, SendWithoutResponse}; diff --git a/common/credential-verification/src/bandwidth_storage_manager.rs b/common/credential-verification/src/bandwidth_storage_manager.rs index 7c3136e91c..19df1dba6f 100644 --- a/common/credential-verification/src/bandwidth_storage_manager.rs +++ b/common/credential-verification/src/bandwidth_storage_manager.rs @@ -7,7 +7,7 @@ use crate::ClientBandwidth; use nym_credentials::ecash::utils::ecash_today; use nym_credentials_interface::Bandwidth; use nym_gateway_requests::ServerResponse; -use nym_gateway_storage::Storage; +use nym_gateway_storage::GatewayStorage; use si_scale::helpers::bibytes2; use time::OffsetDateTime; use tracing::*; @@ -15,17 +15,17 @@ use tracing::*; const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB #[derive(Clone)] -pub struct BandwidthStorageManager { - pub(crate) storage: S, +pub struct BandwidthStorageManager { + pub(crate) storage: GatewayStorage, pub(crate) client_bandwidth: ClientBandwidth, pub(crate) client_id: i64, pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig, pub(crate) only_coconut_credentials: bool, } -impl BandwidthStorageManager { +impl BandwidthStorageManager { pub fn new( - storage: S, + storage: GatewayStorage, client_bandwidth: ClientBandwidth, client_id: i64, bandwidth_cfg: BandwidthFlushingBehaviourConfig, diff --git a/common/credential-verification/src/ecash/credential_sender.rs b/common/credential-verification/src/ecash/credential_sender.rs index 4da2095f43..90de54ca4f 100644 --- a/common/credential-verification/src/ecash/credential_sender.rs +++ b/common/credential-verification/src/ecash/credential_sender.rs @@ -13,7 +13,6 @@ use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY; use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody}; use nym_credentials_interface::Bandwidth; use nym_credentials_interface::{ClientTicket, TicketType}; -use nym_gateway_storage::Storage; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::{ EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient, @@ -126,21 +125,18 @@ pub struct CredentialHandlerConfig { pub maximum_time_between_redemption: Duration, } -pub(crate) struct CredentialHandler { +pub(crate) struct CredentialHandler { config: CredentialHandlerConfig, multisig_threshold: f32, ticket_receiver: UnboundedReceiver, - shared_state: SharedState, + shared_state: SharedState, pending_tickets: Vec, pending_redemptions: Vec, } -impl CredentialHandler -where - St: Storage + Clone + 'static, -{ +impl CredentialHandler { async fn rebuild_pending_tickets( - shared_state: &SharedState, + shared_state: &SharedState, ) -> Result, EcashTicketError> { // 1. get all tickets that were not fully verified let unverified = shared_state.storage.get_all_unverified_tickets().await?; @@ -188,7 +184,7 @@ where } async fn rebuild_pending_votes( - shared_state: &SharedState, + shared_state: &SharedState, ) -> Result, EcashTicketError> { // 1. get all tickets that were not fully verified let unverified = shared_state.storage.get_all_unresolved_proposals().await?; @@ -259,7 +255,7 @@ where pub(crate) async fn new( config: CredentialHandlerConfig, ticket_receiver: UnboundedReceiver, - shared_state: SharedState, + shared_state: SharedState, ) -> Result { let multisig_threshold = shared_state .nyxd_client diff --git a/common/credential-verification/src/ecash/error.rs b/common/credential-verification/src/ecash/error.rs index 9e025316a7..ff85a32c3e 100644 --- a/common/credential-verification/src/ecash/error.rs +++ b/common/credential-verification/src/ecash/error.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_gateway_storage::error::StorageError; +use nym_gateway_storage::error::GatewayStorageError; use nym_validator_client::coconut::EcashApiError; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::error::NyxdError; @@ -37,7 +37,7 @@ pub enum EcashTicketError { #[error("could not handle the ecash ticket due to internal storage failure: {source}")] InternalStorageFailure { #[from] - source: StorageError, + source: GatewayStorageError, }, #[error("failed to create ticket redemption proposal: {source}")] diff --git a/common/credential-verification/src/ecash/mod.rs b/common/credential-verification/src/ecash/mod.rs index 7a2a7b133a..71d0d993ff 100644 --- a/common/credential-verification/src/ecash/mod.rs +++ b/common/credential-verification/src/ecash/mod.rs @@ -8,7 +8,7 @@ use error::EcashTicketError; use futures::channel::mpsc::{self, UnboundedSender}; use nym_credentials::CredentialSpendingData; use nym_credentials_interface::{ClientTicket, CompactEcashError, NymPayInfo, VerificationKeyAuth}; -use nym_gateway_storage::Storage; +use nym_gateway_storage::GatewayStorage; use nym_validator_client::nym_api::EpochId; use nym_validator_client::DirectSigningHttpRpcNyxdClient; use state::SharedState; @@ -23,24 +23,21 @@ mod state; pub const TIME_RANGE_SEC: i64 = 30; -pub struct EcashManager { - shared_state: SharedState, +pub struct EcashManager { + shared_state: SharedState, pk_bytes: [u8; 32], // bytes representation of a pub key representing the verifier pay_infos: Mutex>, cred_sender: UnboundedSender, } -impl EcashManager -where - S: Storage + Clone + 'static, -{ +impl EcashManager { pub async fn new( credential_handler_cfg: CredentialHandlerConfig, nyxd_client: DirectSigningHttpRpcNyxdClient, pk_bytes: [u8; 32], shutdown: nym_task::TaskClient, - storage: S, + storage: GatewayStorage, ) -> Result { let shared_state = SharedState::new(nyxd_client, storage).await?; @@ -66,7 +63,7 @@ where self.shared_state.verification_key(epoch_id).await } - pub fn storage(&self) -> &S { + pub fn storage(&self) -> &GatewayStorage { &self.shared_state.storage } diff --git a/common/credential-verification/src/ecash/state.rs b/common/credential-verification/src/ecash/state.rs index 7f5a718759..4c718c3c30 100644 --- a/common/credential-verification/src/ecash/state.rs +++ b/common/credential-verification/src/ecash/state.rs @@ -6,7 +6,7 @@ use crate::Error; use cosmwasm_std::{from_binary, CosmosMsg, WasmMsg}; use nym_credentials_interface::VerificationKeyAuth; use nym_ecash_contract_common::msg::ExecuteMsg; -use nym_gateway_storage::Storage; +use nym_gateway_storage::GatewayStorage; use nym_validator_client::coconut::all_ecash_api_clients; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::{ @@ -23,20 +23,17 @@ use tracing::{error, trace, warn}; // state shared by different subtasks dealing with credentials #[derive(Clone)] -pub(crate) struct SharedState { +pub(crate) struct SharedState { pub(crate) nyxd_client: Arc>, pub(crate) address: AccountId, pub(crate) epoch_data: Arc>>, - pub(crate) storage: S, + pub(crate) storage: GatewayStorage, } -impl SharedState -where - S: Storage + Clone, -{ +impl SharedState { pub(crate) async fn new( nyxd_client: DirectSigningHttpRpcNyxdClient, - storage: S, + storage: GatewayStorage, ) -> Result { let address = nyxd_client.address(); diff --git a/common/credential-verification/src/error.rs b/common/credential-verification/src/error.rs index 0f6b0e27e5..34dbdb53b6 100644 --- a/common/credential-verification/src/error.rs +++ b/common/credential-verification/src/error.rs @@ -39,7 +39,7 @@ pub enum Error { OutOfBandwidth { required: i64, available: i64 }, #[error("Internal gateway storage error")] - StorageError(#[from] nym_gateway_storage::error::StorageError), + StorageError(#[from] nym_gateway_storage::error::GatewayStorageError), #[error("{0}")] UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType), diff --git a/common/credential-verification/src/lib.rs b/common/credential-verification/src/lib.rs index 066953fc55..50d6db1e05 100644 --- a/common/credential-verification/src/lib.rs +++ b/common/credential-verification/src/lib.rs @@ -2,17 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use bandwidth_storage_manager::BandwidthStorageManager; +use ecash::EcashManager; +use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime}; +use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType}; +use nym_gateway_requests::models::CredentialSpendingRequest; use std::sync::Arc; use time::{Date, OffsetDateTime}; use tracing::*; -use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime}; -use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType}; -use nym_gateway_requests::models::CredentialSpendingRequest; -use nym_gateway_storage::Storage; - pub use client_bandwidth::*; -use ecash::EcashManager; pub use error::*; pub mod bandwidth_storage_manager; @@ -20,17 +18,17 @@ mod client_bandwidth; pub mod ecash; pub mod error; -pub struct CredentialVerifier { +pub struct CredentialVerifier { credential: CredentialSpendingRequest, - ecash_verifier: Arc>, - bandwidth_storage_manager: BandwidthStorageManager, + ecash_verifier: Arc, + bandwidth_storage_manager: BandwidthStorageManager, } -impl CredentialVerifier { +impl CredentialVerifier { pub fn new( credential: CredentialSpendingRequest, - ecash_verifier: Arc>, - bandwidth_storage_manager: BandwidthStorageManager, + ecash_verifier: Arc, + bandwidth_storage_manager: BandwidthStorageManager, ) -> Self { CredentialVerifier { credential, diff --git a/common/gateway-stats-storage/Cargo.toml b/common/gateway-stats-storage/Cargo.toml index 1e816e0233..0a36798e52 100644 --- a/common/gateway-stats-storage/Cargo.toml +++ b/common/gateway-stats-storage/Cargo.toml @@ -16,12 +16,14 @@ sqlx = { workspace = true, features = [ "migrate", "time", ] } +strum = { workspace = true } time = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } nym-sphinx = { path = "../nymsphinx" } nym-credentials-interface = { path = "../credentials-interface" } +nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } nym-statistics-common = { path = "../statistics" } diff --git a/common/gateway-stats-storage/src/lib.rs b/common/gateway-stats-storage/src/lib.rs index 3f94b40a87..74b45e9e7a 100644 --- a/common/gateway-stats-storage/src/lib.rs +++ b/common/gateway-stats-storage/src/lib.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use error::StatsStorageError; -use models::{ActiveSession, FinishedSession, StoredFinishedSession}; +use models::StoredFinishedSession; +use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType}; use nym_sphinx::DestinationAddressBytes; -use nym_statistics_common::gateways::SessionType; use sessions::SessionManager; use sqlx::ConnectOptions; use std::path::Path; @@ -71,8 +71,8 @@ impl PersistentStatsStorage { .session_manager .insert_finished_session( date, - session.duration.whole_milliseconds() as i64, - session.typ.to_string().into(), + session.duration.as_millis() as i64, + session.typ.to_string(), ) .await?) } @@ -126,7 +126,7 @@ impl PersistentStatsStorage { .insert_active_session( client_address.as_base58_string(), session.start, - session.typ.to_string().into(), + session.typ.to_string(), ) .await?) } @@ -138,10 +138,7 @@ impl PersistentStatsStorage { ) -> Result<(), StatsStorageError> { Ok(self .session_manager - .update_active_session_type( - client_address.as_base58_string(), - session_type.to_string().into(), - ) + .update_active_session_type(client_address.as_base58_string(), session_type.to_string()) .await?) } diff --git a/common/gateway-stats-storage/src/models.rs b/common/gateway-stats-storage/src/models.rs index f35883d28e..5553875fd4 100644 --- a/common/gateway-stats-storage/src/models.rs +++ b/common/gateway-stats-storage/src/models.rs @@ -1,10 +1,11 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_credentials_interface::TicketType; -use nym_statistics_common::gateways::SessionType; +use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType}; use sqlx::prelude::FromRow; -use time::{Duration, OffsetDateTime}; +use time::OffsetDateTime; + +pub use nym_credentials_interface::TicketType; #[derive(FromRow)] pub struct StoredFinishedSession { @@ -12,18 +13,28 @@ pub struct StoredFinishedSession { typ: String, } -impl StoredFinishedSession { - pub fn serialize(&self) -> (u64, String) { - ( - self.duration_ms as u64, //we are sure that it fits in a u64, see `fn end_at` - self.typ.clone(), - ) +impl From for FinishedSession { + fn from(value: StoredFinishedSession) -> Self { + FinishedSession { + duration: std::time::Duration::from_millis(value.duration_ms as u64), + typ: SessionType::from_string(value.typ), + } } } -pub struct FinishedSession { - pub duration: Duration, - pub typ: SessionType, +pub trait ToSessionType { + fn to_session_type(&self) -> SessionType; +} + +impl ToSessionType for TicketType { + fn to_session_type(&self) -> SessionType { + match self { + TicketType::V1MixnetEntry => SessionType::Mixnet, + TicketType::V1MixnetExit => SessionType::Mixnet, + TicketType::V1WireguardEntry => SessionType::Vpn, + TicketType::V1WireguardExit => SessionType::Vpn, + } + } } #[derive(FromRow)] @@ -32,38 +43,6 @@ pub(crate) struct StoredActiveSession { typ: String, } -pub struct ActiveSession { - pub start: OffsetDateTime, - pub typ: SessionType, -} - -impl ActiveSession { - pub fn new(start_time: OffsetDateTime) -> Self { - ActiveSession { - start: start_time, - typ: SessionType::Unknown, - } - } - - pub fn set_type(&mut self, ticket_type: TicketType) { - self.typ = ticket_type.into(); - } - - pub fn end_at(self, stop_time: OffsetDateTime) -> Option { - let session_duration = stop_time - self.start; - //ensure duration is positive to fit in a u64 - //u64::max milliseconds is 500k millenia so no overflow issue - if session_duration > Duration::ZERO { - Some(FinishedSession { - duration: session_duration, - typ: self.typ, - }) - } else { - None - } - } -} - impl From for ActiveSession { fn from(value: StoredActiveSession) -> Self { ActiveSession { diff --git a/common/gateway-storage/Cargo.toml b/common/gateway-storage/Cargo.toml index d89bac7639..bd4d8b7256 100644 --- a/common/gateway-storage/Cargo.toml +++ b/common/gateway-storage/Cargo.toml @@ -9,7 +9,6 @@ edition.workspace = true license.workspace = true [dependencies] -async-trait = { workspace = true } bincode = { workspace = true } defguard_wireguard_rs = { workspace = true } log = { workspace = true } diff --git a/common/gateway-storage/src/error.rs b/common/gateway-storage/src/error.rs index 408ec245d6..272d86b557 100644 --- a/common/gateway-storage/src/error.rs +++ b/common/gateway-storage/src/error.rs @@ -4,7 +4,7 @@ use thiserror::Error; #[derive(Error, Debug)] -pub enum StorageError { +pub enum GatewayStorageError { #[error("Database experienced an internal error: {0}")] InternalDatabaseError(#[from] sqlx::Error), diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 8d5fda0912..7b0f70a5b3 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -1,10 +1,8 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use async_trait::async_trait; use bandwidth::BandwidthManager; use clients::{ClientManager, ClientType}; -use error::StorageError; use inboxes::InboxManager; use models::{ Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage, @@ -29,237 +27,11 @@ mod shared_keys; mod tickets; mod wireguard_peers; -#[async_trait] -pub trait Storage: Send + Sync { - async fn get_mixnet_client_id( - &self, - client_address: DestinationAddressBytes, - ) -> Result; - - /// Inserts provided derived shared keys into the database. - /// If keys previously existed for the provided client, they are overwritten with the new data. - /// - /// # Arguments - /// - /// * `client_address`: base58-encoded address of the client - /// * `shared_keys`: - /// - legacy: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store. - /// - current: shared AES256-GCM-SIV keys - async fn insert_shared_keys( - &self, - client_address: DestinationAddressBytes, - shared_keys: &SharedGatewayKey, - ) -> Result; - - /// Tries to retrieve shared keys stored for the particular client. - /// - /// # Arguments - /// - /// * `client_address`: address of the client - async fn get_shared_keys( - &self, - client_address: DestinationAddressBytes, - ) -> Result, StorageError>; - - /// Removes from the database shared keys derived with the particular client. - /// - /// # Arguments - /// - /// * `client_address`: address of the client - // currently there is no code flow that causes removal (not overwriting) - // of the stored keys. However, retain the function for consistency and completion sake - #[allow(dead_code)] - async fn remove_shared_keys( - &self, - client_address: DestinationAddressBytes, - ) -> Result<(), StorageError>; - - /// Tries to retrieve a particular client. - /// - /// # Arguments - /// - /// * `client_id`: id of the client - #[allow(dead_code)] - async fn get_client(&self, client_id: i64) -> Result, StorageError>; - - /// Inserts new message to the storage for an offline client for future retrieval. - /// - /// # Arguments - /// - /// * `client_address`: address of the client - /// * `message`: raw message to store. - async fn store_message( - &self, - client_address: DestinationAddressBytes, - message: Vec, - ) -> Result<(), StorageError>; - - /// Retrieves messages stored for the particular client specified by the provided address. - /// - /// # Arguments - /// - /// * `client_address`: address of the client - /// * `start_after`: optional starting id of the messages to grab - /// - /// returns the retrieved messages alongside optional id of the last message retrieved if - /// there are more messages to retrieve. - async fn retrieve_messages( - &self, - client_address: DestinationAddressBytes, - start_after: Option, - ) -> Result<(Vec, Option), StorageError>; - - /// Removes messages with the specified ids - /// - /// # Arguments - /// - /// * `ids`: ids of the messages to remove - async fn remove_messages(&self, ids: Vec) -> Result<(), StorageError>; - - /// Creates a new bandwidth entry for the particular client. - async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError>; - - /// Set the freepass expiration date of the particular client to the provided date. - /// - /// # Arguments - /// - /// * `client_address`: address of the client - /// * `expiration`: the expiration date of the associated free pass. - async fn set_expiration( - &self, - client_id: i64, - expiration: OffsetDateTime, - ) -> Result<(), StorageError>; - - /// Reset all the bandwidth - /// - /// # Arguments - /// - /// * `client_address`: address of the client - async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError>; - - /// Tries to retrieve available bandwidth for the particular client. - async fn get_available_bandwidth( - &self, - client_id: i64, - ) -> Result, StorageError>; - - /// Increases specified client's bandwidth by the provided amount and returns the current value. - async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result; - - async fn revoke_ticket_bandwidth( - &self, - ticket_id: i64, - amount: i64, - ) -> Result<(), StorageError>; - - #[allow(dead_code)] - /// Decreases specified client's bandwidth by the provided amount and returns the current value. - async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result; - - async fn insert_epoch_signers( - &self, - epoch_id: i64, - signer_ids: Vec, - ) -> Result<(), StorageError>; - - async fn insert_received_ticket( - &self, - client_id: i64, - received_at: OffsetDateTime, - serial_number: Vec, - data: Vec, - ) -> Result; - - // note: this only checks very recent tickets that haven't yet been redeemed - // (but it's better than nothing) - /// Check if the ticket with the provided serial number if already present in the storage. - /// - /// # Arguments - /// - /// * `serial_number`: the unique serial number embedded in the ticket - async fn contains_ticket(&self, serial_number: &[u8]) -> Result; - - async fn insert_ticket_verification( - &self, - ticket_id: i64, - signer_id: i64, - verified_at: OffsetDateTime, - accepted: bool, - ) -> Result<(), StorageError>; - - async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError>; - - async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError>; - - async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError>; - - async fn get_all_verified_tickets_with_sn(&self) -> Result, StorageError>; - async fn get_all_proposed_tickets_with_sn( - &self, - proposal_id: u32, - ) -> Result, StorageError>; - - async fn insert_redemption_proposal( - &self, - tickets: &[VerifiedTicket], - proposal_id: u32, - created_at: OffsetDateTime, - ) -> Result<(), StorageError>; - - async fn clear_post_proposal_data( - &self, - proposal_id: u32, - resolved_at: OffsetDateTime, - rejected: bool, - ) -> Result<(), StorageError>; - - async fn latest_proposal(&self) -> Result, StorageError>; - - async fn get_all_unverified_tickets(&self) -> Result, StorageError>; - async fn get_all_unresolved_proposals(&self) -> Result, StorageError>; - async fn get_votes(&self, ticket_id: i64) -> Result, StorageError>; - - async fn get_signers(&self, epoch_id: i64) -> Result, StorageError>; - - /// Insert a wireguard peer in the storage. - /// - /// # Arguments - /// - /// * `peer`: wireguard peer data to be stored - /// * `with_client_id`: if the peer should have a corresponding client_id - /// (created with entry wireguard ticket) or live without one (or with an - /// exiting one), for temporary backwards compatibility. - async fn insert_wireguard_peer( - &self, - peer: &defguard_wireguard_rs::host::Peer, - with_client_id: bool, - ) -> Result, StorageError>; - - /// Tries to retrieve available bandwidth for the particular peer. - /// - /// # Arguments - /// - /// * `peer_public_key`: wireguard public key of the peer to be retrieved. - async fn get_wireguard_peer( - &self, - peer_public_key: &str, - ) -> Result, StorageError>; - - /// Retrieves all wireguard peers. - async fn get_all_wireguard_peers(&self) -> Result, StorageError>; - - /// Remove a wireguard peer from the storage. - /// - /// # Arguments - /// - /// * `peer_public_key`: wireguard public key of the peer to be removed. - async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError>; -} +pub use error::GatewayStorageError; // note that clone here is fine as upon cloning the same underlying pool will be used #[derive(Clone)] -pub struct PersistentStorage { +pub struct GatewayStorage { client_manager: ClientManager, shared_key_manager: SharedKeysManager, inbox_manager: InboxManager, @@ -268,7 +40,7 @@ pub struct PersistentStorage { wireguard_peer_manager: wireguard_peers::WgPeerManager, } -impl PersistentStorage { +impl GatewayStorage { /// Initialises `PersistentStorage` using the provided path. /// /// # Arguments @@ -278,7 +50,7 @@ impl PersistentStorage { pub async fn init + Send>( database_path: P, message_retrieval_limit: i64, - ) -> Result { + ) -> Result { debug!( "Attempting to connect to database {:?}", database_path.as_ref().as_os_str() @@ -307,7 +79,7 @@ impl PersistentStorage { } // the cloning here are cheap as connection pool is stored behind an Arc - Ok(PersistentStorage { + Ok(GatewayStorage { client_manager: clients::ClientManager::new(connection_pool.clone()), wireguard_peer_manager: wireguard_peers::WgPeerManager::new(connection_pool.clone()), shared_key_manager: SharedKeysManager::new(connection_pool.clone()), @@ -318,23 +90,22 @@ impl PersistentStorage { } } -#[async_trait] -impl Storage for PersistentStorage { - async fn get_mixnet_client_id( +impl GatewayStorage { + pub async fn get_mixnet_client_id( &self, client_address: DestinationAddressBytes, - ) -> Result { + ) -> Result { Ok(self .shared_key_manager .client_id(&client_address.as_base58_string()) .await?) } - async fn insert_shared_keys( + pub async fn insert_shared_keys( &self, client_address: DestinationAddressBytes, shared_keys: &SharedGatewayKey, - ) -> Result { + ) -> Result { let client_address_bs58 = client_address.as_base58_string(); let client_id = match self .shared_key_manager @@ -359,10 +130,10 @@ impl Storage for PersistentStorage { Ok(client_id) } - async fn get_shared_keys( + pub async fn get_shared_keys( &self, client_address: DestinationAddressBytes, - ) -> Result, StorageError> { + ) -> Result, GatewayStorageError> { let keys = self .shared_key_manager .get_shared_keys(&client_address.as_base58_string()) @@ -371,37 +142,37 @@ impl Storage for PersistentStorage { } #[allow(dead_code)] - async fn remove_shared_keys( + pub async fn remove_shared_keys( &self, client_address: DestinationAddressBytes, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { self.shared_key_manager .remove_shared_keys(&client_address.as_base58_string()) .await?; Ok(()) } - async fn get_client(&self, client_id: i64) -> Result, StorageError> { + pub async fn get_client(&self, client_id: i64) -> Result, GatewayStorageError> { let client = self.client_manager.get_client(client_id).await?; Ok(client) } - async fn store_message( + pub async fn store_message( &self, client_address: DestinationAddressBytes, message: Vec, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { self.inbox_manager .insert_message(&client_address.as_base58_string(), message) .await?; Ok(()) } - async fn retrieve_messages( + pub async fn retrieve_messages( &self, client_address: DestinationAddressBytes, start_after: Option, - ) -> Result<(Vec, Option), StorageError> { + ) -> Result<(Vec, Option), GatewayStorageError> { let messages = self .inbox_manager .get_messages(&client_address.as_base58_string(), start_after) @@ -409,87 +180,95 @@ impl Storage for PersistentStorage { Ok(messages) } - async fn remove_messages(&self, ids: Vec) -> Result<(), StorageError> { + pub async fn remove_messages(&self, ids: Vec) -> Result<(), GatewayStorageError> { for id in ids { self.inbox_manager.remove_message(id).await?; } Ok(()) } - async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError> { + pub async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError> { self.bandwidth_manager.insert_new_client(client_id).await?; Ok(()) } - async fn set_expiration( + pub async fn set_expiration( &self, client_id: i64, expiration: OffsetDateTime, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { self.bandwidth_manager .set_expiration(client_id, expiration) .await?; Ok(()) } - async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError> { + pub async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError> { self.bandwidth_manager.reset_bandwidth(client_id).await?; Ok(()) } - async fn get_available_bandwidth( + pub async fn get_available_bandwidth( &self, client_id: i64, - ) -> Result, StorageError> { + ) -> Result, GatewayStorageError> { Ok(self .bandwidth_manager .get_available_bandwidth(client_id) .await?) } - async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result { + pub async fn increase_bandwidth( + &self, + client_id: i64, + amount: i64, + ) -> Result { Ok(self .bandwidth_manager .increase_bandwidth(client_id, amount) .await?) } - async fn revoke_ticket_bandwidth( + pub async fn revoke_ticket_bandwidth( &self, ticket_id: i64, amount: i64, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { Ok(self .bandwidth_manager .revoke_ticket_bandwidth(ticket_id, amount) .await?) } - async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result { + pub async fn decrease_bandwidth( + &self, + client_id: i64, + amount: i64, + ) -> Result { Ok(self .bandwidth_manager .decrease_bandwidth(client_id, amount) .await?) } - async fn insert_epoch_signers( + pub async fn insert_epoch_signers( &self, epoch_id: i64, signer_ids: Vec, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { self.ticket_manager .insert_ecash_signers(epoch_id, signer_ids) .await?; Ok(()) } - async fn insert_received_ticket( + pub async fn insert_received_ticket( &self, client_id: i64, received_at: OffsetDateTime, serial_number: Vec, data: Vec, - ) -> Result { + ) -> Result { // technically if we crash between those 2 calls we'll have a bit of data inconsistency, // but nothing too tragic. we just won't get paid for a single ticket let ticket_id = self @@ -503,24 +282,24 @@ impl Storage for PersistentStorage { Ok(ticket_id) } - async fn contains_ticket(&self, serial_number: &[u8]) -> Result { + pub async fn contains_ticket(&self, serial_number: &[u8]) -> Result { Ok(self.ticket_manager.has_ticket_data(serial_number).await?) } - async fn insert_ticket_verification( + pub async fn insert_ticket_verification( &self, ticket_id: i64, signer_id: i64, verified_at: OffsetDateTime, accepted: bool, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { self.ticket_manager .insert_ticket_verification(ticket_id, signer_id, verified_at, accepted) .await?; Ok(()) } - async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError> { + pub async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { // set the ticket as rejected self.ticket_manager.set_rejected_ticket(ticket_id).await?; @@ -531,7 +310,7 @@ impl Storage for PersistentStorage { Ok(()) } - async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError> { + pub async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { // 1. insert into verified table self.ticket_manager .insert_verified_ticket(ticket_id) @@ -545,36 +324,41 @@ impl Storage for PersistentStorage { Ok(()) } - async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError> { + pub async fn remove_verified_ticket_binary_data( + &self, + ticket_id: i64, + ) -> Result<(), GatewayStorageError> { self.ticket_manager .remove_binary_ticket_data(ticket_id) .await?; Ok(()) } - async fn get_all_verified_tickets_with_sn(&self) -> Result, StorageError> { + pub async fn get_all_verified_tickets_with_sn( + &self, + ) -> Result, GatewayStorageError> { Ok(self .ticket_manager .get_all_verified_tickets_with_sn() .await?) } - async fn get_all_proposed_tickets_with_sn( + pub async fn get_all_proposed_tickets_with_sn( &self, proposal_id: u32, - ) -> Result, StorageError> { + ) -> Result, GatewayStorageError> { Ok(self .ticket_manager .get_all_proposed_tickets_with_sn(proposal_id as i64) .await?) } - async fn insert_redemption_proposal( + pub async fn insert_redemption_proposal( &self, tickets: &[VerifiedTicket], proposal_id: u32, created_at: OffsetDateTime, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { // if we crash between those, there might a bit of an issue. we should revisit it later // 1. insert the actual proposal @@ -592,12 +376,12 @@ impl Storage for PersistentStorage { Ok(()) } - async fn clear_post_proposal_data( + pub async fn clear_post_proposal_data( &self, proposal_id: u32, resolved_at: OffsetDateTime, rejected: bool, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { // 1. update proposal metadata self.ticket_manager .update_redemption_proposal(proposal_id as i64, resolved_at, rejected) @@ -616,11 +400,13 @@ impl Storage for PersistentStorage { Ok(()) } - async fn latest_proposal(&self) -> Result, StorageError> { + pub async fn latest_proposal(&self) -> Result, GatewayStorageError> { Ok(self.ticket_manager.get_latest_redemption_proposal().await?) } - async fn get_all_unverified_tickets(&self) -> Result, StorageError> { + pub async fn get_all_unverified_tickets( + &self, + ) -> Result, GatewayStorageError> { self.ticket_manager .get_unverified_tickets() .await? @@ -629,29 +415,37 @@ impl Storage for PersistentStorage { .collect() } - async fn get_all_unresolved_proposals(&self) -> Result, StorageError> { + pub async fn get_all_unresolved_proposals(&self) -> Result, GatewayStorageError> { Ok(self .ticket_manager .get_all_unresolved_redemption_proposal_ids() .await?) } - async fn get_votes(&self, ticket_id: i64) -> Result, StorageError> { + pub async fn get_votes(&self, ticket_id: i64) -> Result, GatewayStorageError> { Ok(self .ticket_manager .get_verification_votes(ticket_id) .await?) } - async fn get_signers(&self, epoch_id: i64) -> Result, StorageError> { + pub async fn get_signers(&self, epoch_id: i64) -> Result, GatewayStorageError> { Ok(self.ticket_manager.get_epoch_signers(epoch_id).await?) } - async fn insert_wireguard_peer( + /// Insert a wireguard peer in the storage. + /// + /// # Arguments + /// + /// * `peer`: wireguard peer data to be stored + /// * `with_client_id`: if the peer should have a corresponding client_id + /// (created with entry wireguard ticket) or live without one (or with an + /// exiting one), for temporary backwards compatibility. + pub async fn insert_wireguard_peer( &self, peer: &defguard_wireguard_rs::host::Peer, with_client_id: bool, - ) -> Result, StorageError> { + ) -> Result, GatewayStorageError> { let client_id = match self .wireguard_peer_manager .retrieve_peer(&peer.public_key.to_string()) @@ -676,10 +470,15 @@ impl Storage for PersistentStorage { Ok(client_id) } - async fn get_wireguard_peer( + /// Tries to retrieve available bandwidth for the particular peer. + /// + /// # Arguments + /// + /// * `peer_public_key`: wireguard public key of the peer to be retrieved. + pub async fn get_wireguard_peer( &self, peer_public_key: &str, - ) -> Result, StorageError> { + ) -> Result, GatewayStorageError> { let peer = self .wireguard_peer_manager .retrieve_peer(peer_public_key) @@ -687,12 +486,21 @@ impl Storage for PersistentStorage { Ok(peer) } - async fn get_all_wireguard_peers(&self) -> Result, StorageError> { + /// Retrieves all wireguard peers. + pub async fn get_all_wireguard_peers(&self) -> Result, GatewayStorageError> { let ret = self.wireguard_peer_manager.retrieve_all_peers().await?; Ok(ret) } - async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError> { + /// Remove a wireguard peer from the storage. + /// + /// # Arguments + /// + /// * `peer_public_key`: wireguard public key of the peer to be removed. + pub async fn remove_wireguard_peer( + &self, + peer_public_key: &str, + ) -> Result<(), GatewayStorageError> { self.wireguard_peer_manager .remove_peer(peer_public_key) .await?; diff --git a/common/gateway-storage/src/models.rs b/common/gateway-storage/src/models.rs index 74a2e5162d..f1bacdcaea 100644 --- a/common/gateway-storage/src/models.rs +++ b/common/gateway-storage/src/models.rs @@ -1,7 +1,7 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::error::StorageError; +use crate::error::GatewayStorageError; use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData}; use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey}; use sqlx::FromRow; @@ -24,24 +24,24 @@ pub struct PersistedSharedKeys { } impl TryFrom for SharedGatewayKey { - type Error = StorageError; + type Error = GatewayStorageError; fn try_from(value: PersistedSharedKeys) -> Result { match ( &value.derived_aes256_gcm_siv_key, &value.derived_aes128_ctr_blake3_hmac_keys_bs58, ) { - (None, None) => Err(StorageError::MissingSharedKey { + (None, None) => Err(GatewayStorageError::MissingSharedKey { id: value.client_id, }), (Some(aes256gcm_siv), _) => { let current_key = SharedSymmetricKey::try_from_bytes(aes256gcm_siv) - .map_err(|source| StorageError::DataCorruption(source.to_string()))?; + .map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?; Ok(SharedGatewayKey::Current(current_key)) } (None, Some(aes128ctr_hmac)) => { let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac) - .map_err(|source| StorageError::DataCorruption(source.to_string()))?; + .map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?; Ok(SharedGatewayKey::Legacy(legacy_key)) } } @@ -91,12 +91,12 @@ pub struct UnverifiedTicketData { } impl TryFrom for ClientTicket { - type Error = StorageError; + type Error = GatewayStorageError; fn try_from(value: UnverifiedTicketData) -> Result { Ok(ClientTicket { spending_data: CredentialSpendingData::try_from_bytes(&value.data).map_err(|_| { - StorageError::MalformedStoredTicketData { + GatewayStorageError::MalformedStoredTicketData { ticket_id: value.ticket_id, } })?, @@ -152,7 +152,7 @@ impl From for WireguardPeer { } impl TryFrom for defguard_wireguard_rs::host::Peer { - type Error = crate::error::StorageError; + type Error = crate::error::GatewayStorageError; fn try_from(value: WireguardPeer) -> Result { Ok(Self { diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index a955aba65d..8fbab39525 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -26,7 +26,7 @@ url = { workspace = true } time.workspace = true thiserror = { workspace = true } -nym-crypto = { path = "../crypto" } +nym-crypto = { path = "../crypto", features = ["asymmetric"] } nym-network-defaults = { path = "../network-defaults" } nym-sphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } nym-sphinx-addressing = { path = "../nymsphinx/addressing" } @@ -35,7 +35,5 @@ nym-sphinx-framing = { path = "../nymsphinx/framing" } nym-sphinx-params = { path = "../nymsphinx/params" } nym-sphinx-types = { path = "../nymsphinx/types" } nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-bin-common = { path = "../bin-common" } nym-metrics = { path = "../nym-metrics" } -nym-node-http-api = { path = "../../nym-node/nym-node-http-api" } + diff --git a/common/mixnode-common/src/lib.rs b/common/mixnode-common/src/lib.rs index 4ab41c6040..ffd6ff8c80 100644 --- a/common/mixnode-common/src/lib.rs +++ b/common/mixnode-common/src/lib.rs @@ -1,4 +1,4 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + pub mod packet_processor; -pub mod verloc; diff --git a/common/mixnode-common/src/verloc/error.rs b/common/mixnode-common/src/verloc/error.rs deleted file mode 100644 index 4bfe0da48e..0000000000 --- a/common/mixnode-common/src/verloc/error.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::{self, Display, Formatter}; -use std::io; - -#[derive(Debug)] -pub enum RttError { - UnexpectedEchoPacketSize, - UnexpectedReplyPacketSize, - - MalformedSenderIdentity, - - MalformedEchoSignature, - MalformedReplySignature, - - InvalidEchoSignature, - InvalidReplySignature, - - UnreachableNode(String, io::Error), - UnexpectedConnectionFailureWrite(String, io::Error), - UnexpectedConnectionFailureRead(String, io::Error), - ConnectionReadTimeout(String), - ConnectionWriteTimeout(String), - - UnexpectedReplySequence, - - ShutdownReceived, -} - -impl Display for RttError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - RttError::UnexpectedEchoPacketSize => { - write!(f, "The received echo packet had unexpected size") - } - RttError::UnexpectedReplyPacketSize => { - write!(f, "The received reply packet had unexpected size") - } - RttError::MalformedSenderIdentity => { - write!(f, "The received echo packet had malformed sender") - } - RttError::MalformedEchoSignature => { - write!(f, "The received echo packet had malformed signature") - } - RttError::MalformedReplySignature => { - write!(f, "The received reply packet had malformed signature") - } - RttError::InvalidEchoSignature => { - write!(f, "The received echo packet had invalid signature") - } - RttError::InvalidReplySignature => { - write!(f, "The received reply packet had invalid signature") - } - RttError::UnreachableNode(id, err) => { - write!(f, "Could not establish connection to {id} - {err}") - } - RttError::UnexpectedConnectionFailureWrite(id, err) => { - write!(f, "Failed to write echo packet to {id} - {err}") - } - RttError::UnexpectedConnectionFailureRead(id, err) => { - write!(f, "Failed to read reply packet from {id} - {err}") - } - RttError::ConnectionReadTimeout(id) => { - write!(f, "Timed out while trying to read reply packet from {id}") - } - RttError::ConnectionWriteTimeout(id) => { - write!(f, "Timed out while trying to write echo packet to {id}") - } - RttError::UnexpectedReplySequence => write!( - f, - "The received reply packet had an unexpected sequence number" - ), - RttError::ShutdownReceived => { - write!(f, "Shutdown signal received") - } - } - } -} - -impl std::error::Error for RttError {} diff --git a/common/mixnode-common/src/verloc/measurement.rs b/common/mixnode-common/src/verloc/measurement.rs deleted file mode 100644 index cacf72080a..0000000000 --- a/common/mixnode-common/src/verloc/measurement.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2021-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult}; -use std::mem; -use time::OffsetDateTime; - -pub(crate) trait VerlocStatsUpdateExt { - async fn start_new_measurements(&self, nodes_to_test: usize); - - async fn append_measurement_results(&self, new_data: Vec); - - async fn finish_measurements(&self); -} - -impl VerlocStatsUpdateExt for SharedVerlocStats { - async fn start_new_measurements(&self, nodes_to_test: usize) { - let mut guard = self.write().await; - guard.previous_run_data = mem::take(&mut guard.current_run_data); - guard.current_run_data.nodes_tested = nodes_to_test; - } - - async fn append_measurement_results(&self, mut new_data: Vec) { - let mut write_permit = self.write().await; - write_permit.current_run_data.results.append(&mut new_data); - // make sure the data always stays in order. - // TODO: considering the front of the results is guaranteed to be sorted, should perhaps - // a non-default sorting algorithm be used? - write_permit.current_run_data.results.sort() - } - - async fn finish_measurements(&self) { - self.write().await.current_run_data.run_finished = Some(OffsetDateTime::now_utc()) - } -} diff --git a/common/mixnode-common/src/verloc/mod.rs b/common/mixnode-common/src/verloc/mod.rs deleted file mode 100644 index 7bcea71c9d..0000000000 --- a/common/mixnode-common/src/verloc/mod.rs +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright 2021-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::verloc::listener::PacketListener; -use crate::verloc::sender::{PacketSender, TestedNode}; -use futures::stream::FuturesUnordered; -use futures::StreamExt; -use log::*; -use nym_bin_common::version_checker::{self, parse_version}; -use nym_crypto::asymmetric::identity; -use nym_network_defaults::mainnet::NYM_API; -use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult}; -use nym_task::TaskClient; -use rand::seq::SliceRandom; -use rand::thread_rng; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::Duration; -use tokio::task::JoinHandle; -use tokio::time::sleep; -use url::Url; - -use measurement::VerlocStatsUpdateExt; - -// pub use crate::verloc::measurement::{AtomicVerlocResult, Verloc, VerlocResult}; - -pub mod error; -pub(crate) mod listener; -pub(crate) mod measurement; -pub(crate) mod packet; -pub(crate) mod sender; - -// TODO: MUST BE UPDATED BEFORE ACTUAL RELEASE!! -pub const MINIMUM_NODE_VERSION: &str = "0.10.1"; - -// by default all of those are overwritten by config data from mixnodes directly -const DEFAULT_VERLOC_PORT: u16 = 1790; -const DEFAULT_PACKETS_PER_NODE: usize = 100; -const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); -const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); -const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); -const DEFAULT_BATCH_SIZE: usize = 50; -const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); -const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); - -#[derive(Clone, Debug)] -pub struct Config { - /// Minimum semver version of a node (gateway or mixnode) that is capable of replying to echo packets. - minimum_compatible_node_version: version_checker::Version, - - /// Socket address of this node on which it will be listening for the measurement packets. - listening_address: SocketAddr, - - /// Specifies number of echo packets sent to each node during a measurement run. - packets_per_node: usize, - - /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. - packet_timeout: Duration, - - /// Specifies maximum amount of time to wait for the connection to get established. - connection_timeout: Duration, - - /// Specifies delay between subsequent test packets being sent (after receiving a reply). - delay_between_packets: Duration, - - /// Specifies number of nodes being tested at once. - tested_nodes_batch_size: usize, - - /// Specifies delay between subsequent test runs. - testing_interval: Duration, - - /// Specifies delay between attempting to run the measurement again if the previous run failed - /// due to being unable to get the list of nodes. - retry_timeout: Duration, - - /// URLs to the nym apis for obtaining network topology. - nym_api_urls: Vec, -} - -impl Config { - pub fn build() -> ConfigBuilder { - ConfigBuilder::new() - } -} - -#[must_use] -pub struct ConfigBuilder(Config); - -impl ConfigBuilder { - pub fn new() -> ConfigBuilder { - Self::default() - } - - pub fn minimum_compatible_node_version(mut self, version: version_checker::Version) -> Self { - self.0.minimum_compatible_node_version = version; - self - } - - pub fn listening_address(mut self, listening_address: SocketAddr) -> Self { - self.0.listening_address = listening_address; - self - } - - pub fn packets_per_node(mut self, packets_per_node: usize) -> Self { - self.0.packets_per_node = packets_per_node; - self - } - - pub fn packet_timeout(mut self, packet_timeout: Duration) -> Self { - self.0.packet_timeout = packet_timeout; - self - } - - pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { - self.0.connection_timeout = connection_timeout; - self - } - - pub fn delay_between_packets(mut self, delay_between_packets: Duration) -> Self { - self.0.delay_between_packets = delay_between_packets; - self - } - - pub fn tested_nodes_batch_size(mut self, tested_nodes_batch_size: usize) -> Self { - self.0.tested_nodes_batch_size = tested_nodes_batch_size; - self - } - - pub fn testing_interval(mut self, testing_interval: Duration) -> Self { - self.0.testing_interval = testing_interval; - self - } - - pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self { - self.0.retry_timeout = retry_timeout; - self - } - - pub fn nym_api_urls(mut self, nym_api_urls: Vec) -> Self { - self.0.nym_api_urls = nym_api_urls; - self - } - - pub fn build(self) -> Config { - // panics here are fine as those are only ever constructed at the initial setup - assert!( - !self.0.nym_api_urls.is_empty(), - "at least one validator endpoint must be provided", - ); - self.0 - } -} - -impl Default for ConfigBuilder { - fn default() -> Self { - ConfigBuilder(Config { - minimum_compatible_node_version: parse_version(MINIMUM_NODE_VERSION).unwrap(), - listening_address: format!("[::]:{DEFAULT_VERLOC_PORT}").parse().unwrap(), - packets_per_node: DEFAULT_PACKETS_PER_NODE, - packet_timeout: DEFAULT_PACKET_TIMEOUT, - connection_timeout: DEFAULT_CONNECTION_TIMEOUT, - delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS, - tested_nodes_batch_size: DEFAULT_BATCH_SIZE, - testing_interval: DEFAULT_TESTING_INTERVAL, - retry_timeout: DEFAULT_RETRY_TIMEOUT, - nym_api_urls: vec![NYM_API.parse().expect("Invalid default API URL")], - }) - } -} - -pub struct VerlocMeasurer { - config: Config, - packet_sender: Arc, - packet_listener: Arc, - shutdown_listener: TaskClient, - - currently_used_api: usize, - - // Note: this client is only fine here as it does not maintain constant connection to the validator. - // It only does bunch of REST queries. If we update it at some point to a more sophisticated (maybe signing) client, - // then it definitely cannot be constructed here and probably will need to be passed from outside, - // as mixnodes/gateways would already be using an instance of said client. - validator_client: nym_validator_client::NymApiClient, - state: SharedVerlocStats, -} - -impl VerlocMeasurer { - pub fn new( - mut config: Config, - identity: Arc, - shutdown_listener: TaskClient, - ) -> Self { - config.nym_api_urls.shuffle(&mut thread_rng()); - - VerlocMeasurer { - packet_sender: Arc::new(PacketSender::new( - Arc::clone(&identity), - config.packets_per_node, - config.packet_timeout, - config.connection_timeout, - config.delay_between_packets, - shutdown_listener.clone().named("VerlocPacketSender"), - )), - packet_listener: Arc::new(PacketListener::new( - config.listening_address, - Arc::clone(&identity), - shutdown_listener.clone().named("VerlocPacketListener"), - )), - shutdown_listener, - currently_used_api: 0, - validator_client: nym_validator_client::NymApiClient::new( - config.nym_api_urls[0].clone(), - ), - config, - state: SharedVerlocStats::default(), - } - } - - pub fn set_shared_state(&mut self, state: SharedVerlocStats) { - self.state = state; - } - - fn use_next_nym_api(&mut self) { - if self.config.nym_api_urls.len() == 1 { - warn!("There's only a single validator API available - it won't be possible to use a different one"); - return; - } - - self.currently_used_api = (self.currently_used_api + 1) % self.config.nym_api_urls.len(); - self.validator_client - .change_nym_api(self.config.nym_api_urls[self.currently_used_api].clone()) - } - - fn start_listening(&self) -> JoinHandle<()> { - let packet_listener = Arc::clone(&self.packet_listener); - tokio::spawn(packet_listener.run()) - } - - async fn perform_measurement(&self, nodes_to_test: Vec) -> MeasurementOutcome { - log::trace!("Performing measurements"); - if nodes_to_test.is_empty() { - log::debug!("there are no nodes to measure"); - return MeasurementOutcome::Done; - } - - let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement"); - shutdown_listener.disarm(); - - for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) { - let mut chunk_results = Vec::with_capacity(chunk.len()); - - let mut measurement_chunk = chunk - .iter() - .map(|node| { - let node = *node; - let packet_sender = Arc::clone(&self.packet_sender); - // TODO: there's a potential issue here. if we make the measurement go into separate - // task, we risk biasing results with the bunch of context switches overhead - // but if we don't do it, it will take ages to complete - - // TODO: check performance difference when it's not spawned as a separate task - tokio::spawn(async move { - ( - packet_sender.send_packets_to_node(node).await, - node.identity, - ) - }) - }) - .collect::>(); - - // exhaust the results - while !shutdown_listener.is_shutdown() { - tokio::select! { - measurement_result = measurement_chunk.next() => { - let Some(result) = measurement_result else { - // if the stream has finished, it means we got everything we could have gotten - break - }; - - // if we receive JoinError it means the task failed to get executed, so either there's a bigger issue with tokio - // or there was a panic inside the task itself. In either case, we should just terminate ourselves. - let execution_result = result.expect("the measurement task panicked!"); - let identity = execution_result.1; - - let measurement_result = match execution_result.0 { - Err(err) => { - debug!("Failed to perform measurement for {identity}: {err}"); - None - } - Ok(result) => Some(result), - }; - chunk_results.push(VerlocNodeResult::new(identity, measurement_result)); - }, - _ = shutdown_listener.recv() => { - trace!("Shutdown received while measuring"); - return MeasurementOutcome::Shutdown; - } - } - } - - // update the results vector with chunks as they become available (by default every 50 nodes) - self.state.append_measurement_results(chunk_results).await; - } - - MeasurementOutcome::Done - } - - pub async fn run(&mut self) { - self.start_listening(); - - while !self.shutdown_listener.is_shutdown() { - info!("Starting verloc measurements"); - // TODO: should we also measure gateways? - - let all_mixes = match self.validator_client.get_all_described_nodes().await { - Ok(nodes) => nodes, - Err(err) => { - error!( - "failed to obtain list of mixnodes from the validator - {}. Going to attempt to use another validator API in the next run", - err - ); - self.use_next_nym_api(); - sleep(self.config.retry_timeout).await; - continue; - } - }; - if all_mixes.is_empty() { - warn!("There does not seem there are any nodes to measure...") - } - - // we only care about address and identity - let tested_nodes = all_mixes - .into_iter() - .filter(|n| n.description.declared_role.mixnode) - .filter_map(|node| { - // try to parse the identity and host - let node_identity = node.ed25519_identity_key(); - - let ip = node.description.host_information.ip_address.first()?; - let verloc_port = node.description.verloc_port(); - let verloc_host = SocketAddr::new(*ip, verloc_port); - - // TODO: possible problem in the future, this does name resolution and theoretically - // if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all - // However, maybe it's not a problem as if they are misconfigured, they will eventually be - // pushed out of the network and on top of that, verloc is done in separate task that runs - // only every few hours. - Some(TestedNode::new(verloc_host, node_identity)) - }) - .collect::>(); - - // on start of each run remove old results - self.state.start_new_measurements(tested_nodes.len()).await; - - if let MeasurementOutcome::Shutdown = self.perform_measurement(tested_nodes).await { - log::trace!("Shutting down after aborting measurements"); - break; - } - - // write current time to "run finished" field - self.state.finish_measurements().await; - - info!( - "Finished performing verloc measurements. The next one will happen in {:?}", - self.config.testing_interval - ); - - tokio::select! { - _ = sleep(self.config.testing_interval) => {}, - _ = self.shutdown_listener.recv() => { - log::trace!("Shutdown received while sleeping"); - } - } - } - - log::trace!("Verloc: Exiting"); - } -} - -enum MeasurementOutcome { - Done, - Shutdown, -} diff --git a/common/nymsphinx/framing/src/packet.rs b/common/nymsphinx/framing/src/packet.rs index 7d6dfcfa01..fec9d5285f 100644 --- a/common/nymsphinx/framing/src/packet.rs +++ b/common/nymsphinx/framing/src/packet.rs @@ -18,19 +18,13 @@ pub struct FramedNymPacket { } impl FramedNymPacket { - pub fn new(packet: NymPacket, packet_type: PacketType, use_legacy_version: bool) -> Self { + pub fn new(packet: NymPacket, packet_type: PacketType) -> Self { // If this fails somebody is using the library in a super incorrect way, because they // already managed to somehow create a sphinx packet let packet_size = PacketSize::get_type(packet.len()).unwrap(); - let use_legacy = if packet_type == PacketType::Outfox { - false - } else { - use_legacy_version - }; - let header = Header { - packet_version: PacketVersion::new(use_legacy), + packet_version: PacketVersion::new(), packet_size, packet_type, }; diff --git a/common/nymsphinx/params/src/packet_version.rs b/common/nymsphinx/params/src/packet_version.rs index c8c6ce3fc6..8639dea499 100644 --- a/common/nymsphinx/params/src/packet_version.rs +++ b/common/nymsphinx/params/src/packet_version.rs @@ -13,12 +13,8 @@ pub enum PacketVersion { } impl PacketVersion { - pub fn new(use_legacy: bool) -> Self { - if use_legacy { - Self::new_legacy() - } else { - Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER) - } + pub fn new() -> Self { + Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER) } pub fn new_legacy() -> Self { diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index 9f8b8db177..230cf62a2f 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -26,3 +26,4 @@ nym-sphinx = { path = "../nymsphinx" } nym-credentials-interface = { path = "../credentials-interface" } nym-metrics = { path = "../nym-metrics" } nym-task = { path = "../task" } + diff --git a/common/statistics/src/gateways.rs b/common/statistics/src/gateways.rs index 97d75334f1..cc617ae027 100644 --- a/common/statistics/src/gateways.rs +++ b/common/statistics/src/gateways.rs @@ -31,40 +31,12 @@ impl GatewayStatsReporter { /// Gateway Statistics events pub enum GatewayStatsEvent { /// Events in the lifecycle of an established client tunnel - SessionStatsEvent(SessionEvent), -} - -impl GatewayStatsEvent { - /// A new session between this gateway and the client remote has successfully opened - pub fn new_session_start(client: DestinationAddressBytes) -> GatewayStatsEvent { - GatewayStatsEvent::SessionStatsEvent(SessionEvent::SessionStart { - start_time: OffsetDateTime::now_utc(), - client, - }) - } - - /// An existing session with the client remote has ended - pub fn new_session_stop(client: DestinationAddressBytes) -> GatewayStatsEvent { - GatewayStatsEvent::SessionStatsEvent(SessionEvent::SessionStop { - stop_time: OffsetDateTime::now_utc(), - client, - }) - } - - /// A new ecash ticket has been added / requested - pub fn new_ecash_ticket( - client: DestinationAddressBytes, - ticket_type: TicketType, - ) -> GatewayStatsEvent { - GatewayStatsEvent::SessionStatsEvent(SessionEvent::EcashTicket { - ticket_type, - client, - }) - } + SessionStatsEvent(GatewaySessionEvent), } /// Events in the lifecycle of an established client tunnel -pub enum SessionEvent { +#[derive(Debug, Clone, Copy)] +pub enum GatewaySessionEvent { /// A new session between this gateway and the client remote has successfully opened SessionStart { /// The timestamp of the session open event @@ -88,38 +60,31 @@ pub enum SessionEvent { }, } -#[derive(PartialEq)] -pub enum SessionType { - Vpn, - Mixnet, - Unknown, -} - -impl SessionType { - pub fn to_string(&self) -> &str { - match self { - Self::Vpn => "vpn", - Self::Mixnet => "mixnet", - Self::Unknown => "unknown", +impl GatewaySessionEvent { + /// A new session between this gateway and the client remote has successfully opened + pub fn new_session_start(client: DestinationAddressBytes) -> GatewaySessionEvent { + GatewaySessionEvent::SessionStart { + start_time: OffsetDateTime::now_utc(), + client, } } - pub fn from_string(s: &str) -> Self { - match s { - "vpn" => Self::Vpn, - "mixnet" => Self::Mixnet, - _ => Self::Unknown, + /// An existing session with the client remote has ended + pub fn new_session_stop(client: DestinationAddressBytes) -> GatewaySessionEvent { + GatewaySessionEvent::SessionStop { + stop_time: OffsetDateTime::now_utc(), + client, } } -} -impl From for SessionType { - fn from(value: TicketType) -> Self { - match value { - TicketType::V1MixnetEntry => Self::Mixnet, - TicketType::V1MixnetExit => Self::Mixnet, - TicketType::V1WireguardEntry => Self::Vpn, - TicketType::V1WireguardExit => Self::Vpn, + /// A new ecash ticket has been added / requested + pub fn new_ecash_ticket( + client: DestinationAddressBytes, + ticket_type: TicketType, + ) -> GatewaySessionEvent { + GatewaySessionEvent::EcashTicket { + ticket_type, + client, } } } diff --git a/common/statistics/src/lib.rs b/common/statistics/src/lib.rs index eccf217d77..c0ee03dddc 100644 --- a/common/statistics/src/lib.rs +++ b/common/statistics/src/lib.rs @@ -12,7 +12,7 @@ #![warn(clippy::dbg_macro)] use nym_crypto::asymmetric::ed25519; -use sha2::Digest; +use sha2::{Digest, Sha256}; /// Client specific statistics interfaces and events. pub mod clients; @@ -36,3 +36,7 @@ fn generate_stats_id>(prefix: &str, id_seed: M) -> String { let output = hasher.finalize(); format!("{:x}", output) } + +pub fn hash_identifier>(identifier: M) -> String { + format!("{:x}", Sha256::digest(identifier)) +} diff --git a/common/task/src/manager.rs b/common/task/src/manager.rs index b3bc6e0232..7d8ba3a780 100644 --- a/common/task/src/manager.rs +++ b/common/task/src/manager.rs @@ -455,6 +455,10 @@ impl TaskClient { self.mode.set_should_not_signal_on_drop(); } + pub fn rearm(&mut self) { + self.mode.set_should_signal_on_drop(); + } + pub fn send_we_stopped(&mut self, err: SentError) { if self.mode.is_dummy() { return; @@ -482,7 +486,7 @@ impl Drop for TaskClient { if !self.mode.should_signal_on_drop() { self.log( Level::Trace, - "the task client is getting dropped but inststructed to not signal: this is expected during client shutdown", + "the task client is getting dropped but instructed to not signal: this is expected during client shutdown", ); return; } else { @@ -527,6 +531,14 @@ impl ClientOperatingMode { } } + fn set_should_signal_on_drop(&mut self) { + use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt}; + *self = match &self { + ListeningButDontReportHalt | Listening => Listening, + Dummy => Dummy, + }; + } + fn set_should_not_signal_on_drop(&mut self) { use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt}; *self = match &self { diff --git a/common/verloc/Cargo.toml b/common/verloc/Cargo.toml new file mode 100644 index 0000000000..e906f72dde --- /dev/null +++ b/common/verloc/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "nym-verloc" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +[dependencies] +bytes = { workspace = true } +futures = { workspace = true } +humantime = { workspace = true } +tracing = { workspace = true } +time = { workspace = true } +tokio = { workspace = true, features = ["sync", "net", "rt-multi-thread", "io-util"] } +tokio-util = { workspace = true, features = ["codec"] } +thiserror = { workspace = true } +rand = { workspace = true } +url = { workspace = true } + +nym-crypto = { path = "../crypto", features = ["asymmetric"] } +nym-task = { path = "../task" } +nym-validator-client = { path = "../client-libs/validator-client" } diff --git a/common/verloc/src/error.rs b/common/verloc/src/error.rs new file mode 100644 index 0000000000..836177d62f --- /dev/null +++ b/common/verloc/src/error.rs @@ -0,0 +1,72 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::io; +use std::net::SocketAddr; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum VerlocError { + #[error("the received echo packet had unexpected size")] + UnexpectedEchoPacketSize, + + #[error("the received reply packet had unexpected size")] + UnexpectedReplyPacketSize, + + #[error("the received echo packet had malformed sender")] + MalformedSenderIdentity, + + #[error("the received echo packet had malformed signature")] + MalformedEchoSignature, + + #[error("the received reply packet had malformed signature")] + MalformedReplySignature, + + #[error("the received echo packet had invalid signature")] + InvalidEchoSignature, + + #[error("the received reply packet had invalid signature")] + InvalidReplySignature, + + #[error("could not establish connection to {identity} on {address}: {err}")] + UnreachableNode { + identity: String, + address: SocketAddr, + #[source] + err: io::Error, + }, + + #[error("failed to write echo packet to {identity} on {address}: {err}")] + UnexpectedConnectionFailureWrite { + identity: String, + address: SocketAddr, + #[source] + err: io::Error, + }, + + #[error("failed to read reply packet from {identity} on {address}: {err}")] + UnexpectedConnectionFailureRead { + identity: String, + address: SocketAddr, + #[source] + err: io::Error, + }, + + #[error("timed out while trying to read reply packet from {identity} on {address}")] + ConnectionReadTimeout { + identity: String, + address: SocketAddr, + }, + + #[error("timed out while trying to write echo packet to {identity} on {address}")] + ConnectionWriteTimeout { + identity: String, + address: SocketAddr, + }, + + #[error("the received reply packet had an unexpected sequence number")] + UnexpectedReplySequence, + + #[error("shutdown signal received")] + ShutdownReceived, +} diff --git a/common/verloc/src/lib.rs b/common/verloc/src/lib.rs new file mode 100644 index 0000000000..2f976e06fb --- /dev/null +++ b/common/verloc/src/lib.rs @@ -0,0 +1,11 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] +#![warn(clippy::todo)] +#![warn(clippy::dbg_macro)] + +pub mod error; +pub mod measurements; +pub mod models; diff --git a/common/verloc/src/measurements/config.rs b/common/verloc/src/measurements/config.rs new file mode 100644 index 0000000000..d6a15ef3d2 --- /dev/null +++ b/common/verloc/src/measurements/config.rs @@ -0,0 +1,135 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_validator_client::UserAgent; +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::time::Duration; +use url::Url; + +// by default all of those are overwritten by config data from nym-node directly +const DEFAULT_VERLOC_PORT: u16 = 1790; +const DEFAULT_PACKETS_PER_NODE: usize = 100; +const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); +const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); +const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); +const DEFAULT_BATCH_SIZE: usize = 50; +const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); +const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); + +#[derive(Clone, Debug)] +pub struct Config { + /// Socket address of this node on which it will be listening for the measurement packets. + pub listening_address: SocketAddr, + + /// Specifies number of echo packets sent to each node during a measurement run. + pub packets_per_node: usize, + + /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. + pub packet_timeout: Duration, + + /// Specifies maximum amount of time to wait for the connection to get established. + pub connection_timeout: Duration, + + /// Specifies delay between subsequent test packets being sent (after receiving a reply). + pub delay_between_packets: Duration, + + /// Specifies number of nodes being tested at once. + pub tested_nodes_batch_size: usize, + + /// Specifies delay between subsequent test runs. + pub testing_interval: Duration, + + /// Specifies delay between attempting to run the measurement again if the previous run failed + /// due to being unable to get the list of nodes. + pub retry_timeout: Duration, + + /// URLs to the nym apis for obtaining network topology. + pub nym_api_urls: Vec, + + /// User agent used for querying the nym-api + pub user_agent: UserAgent, +} + +impl Config { + pub fn build(nym_api_urls: Vec, user_agent: impl Into) -> ConfigBuilder { + ConfigBuilder::new(nym_api_urls, user_agent) + } +} + +#[must_use] +pub struct ConfigBuilder(Config); + +impl ConfigBuilder { + pub fn new(nym_api_urls: Vec, user_agent: impl Into) -> ConfigBuilder { + ConfigBuilder(Config { + // '[::]:port' + listening_address: SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + DEFAULT_VERLOC_PORT, + ), + packets_per_node: DEFAULT_PACKETS_PER_NODE, + packet_timeout: DEFAULT_PACKET_TIMEOUT, + connection_timeout: DEFAULT_CONNECTION_TIMEOUT, + delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: DEFAULT_BATCH_SIZE, + testing_interval: DEFAULT_TESTING_INTERVAL, + retry_timeout: DEFAULT_RETRY_TIMEOUT, + nym_api_urls, + user_agent: user_agent.into(), + }) + } + + pub fn listening_address(mut self, listening_address: SocketAddr) -> Self { + self.0.listening_address = listening_address; + self + } + + pub fn packets_per_node(mut self, packets_per_node: usize) -> Self { + self.0.packets_per_node = packets_per_node; + self + } + + pub fn packet_timeout(mut self, packet_timeout: Duration) -> Self { + self.0.packet_timeout = packet_timeout; + self + } + + pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { + self.0.connection_timeout = connection_timeout; + self + } + + pub fn delay_between_packets(mut self, delay_between_packets: Duration) -> Self { + self.0.delay_between_packets = delay_between_packets; + self + } + + pub fn tested_nodes_batch_size(mut self, tested_nodes_batch_size: usize) -> Self { + self.0.tested_nodes_batch_size = tested_nodes_batch_size; + self + } + + pub fn testing_interval(mut self, testing_interval: Duration) -> Self { + self.0.testing_interval = testing_interval; + self + } + + pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self { + self.0.retry_timeout = retry_timeout; + self + } + + pub fn nym_api_urls(mut self, nym_api_urls: Vec) -> Self { + self.0.nym_api_urls = nym_api_urls; + self + } + + pub fn build(self) -> Config { + // panics here are fine as those are only ever constructed at the initial setup + assert!( + !self.0.nym_api_urls.is_empty(), + "at least one validator endpoint must be provided", + ); + self.0 + } +} diff --git a/common/mixnode-common/src/verloc/listener.rs b/common/verloc/src/measurements/listener.rs similarity index 78% rename from common/mixnode-common/src/verloc/listener.rs rename to common/verloc/src/measurements/listener.rs index aa58b56b8e..77c1739068 100644 --- a/common/mixnode-common/src/verloc/listener.rs +++ b/common/verloc/src/measurements/listener.rs @@ -1,29 +1,29 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::verloc::error::RttError; -use crate::verloc::packet::{EchoPacket, ReplyPacket}; +use crate::error::VerlocError; +use crate::measurements::packet::{EchoPacket, ReplyPacket}; use bytes::{BufMut, BytesMut}; use futures::StreamExt; -use log::*; use nym_crypto::asymmetric::identity; use nym_task::TaskClient; -use std::fmt::{Display, Formatter}; use std::net::SocketAddr; use std::sync::Arc; -use std::{fmt, io, process}; +use std::{io, process}; +use thiserror::Error; use tokio::io::AsyncWriteExt; use tokio::net::{TcpListener, TcpStream}; use tokio_util::codec::{Decoder, Encoder, Framed}; +use tracing::{debug, error, info, trace, warn}; -pub(crate) struct PacketListener { +pub struct PacketListener { address: SocketAddr, connection_handler: Arc, shutdown: TaskClient, } impl PacketListener { - pub(crate) fn new( + pub fn new( address: SocketAddr, identity: Arc, shutdown: TaskClient, @@ -37,13 +37,13 @@ impl PacketListener { } impl PacketListener { - pub(super) async fn run(self: Arc) { + pub async fn run(self: Arc) { let listener = match TcpListener::bind(self.address).await { Ok(listener) => listener, Err(err) => { error!( - "Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", - self.address, err + "Failed to bind to {}: {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", + self.address ); process::exit(1); } @@ -71,7 +71,7 @@ impl PacketListener { } }, _ = shutdown_listener.recv() => { - log::trace!("PacketListener: Received shutdown"); + trace!("PacketListener: Received shutdown"); } } } @@ -137,28 +137,18 @@ impl ConnectionHandler { } } -#[derive(Debug)] +#[derive(Debug, Error)] enum EchoPacketCodecError { - IoError(io::Error), - PacketRecoveryError(RttError), + #[error("encountered io error {0}")] + IoError(#[from] io::Error), + + #[error("failed to correctly decode an echo packet: {0}")] + PacketRecoveryError(Box), } -impl Display for EchoPacketCodecError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - EchoPacketCodecError::IoError(err) => write!(f, "encountered io error - {err}"), - EchoPacketCodecError::PacketRecoveryError(err) => { - write!(f, "failed to correctly decode an echo packet - {err}") - } - } - } -} - -impl std::error::Error for EchoPacketCodecError {} - -impl From for EchoPacketCodecError { - fn from(err: io::Error) -> Self { - EchoPacketCodecError::IoError(err) +impl From for EchoPacketCodecError { + fn from(value: VerlocError) -> Self { + EchoPacketCodecError::PacketRecoveryError(Box::new(value)) } } @@ -188,10 +178,7 @@ impl Decoder for EchoPacketCodec { let packet_bytes = src.split_to(EchoPacket::SIZE); - let echo_packet = match EchoPacket::try_from_bytes(&packet_bytes) { - Ok(packet) => packet, - Err(err) => return Err(EchoPacketCodecError::PacketRecoveryError(err)), - }; + let echo_packet = EchoPacket::try_from_bytes(&packet_bytes)?; // reserve enough bytes for the next frame src.reserve(EchoPacket::SIZE); diff --git a/common/verloc/src/measurements/measurer.rs b/common/verloc/src/measurements/measurer.rs new file mode 100644 index 0000000000..e56f35d086 --- /dev/null +++ b/common/verloc/src/measurements/measurer.rs @@ -0,0 +1,225 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::measurements::metrics::SharedVerlocStats; +use crate::measurements::sender::TestedNode; +use crate::measurements::{Config, PacketListener, PacketSender}; +use crate::models::VerlocNodeResult; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use nym_crypto::asymmetric::identity; +use nym_task::TaskClient; +use nym_validator_client::models::NymNodeDescription; +use nym_validator_client::NymApiClient; +use rand::prelude::SliceRandom; +use rand::thread_rng; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::task::JoinHandle; +use tokio::time::sleep; +use tracing::{debug, error, info, trace, warn}; + +pub struct VerlocMeasurer { + config: Config, + packet_sender: Arc, + packet_listener: Arc, + shutdown_listener: TaskClient, + state: SharedVerlocStats, +} + +impl VerlocMeasurer { + pub fn new( + config: Config, + identity: Arc, + shutdown_listener: TaskClient, + ) -> Self { + VerlocMeasurer { + packet_sender: Arc::new(PacketSender::new( + Arc::clone(&identity), + config.packets_per_node, + config.packet_timeout, + config.connection_timeout, + config.delay_between_packets, + shutdown_listener.clone().named("VerlocPacketSender"), + )), + packet_listener: Arc::new(PacketListener::new( + config.listening_address, + Arc::clone(&identity), + shutdown_listener.clone().named("VerlocPacketListener"), + )), + shutdown_listener, + config, + state: SharedVerlocStats::default(), + } + } + + pub fn set_shared_state(&mut self, state: SharedVerlocStats) { + self.state = state; + } + + fn start_listening(&self) -> JoinHandle<()> { + let packet_listener = Arc::clone(&self.packet_listener); + tokio::spawn(packet_listener.run()) + } + + async fn perform_measurement(&self, nodes_to_test: Vec) -> MeasurementOutcome { + trace!("Performing measurements"); + if nodes_to_test.is_empty() { + debug!("there are no nodes to measure"); + return MeasurementOutcome::Done; + } + + let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement"); + shutdown_listener.disarm(); + + for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) { + let mut chunk_results = Vec::with_capacity(chunk.len()); + + let mut measurement_chunk = chunk + .iter() + .map(|node| { + let node = *node; + let packet_sender = Arc::clone(&self.packet_sender); + // TODO: there's a potential issue here. if we make the measurement go into separate + // task, we risk biasing results with the bunch of context switches overhead + // but if we don't do it, it will take ages to complete + + // TODO: check performance difference when it's not spawned as a separate task + tokio::spawn(async move { + ( + packet_sender.send_packets_to_node(node).await, + node.identity, + ) + }) + }) + .collect::>(); + + // exhaust the results + while !shutdown_listener.is_shutdown() { + tokio::select! { + measurement_result = measurement_chunk.next() => { + let Some(result) = measurement_result else { + // if the stream has finished, it means we got everything we could have gotten + break + }; + + // if we receive JoinError it means the task failed to get executed, so either there's a bigger issue with tokio + // or there was a panic inside the task itself. In either case, we should just terminate ourselves. + let Ok(execution_result) = result else { + error!("the verloc measurer has panicked!"); + continue + }; + let identity = execution_result.1; + + let measurement_result = match execution_result.0 { + Err(err) => { + debug!("Failed to perform measurement for {identity}: {err}"); + None + } + Ok(result) => Some(result), + }; + chunk_results.push(VerlocNodeResult::new(identity, measurement_result)); + }, + _ = shutdown_listener.recv() => { + trace!("Shutdown received while measuring"); + return MeasurementOutcome::Shutdown; + } + } + } + + // update the results vector with chunks as they become available (by default every 50 nodes) + self.state.append_measurement_results(chunk_results).await; + } + + MeasurementOutcome::Done + } + + async fn get_list_of_nodes(&self) -> Option> { + let mut api_endpoints = self.config.nym_api_urls.clone(); + api_endpoints.shuffle(&mut thread_rng()); + for api_endpoint in api_endpoints { + let client = NymApiClient::new_with_user_agent( + api_endpoint.clone(), + self.config.user_agent.clone(), + ); + match client.get_all_described_nodes().await { + Ok(res) => return Some(res), + Err(err) => { + warn!("failed to get described nodes from {api_endpoint}: {err}") + } + } + } + None + } + + pub async fn run(&mut self) { + self.start_listening(); + + while !self.shutdown_listener.is_shutdown() { + info!("Starting verloc measurements"); + // TODO: should we also measure gateways? + + let Some(all_nodes) = self.get_list_of_nodes().await else { + error!("failed to obtain list of all nodes from any available api endpoint"); + sleep(self.config.retry_timeout).await; + continue; + }; + + if all_nodes.is_empty() { + warn!("it does not seem there are any nodes to measure..."); + sleep(self.config.retry_timeout).await; + continue; + } + + // we only care about address and identity + let tested_nodes = all_nodes + .into_iter() + .filter_map(|node| { + // try to parse the identity and host + let node_identity = node.ed25519_identity_key(); + + let ip = node.description.host_information.ip_address.first()?; + let verloc_port = node.description.verloc_port(); + let verloc_host = SocketAddr::new(*ip, verloc_port); + + // TODO: possible problem in the future, this does name resolution and theoretically + // if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all + // However, maybe it's not a problem as if they are misconfigured, they will eventually be + // pushed out of the network and on top of that, verloc is done in separate task that runs + // only every few hours. + Some(TestedNode::new(verloc_host, node_identity)) + }) + .collect::>(); + + // on start of each run remove old results + self.state.start_new_measurements(tested_nodes.len()).await; + + if let MeasurementOutcome::Shutdown = self.perform_measurement(tested_nodes).await { + trace!("Shutting down after aborting measurements"); + break; + } + + // write current time to "run finished" field + self.state.finish_measurements().await; + + info!( + "Finished performing verloc measurements. The next one will happen in {:?}", + self.config.testing_interval + ); + + tokio::select! { + _ = sleep(self.config.testing_interval) => {}, + _ = self.shutdown_listener.recv() => { + trace!("Shutdown received while sleeping"); + } + } + } + + trace!("Verloc: Exiting"); + } +} + +enum MeasurementOutcome { + Done, + Shutdown, +} diff --git a/common/verloc/src/measurements/metrics.rs b/common/verloc/src/measurements/metrics.rs new file mode 100644 index 0000000000..210607696e --- /dev/null +++ b/common/verloc/src/measurements/metrics.rs @@ -0,0 +1,50 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::{VerlocNodeResult, VerlocResultData}; +use std::mem; +use std::sync::Arc; +use time::OffsetDateTime; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Clone, Debug, Default)] +pub struct SharedVerlocStats { + inner: Arc>, +} + +impl SharedVerlocStats { + pub(crate) async fn start_new_measurements(&self, nodes_to_test: usize) { + let mut guard = self.write().await; + guard.previous_run_data = mem::take(&mut guard.current_run_data); + guard.current_run_data.nodes_tested = nodes_to_test; + } + + pub(crate) async fn append_measurement_results(&self, mut new_data: Vec) { + let mut write_permit = self.write().await; + write_permit.current_run_data.results.append(&mut new_data); + // make sure the data always stays in order. + // TODO: considering the front of the results is guaranteed to be sorted, should perhaps + // a non-default sorting algorithm be used? + write_permit.current_run_data.results.sort() + } + + pub(crate) async fn finish_measurements(&self) { + self.write().await.current_run_data.run_finished = Some(OffsetDateTime::now_utc()) + } +} + +#[derive(Clone, Debug, Default)] +pub struct VerlocStatsState { + pub current_run_data: VerlocResultData, + pub previous_run_data: VerlocResultData, +} + +impl SharedVerlocStats { + pub async fn read(&self) -> RwLockReadGuard<'_, VerlocStatsState> { + self.inner.read().await + } + + pub async fn write(&self) -> RwLockWriteGuard<'_, VerlocStatsState> { + self.inner.write().await + } +} diff --git a/common/verloc/src/measurements/mod.rs b/common/verloc/src/measurements/mod.rs new file mode 100644 index 0000000000..303fab2240 --- /dev/null +++ b/common/verloc/src/measurements/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod config; +pub mod listener; +pub mod measurer; +pub mod metrics; +pub mod packet; +pub mod sender; + +pub use config::{Config, ConfigBuilder}; +pub use listener::PacketListener; +pub use measurer::VerlocMeasurer; +pub use metrics::{SharedVerlocStats, VerlocStatsState}; +pub use packet::{EchoPacket, ReplyPacket}; +pub use sender::PacketSender; diff --git a/common/mixnode-common/src/verloc/packet.rs b/common/verloc/src/measurements/packet.rs similarity index 61% rename from common/mixnode-common/src/verloc/packet.rs rename to common/verloc/src/measurements/packet.rs index c5729904b7..2a918b007a 100644 --- a/common/mixnode-common/src/verloc/packet.rs +++ b/common/verloc/src/measurements/packet.rs @@ -1,20 +1,20 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::verloc::error::RttError; -use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; +use crate::error::VerlocError; +use nym_crypto::asymmetric::ed25519::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; -pub(crate) struct EchoPacket { +pub struct EchoPacket { sequence_number: u64, - sender: identity::PublicKey, + sender: ed25519::PublicKey, - signature: identity::Signature, + signature: ed25519::Signature, } impl EchoPacket { pub(crate) const SIZE: usize = 8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH; - pub(crate) fn new(sequence_number: u64, keys: &identity::KeyPair) -> Self { + pub(crate) fn new(sequence_number: u64, keys: &ed25519::KeyPair) -> Self { let bytes_to_sign = sequence_number .to_be_bytes() .iter() @@ -42,20 +42,22 @@ impl EchoPacket { .collect() } - pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result { + pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result { if bytes.len() != Self::SIZE { - return Err(RttError::UnexpectedEchoPacketSize); + return Err(VerlocError::UnexpectedEchoPacketSize); } + // SAFETY: we have ensured our packet has correct size + #[allow(clippy::unwrap_used)] let sequence_number = u64::from_be_bytes(bytes[..8].try_into().unwrap()); - let sender = identity::PublicKey::from_bytes(&bytes[8..8 + PUBLIC_KEY_LENGTH]) - .map_err(|_| RttError::MalformedSenderIdentity)?; - let signature = identity::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH..]) - .map_err(|_| RttError::MalformedEchoSignature)?; + let sender = ed25519::PublicKey::from_bytes(&bytes[8..8 + PUBLIC_KEY_LENGTH]) + .map_err(|_| VerlocError::MalformedSenderIdentity)?; + let signature = ed25519::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH..]) + .map_err(|_| VerlocError::MalformedEchoSignature)?; sender .verify(&bytes[..Self::SIZE - SIGNATURE_LENGTH], &signature) - .map_err(|_| RttError::InvalidEchoSignature)?; + .map_err(|_| VerlocError::InvalidEchoSignature)?; Ok(EchoPacket { sequence_number, @@ -64,7 +66,7 @@ impl EchoPacket { }) } - pub(crate) fn construct_reply(self, private_key: &identity::PrivateKey) -> ReplyPacket { + pub(crate) fn construct_reply(self, private_key: &ed25519::PrivateKey) -> ReplyPacket { let bytes = self.to_bytes(); let signature = private_key.sign(bytes); ReplyPacket { @@ -74,9 +76,9 @@ impl EchoPacket { } } -pub(crate) struct ReplyPacket { +pub struct ReplyPacket { base_packet: EchoPacket, - signature: identity::Signature, + signature: ed25519::Signature, } impl ReplyPacket { @@ -96,21 +98,21 @@ impl ReplyPacket { pub(crate) fn try_from_bytes( bytes: &[u8], - remote_identity: &identity::PublicKey, - ) -> Result { + remote_ed25519: &ed25519::PublicKey, + ) -> Result { if bytes.len() != Self::SIZE { - return Err(RttError::UnexpectedReplyPacketSize); + return Err(VerlocError::UnexpectedReplyPacketSize); } let base_packet = EchoPacket::try_from_bytes(&bytes[..8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH])?; let signature = - identity::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH..]) - .map_err(|_| RttError::MalformedReplySignature)?; + ed25519::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH..]) + .map_err(|_| VerlocError::MalformedReplySignature)?; - remote_identity + remote_ed25519 .verify(&bytes[..Self::SIZE - SIGNATURE_LENGTH], &signature) - .map_err(|_| RttError::InvalidReplySignature)?; + .map_err(|_| VerlocError::InvalidReplySignature)?; Ok(ReplyPacket { base_packet, diff --git a/common/mixnode-common/src/verloc/sender.rs b/common/verloc/src/measurements/sender.rs similarity index 68% rename from common/mixnode-common/src/verloc/sender.rs rename to common/verloc/src/measurements/sender.rs index 5887a22a6e..292cd8d8c0 100644 --- a/common/mixnode-common/src/verloc/sender.rs +++ b/common/verloc/src/measurements/sender.rs @@ -1,11 +1,10 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::verloc::error::RttError; -use crate::verloc::packet::{EchoPacket, ReplyPacket}; -use log::*; -use nym_crypto::asymmetric::identity; -use nym_node_http_api::state::metrics::VerlocMeasurement; +use crate::error::VerlocError; +use crate::measurements::packet::{EchoPacket, ReplyPacket}; +use crate::models::VerlocMeasurement; +use nym_crypto::asymmetric::ed25519; use nym_task::TaskClient; use rand::{thread_rng, Rng}; use std::net::SocketAddr; @@ -15,15 +14,16 @@ use std::{fmt, io}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::time::sleep; +use tracing::{debug, trace}; #[derive(Copy, Clone)] pub(crate) struct TestedNode { pub(crate) address: SocketAddr, - pub(crate) identity: identity::PublicKey, + pub(crate) identity: ed25519::PublicKey, } impl TestedNode { - pub(crate) fn new(address: SocketAddr, identity: identity::PublicKey) -> Self { + pub(crate) fn new(address: SocketAddr, identity: ed25519::PublicKey) -> Self { TestedNode { address, identity } } } @@ -38,8 +38,8 @@ impl fmt::Display for TestedNode { } } -pub(crate) struct PacketSender { - identity: Arc, +pub struct PacketSender { + identity: Arc, // timeout for receiving before sending new one packets_per_node: usize, packet_timeout: Duration, @@ -49,8 +49,8 @@ pub(crate) struct PacketSender { } impl PacketSender { - pub(super) fn new( - identity: Arc, + pub fn new( + identity: Arc, packets_per_node: usize, packet_timeout: Duration, connection_timeout: Duration, @@ -82,7 +82,7 @@ impl PacketSender { pub(super) async fn send_packets_to_node( self: Arc, tested_node: TestedNode, - ) -> Result { + ) -> Result { let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string()); shutdown_listener.disarm(); @@ -93,16 +93,18 @@ impl PacketSender { .await { Err(_timeout) => { - return Err(RttError::UnreachableNode( - tested_node.identity.to_base58_string(), - io::ErrorKind::TimedOut.into(), - )) + return Err(VerlocError::UnreachableNode { + identity: tested_node.identity.to_string(), + err: io::ErrorKind::TimedOut.into(), + address: tested_node.address, + }) } Ok(Err(err)) => { - return Err(RttError::UnreachableNode( - tested_node.identity.to_base58_string(), + return Err(VerlocError::UnreachableNode { + identity: tested_node.identity.to_string(), err, - )) + address: tested_node.address, + }) } Ok(Ok(conn)) => conn, }; @@ -121,33 +123,34 @@ impl PacketSender { write = tokio::time::timeout(self.packet_timeout, conn.write_all(packet_bytes.as_ref())) => { match write { Err(_timeout) => { - let identity_string = tested_node.identity.to_base58_string(); + let identity = tested_node.identity; debug!( - "failed to write echo packet to {} within {:?}. Stopping the test.", - identity_string, self.packet_timeout + "failed to write echo packet to {identity} within {:?}. Stopping the test.", + self.packet_timeout ); - return Err(RttError::UnexpectedConnectionFailureWrite( - identity_string, - io::ErrorKind::TimedOut.into(), - )); + return Err(VerlocError::UnexpectedConnectionFailureWrite{ + identity: identity.to_string(), + err:io::ErrorKind::TimedOut.into(), + address: tested_node.address + }); } Ok(Err(err)) => { - let identity_string = tested_node.identity.to_base58_string(); + let identity = tested_node.identity; debug!( - "failed to write echo packet to {} - {}. Stopping the test.", - identity_string, err + "failed to write echo packet to {identity}: {err}. Stopping the test.", ); - return Err(RttError::UnexpectedConnectionFailureWrite( - identity_string, + return Err(VerlocError::UnexpectedConnectionFailureWrite{ + identity: identity.to_string(), err, - )); + address: tested_node.address + }); } Ok(Ok(_)) => {} } }, _ = shutdown_listener.recv() => { - log::trace!("PacketSender: Received shutdown while sending"); - return Err(RttError::ShutdownReceived); + trace!("PacketSender: Received shutdown while sending"); + return Err(VerlocError::ShutdownReceived); }, } @@ -156,15 +159,15 @@ impl PacketSender { let reply_packet_future = async { let mut buf = [0u8; ReplyPacket::SIZE]; if let Err(err) = conn.read_exact(&mut buf).await { + let identity = tested_node.identity; debug!( - "failed to read reply packet from {} - {}. Stopping the test.", - tested_node.identity.to_base58_string(), - err + "failed to read reply packet from {identity}: {err}. Stopping the test.", ); - return Err(RttError::UnexpectedConnectionFailureRead( - tested_node.identity.to_base58_string(), + return Err(VerlocError::UnexpectedConnectionFailureRead { + identity: identity.to_string(), err, - )); + address: tested_node.address, + }); } ReplyPacket::try_from_bytes(&buf, &tested_node.identity) }; @@ -180,15 +183,16 @@ impl PacketSender { "failed to receive reply to our echo packet within {:?}. Stopping the test", self.packet_timeout ); - return Err(RttError::ConnectionReadTimeout( - tested_node.identity.to_base58_string(), - )); + return Err(VerlocError::ConnectionReadTimeout{ + identity: tested_node.identity.to_string(), + address: tested_node.address + }); } } }, _ = shutdown_listener.recv() => { - log::trace!("PacketSender: Received shutdown while waiting for reply"); - return Err(RttError::ShutdownReceived); + trace!("PacketSender: Received shutdown while waiting for reply"); + return Err(VerlocError::ShutdownReceived); } }; @@ -198,7 +202,7 @@ impl PacketSender { // we have received the previous one if reply_packet.base_sequence_number() != seq { debug!("Received reply packet with invalid sequence number! Got {} expected {}. Stopping the test", reply_packet.base_sequence_number(), seq); - return Err(RttError::UnexpectedReplySequence); + return Err(VerlocError::UnexpectedReplySequence); } let time_taken = tokio::time::Instant::now().duration_since(start); diff --git a/common/verloc/src/models.rs b/common/verloc/src/models.rs new file mode 100644 index 0000000000..0bedab812c --- /dev/null +++ b/common/verloc/src/models.rs @@ -0,0 +1,225 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_crypto::asymmetric::ed25519::{self}; +use std::cmp::Ordering; +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::time::Duration; +use time::OffsetDateTime; + +#[derive(Debug, Clone)] +pub struct VerlocResultData { + pub nodes_tested: usize, + + pub run_started: OffsetDateTime, + + pub run_finished: Option, + + pub results: Vec, +} + +impl Default for VerlocResultData { + fn default() -> Self { + VerlocResultData { + nodes_tested: 0, + run_started: OffsetDateTime::now_utc(), + run_finished: None, + results: vec![], + } + } +} + +impl VerlocResultData { + pub fn run_finished(&self) -> bool { + self.run_finished.is_some() + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct VerlocNodeResult { + pub node_identity: ed25519::PublicKey, + + pub latest_measurement: Option, +} + +impl VerlocNodeResult { + pub fn new( + node_identity: ed25519::PublicKey, + latest_measurement: Option, + ) -> Self { + VerlocNodeResult { + node_identity, + latest_measurement, + } + } +} + +impl PartialOrd for VerlocNodeResult { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VerlocNodeResult { + fn cmp(&self, other: &Self) -> Ordering { + // if both have measurement, compare measurements + // then if only one have measurement, prefer that one + // completely ignore identity as it makes no sense to order by it + if let Some(self_measurement) = &self.latest_measurement { + if let Some(other_measurement) = &other.latest_measurement { + self_measurement.cmp(other_measurement) + } else { + Ordering::Less + } + } else if other.latest_measurement.is_some() { + Ordering::Greater + } else { + Ordering::Equal + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct VerlocMeasurement { + /// Minimum RTT duration it took to receive an echo packet. + pub minimum: Duration, + + /// Average RTT duration it took to receive the echo packets. + pub mean: Duration, + + /// Maximum RTT duration it took to receive an echo packet. + pub maximum: Duration, + + /// The standard deviation of the RTT duration it took to receive the echo packets. + pub standard_deviation: Duration, +} + +impl VerlocMeasurement { + pub fn new(raw_results: &[Duration]) -> Self { + let minimum = raw_results.iter().min().copied().unwrap_or_default(); + let maximum = raw_results.iter().max().copied().unwrap_or_default(); + + let mean = Self::duration_mean(raw_results); + let standard_deviation = Self::duration_standard_deviation(raw_results, mean); + + VerlocMeasurement { + minimum, + mean, + maximum, + standard_deviation, + } + } + + fn duration_mean(data: &[Duration]) -> Duration { + if data.is_empty() { + return Default::default(); + } + + let sum = data.iter().sum::(); + let count = data.len() as u32; + + sum / count + } + + fn duration_standard_deviation(data: &[Duration], mean: Duration) -> Duration { + if data.is_empty() { + return Default::default(); + } + + let variance_micros = data + .iter() + .map(|&value| { + // make sure we don't underflow + let diff = if mean > value { + mean - value + } else { + value - mean + }; + // we don't need nanos precision + let diff_micros = diff.as_micros(); + diff_micros * diff_micros + }) + .sum::() + / data.len() as u128; + + // we shouldn't really overflow as our differences shouldn't be larger than couple seconds at the worst possible case scenario + let std_deviation_micros = (variance_micros as f64).sqrt() as u64; + Duration::from_micros(std_deviation_micros) + } +} + +impl Display for VerlocMeasurement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "rtt min/avg/max/mdev = {} / {} / {} / {}", + humantime::format_duration(self.minimum), + humantime::format_duration(self.mean), + humantime::format_duration(self.maximum), + humantime::format_duration(self.standard_deviation) + ) + } +} + +impl PartialOrd for VerlocMeasurement { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VerlocMeasurement { + fn cmp(&self, other: &Self) -> Ordering { + // minimum value is most important, then look at standard deviation, then mean and finally maximum + let min_cmp = self.minimum.cmp(&other.minimum); + if min_cmp != Ordering::Equal { + return min_cmp; + } + let std_dev_cmp = self.standard_deviation.cmp(&other.standard_deviation); + if std_dev_cmp != Ordering::Equal { + return std_dev_cmp; + } + let std_dev_cmp = self.mean.cmp(&other.mean); + if std_dev_cmp != Ordering::Equal { + return std_dev_cmp; + } + self.maximum.cmp(&other.maximum) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sorting_vec_of_verlocs() { + let some_identity = + ed25519::PublicKey::from_base58_string("Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt") + .unwrap(); + let no_measurement = VerlocNodeResult::new(some_identity, None); + let low_min = VerlocNodeResult::new( + some_identity, + Some(VerlocMeasurement { + minimum: Duration::from_millis(42), + mean: Duration::from_millis(43), + maximum: Duration::from_millis(44), + standard_deviation: Duration::from_millis(45), + }), + ); + let higher_min = VerlocNodeResult::new( + some_identity, + Some(VerlocMeasurement { + minimum: Duration::from_millis(420), + mean: Duration::from_millis(430), + maximum: Duration::from_millis(440), + standard_deviation: Duration::from_millis(450), + }), + ); + + let mut vec_verloc = vec![no_measurement, low_min, no_measurement, higher_min]; + vec_verloc.sort(); + + let expected_sorted = vec![low_min, higher_min, no_measurement, no_measurement]; + assert_eq!(expected_sorted, vec_verloc); + } +} diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index c594e6b891..df333cab82 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -27,6 +27,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } tokio-stream = { workspace = true } time = { workspace = true } +tracing = { workspace = true } nym-authenticator-requests = { path = "../authenticator-requests" } nym-credential-verification = { path = "../credential-verification" } diff --git a/common/wireguard/src/error.rs b/common/wireguard/src/error.rs index 65a7269952..d2fd0e7956 100644 --- a/common/wireguard/src/error.rs +++ b/common/wireguard/src/error.rs @@ -16,7 +16,7 @@ pub enum Error { MissingClientBandwidthEntry, #[error("{0}")] - GatewayStorage(#[from] nym_gateway_storage::error::StorageError), + GatewayStorage(#[from] nym_gateway_storage::error::GatewayStorageError), #[error("{0}")] SystemTime(#[from] std::time::SystemTimeError), diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 48f3c1f8ed..7b5f190193 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -7,16 +7,18 @@ // #![warn(clippy::unwrap_used)] use defguard_wireguard_rs::WGApi; -#[cfg(target_os = "linux")] -use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; use nym_crypto::asymmetric::encryption::KeyPair; -#[cfg(target_os = "linux")] -use nym_network_defaults::constants::WG_TUN_BASE_NAME; use nym_wireguard_types::Config; use peer_controller::PeerControlRequest; use std::sync::Arc; use tokio::sync::mpsc::{self, Receiver, Sender}; +#[cfg(target_os = "linux")] +use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; + +#[cfg(target_os = "linux")] +use nym_network_defaults::constants::WG_TUN_BASE_NAME; + pub(crate) mod error; pub mod peer_controller; pub mod peer_handle; @@ -81,8 +83,8 @@ pub struct WireguardData { /// Start wireguard device #[cfg(target_os = "linux")] -pub async fn start_wireguard( - storage: St, +pub async fn start_wireguard( + storage: nym_gateway_storage::GatewayStorage, all_peers: Vec, task_client: nym_task::TaskClient, wireguard_data: WireguardData, @@ -93,6 +95,7 @@ pub async fn start_wireguard use peer_controller::PeerController; use std::collections::HashMap; use tokio::sync::RwLock; + use tracing::info; let ifname = String::from(WG_TUN_BASE_NAME); let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), false)?; @@ -121,6 +124,7 @@ pub async fn start_wireguard .await?; peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone())); } + wg_api.create_interface()?; let interface_config = InterfaceConfiguration { name: ifname.clone(), @@ -130,6 +134,11 @@ pub async fn start_wireguard peers, mtu: None, }; + info!( + "attempting to configure wireguard interface '{ifname}': address={}, port={}", + interface_config.address, interface_config.port + ); + wg_api.configure_interface(&interface_config)?; std::process::Command::new("ip") .args([ diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index 321b15462f..5f2cf6399f 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -7,6 +7,7 @@ use defguard_wireguard_rs::{ WireguardInterfaceApi, }; use futures::channel::oneshot; +use log::info; use nym_authenticator_requests::latest::registration::{ RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY, }; @@ -14,7 +15,7 @@ use nym_credential_verification::{ bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, ClientBandwidth, }; -use nym_gateway_storage::Storage; +use nym_gateway_storage::GatewayStorage; use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK; use std::{collections::HashMap, sync::Arc}; use tokio::sync::{mpsc, RwLock}; @@ -62,24 +63,24 @@ pub struct QueryBandwidthControlResponse { pub bandwidth_data: Option, } -pub struct PeerController { - storage: St, +pub struct PeerController { + storage: GatewayStorage, // used to receive commands from individual handles too request_tx: mpsc::Sender, request_rx: mpsc::Receiver, wg_api: Arc, host_information: Arc>, - bw_storage_managers: HashMap>>, + bw_storage_managers: HashMap>, timeout_check_interval: IntervalStream, task_client: nym_task::TaskClient, } -impl PeerController { +impl PeerController { pub fn new( - storage: St, + storage: GatewayStorage, wg_api: Arc, initial_host_information: Host, - bw_storage_managers: HashMap>, Peer)>, + bw_storage_managers: HashMap, Peer)>, request_tx: mpsc::Sender, request_rx: mpsc::Receiver, task_client: nym_task::TaskClient, @@ -158,9 +159,9 @@ impl PeerController { } pub async fn generate_bandwidth_manager( - storage: St, + storage: GatewayStorage, public_key: &Key, - ) -> Result>, Error> { + ) -> Result, Error> { if let Some(client_id) = storage .get_wireguard_peer(&public_key.to_string()) .await? @@ -257,6 +258,7 @@ impl PeerController { } pub async fn run(&mut self) { + info!("started wireguard peer controller"); loop { tokio::select! { _ = self.timeout_check_interval.next() => { diff --git a/common/wireguard/src/peer_handle.rs b/common/wireguard/src/peer_handle.rs index a6e5f9e502..c7c8709bbc 100644 --- a/common/wireguard/src/peer_handle.rs +++ b/common/wireguard/src/peer_handle.rs @@ -10,7 +10,6 @@ use futures::channel::oneshot; use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY; use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager; use nym_gateway_storage::models::WireguardPeer; -use nym_gateway_storage::Storage; use nym_task::TaskClient; use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK; use std::sync::Arc; @@ -18,26 +17,26 @@ use std::time::{Duration, SystemTime}; use tokio::sync::{mpsc, RwLock}; use tokio_stream::{wrappers::IntervalStream, StreamExt}; -pub(crate) type SharedBandwidthStorageManager = Arc>>; +pub(crate) type SharedBandwidthStorageManager = Arc>; const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days -pub struct PeerHandle { +pub struct PeerHandle { public_key: Key, host_information: Arc>, - peer_storage_manager: PeerStorageManager, - bandwidth_storage_manager: Option>, + peer_storage_manager: PeerStorageManager, + bandwidth_storage_manager: Option, request_tx: mpsc::Sender, timeout_check_interval: IntervalStream, task_client: TaskClient, startup_timestamp: SystemTime, } -impl PeerHandle { +impl PeerHandle { pub fn new( public_key: Key, host_information: Arc>, - peer_storage_manager: PeerStorageManager, - bandwidth_storage_manager: Option>, + peer_storage_manager: PeerStorageManager, + bandwidth_storage_manager: Option, request_tx: mpsc::Sender, task_client: &TaskClient, ) -> Self { diff --git a/common/wireguard/src/peer_storage_manager.rs b/common/wireguard/src/peer_storage_manager.rs index f7992b8bb0..c2567ce83a 100644 --- a/common/wireguard/src/peer_storage_manager.rs +++ b/common/wireguard/src/peer_storage_manager.rs @@ -4,7 +4,7 @@ use crate::error::Error; use defguard_wireguard_rs::host::Peer; use nym_gateway_storage::models::WireguardPeer; -use nym_gateway_storage::Storage; +use nym_gateway_storage::GatewayStorage; use std::time::Duration; use time::OffsetDateTime; @@ -29,15 +29,15 @@ impl Default for PeerFlushingBehaviourConfig { } } -pub struct PeerStorageManager { - pub(crate) storage: S, +pub struct PeerStorageManager { + pub(crate) storage: GatewayStorage, pub(crate) peer_information: Option, pub(crate) cfg: PeerFlushingBehaviourConfig, pub(crate) with_client_id: bool, } -impl PeerStorageManager { - pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self { +impl PeerStorageManager { + pub(crate) fn new(storage: GatewayStorage, peer: Peer, with_client_id: bool) -> Self { let peer_information = Some(PeerInformation::new(peer)); Self { storage, diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index f760a6ba23..076f1304f8 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -58,7 +58,6 @@ nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } nym-mixnode-common = { path = "../common/mixnode-common" } nym-network-defaults = { path = "../common/network-defaults" } nym-network-requester = { path = "../service-providers/network-requester" } -nym-node-http-api = { path = "../nym-node/nym-node-http-api" } nym-sdk = { path = "../sdk/rust/nym-sdk" } nym-sphinx = { path = "../common/nymsphinx" } nym-statistics-common = { path = "../common/statistics" } @@ -67,13 +66,13 @@ nym-topology = { path = "../common/topology" } nym-types = { path = "../common/types" } nym-validator-client = { path = "../common/client-libs/validator-client" } nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } +nym-node-metrics = { path = "../nym-node/nym-node-metrics" } nym-wireguard = { path = "../common/wireguard" } nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } defguard_wireguard_rs = { workspace = true } - [build-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } sqlx = { workspace = true, features = [ diff --git a/gateway/src/config.rs b/gateway/src/config.rs index 0c2d7568d0..7d62118daa 100644 --- a/gateway/src/config.rs +++ b/gateway/src/config.rs @@ -1,36 +1,19 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_network_defaults::{DEFAULT_NYM_NODE_HTTP_PORT, TICKETBOOK_VALIDITY_DAYS}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::path::PathBuf; +use nym_network_defaults::TICKETBOOK_VALIDITY_DAYS; +use std::net::SocketAddr; use std::time::Duration; use url::Url; -use zeroize::{Zeroize, ZeroizeOnDrop}; -// 'DEBUG' -// where applicable, the below are defined in milliseconds -const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000); -const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); -const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); -const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); -const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; - -const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; -const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; - -const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5); -const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB +// TODO: can we move those away? +pub const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5); +pub const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB #[derive(Debug)] pub struct Config { - pub host: Host, - - pub http: Http, - pub gateway: Gateway, - // pub storage_paths: GatewayPaths, pub network_requester: NetworkRequester, pub ip_packet_router: IpPacketRouter, @@ -39,18 +22,13 @@ pub struct Config { } impl Config { - #[allow(clippy::too_many_arguments)] - pub fn externally_loaded( - host: impl Into, - http: impl Into, + pub fn new( gateway: impl Into, network_requester: impl Into, ip_packet_router: impl Into, debug: impl Into, ) -> Self { Config { - host: host.into(), - http: http.into(), gateway: gateway.into(), network_requester: network_requester.into(), ip_packet_router: ip_packet_router.into(), @@ -65,96 +43,23 @@ impl Config { pub fn get_nyxd_urls(&self) -> Vec { self.gateway.nyxd_urls.clone() } - - pub fn get_cosmos_mnemonic(&self) -> bip39::Mnemonic { - self.gateway.cosmos_mnemonic.clone() - } } -// TODO: this is very much a WIP. we need proper ssl certificate support here -#[derive(Debug, PartialEq)] -pub struct Host { - /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. - pub public_ips: Vec, - - /// Optional hostname of this node, for example nymtech.net. - // TODO: this is temporary. to be replaced by pulling the data directly from the certs. - pub hostname: Option, -} - -impl Host { - pub fn validate(&self) -> bool { - if self.public_ips.is_empty() { - return false; - } - - true - } -} - -#[derive(Debug, PartialEq)] -pub struct Http { - /// Socket address this node will use for binding its http API. - /// default: `0.0.0.0:8000` - pub bind_address: SocketAddr, - - /// Path to assets directory of custom landing page of this node. - pub landing_page_assets_path: Option, -} - -impl Default for Http { - fn default() -> Self { - Http { - bind_address: SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - DEFAULT_NYM_NODE_HTTP_PORT, - ), - landing_page_assets_path: None, - } - } -} - -// we only really care about the mnemonic being zeroized -#[derive(Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] +#[derive(Debug, PartialEq, Eq)] pub struct Gateway { - /// Version of the gateway for which this configuration was created. - pub version: String, + /// Indicates whether this gateway is accepting only zk-nym credentials for accessing the mixnet + /// or if it also accepts non-paying clients + pub enforce_zk_nyms: bool, - /// ID specifies the human readable ID of this particular gateway. - pub id: String, - - /// Indicates whether this gateway is accepting only coconut credentials for accessing the - /// the mixnet, or if it also accepts non-paying clients - pub only_coconut_credentials: bool, - - /// Address to which this mixnode will bind to and will be listening for packets. - #[zeroize(skip)] - pub listening_address: IpAddr, - - /// Port used for listening for all mixnet traffic. - /// (default: 1789) - pub mix_port: u16, - - /// Port used for listening for all client-related traffic. - /// (default: 9000) - pub clients_port: u16, - - /// If applicable, announced port for listening for secure websocket client traffic. - /// (default: None) - pub clients_wss_port: Option, + /// Socket address this node will use for binding its client websocket API. + /// default: `0.0.0.0:9000` + pub websocket_bind_address: SocketAddr, /// Addresses to APIs from which the node gets the view of the network. - #[zeroize(skip)] pub nym_api_urls: Vec, /// Addresses to validators which the node uses to check for double spending of ERC20 tokens. - #[zeroize(skip)] pub nyxd_urls: Vec, - - /// Mnemonic of a cosmos wallet used in checking for double spending. - // #[deprecated(note = "move to storage")] - // TODO: I don't think this should be stored directly in the config... - pub cosmos_mnemonic: bip39::Mnemonic, } #[derive(Debug, PartialEq)] @@ -185,60 +90,21 @@ impl Default for IpPacketRouter { #[derive(Debug)] pub struct Debug { - /// Initial value of an exponential backoff to reconnect to dropped TCP connection when - /// forwarding sphinx packets. - pub packet_forwarding_initial_backoff: Duration, - - /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when - /// forwarding sphinx packets. - pub packet_forwarding_maximum_backoff: Duration, - - /// Timeout for establishing initial connection when trying to forward a sphinx packet. - pub initial_connection_timeout: Duration, - - /// Maximum number of packets that can be stored waiting to get sent to a particular connection. - pub maximum_connection_buffer_size: usize, - - /// Delay between each subsequent presence data being sent. - // DEAD FIELD - pub presence_sending_delay: Duration, - - /// Length of filenames for new client messages. - // DEAD FIELD - pub stored_messages_filename_length: u16, - - /// Number of messages from offline client that can be pulled at once from the storage. - pub message_retrieval_limit: i64, - /// Defines maximum delay between client bandwidth information being flushed to the persistent storage. pub client_bandwidth_max_flushing_rate: Duration, /// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage. pub client_bandwidth_max_delta_flushing_amount: i64, - /// Specifies whether the mixnode should be using the legacy framing for the sphinx packets. - // it's set to true by default. The reason for that decision is to preserve compatibility with the - // existing nodes whilst everyone else is upgrading and getting the code for handling the new field. - // It shall be disabled in the subsequent releases. - pub use_legacy_framed_packet_version: bool, - pub zk_nym_tickets: ZkNymTicketHandlerDebug, } impl Default for Debug { fn default() -> Self { Debug { - packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, - packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, - initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, - presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, - maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, - stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, - message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, client_bandwidth_max_flushing_rate: DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE, client_bandwidth_max_delta_flushing_amount: DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT, - use_legacy_framed_packet_version: false, zk_nym_tickets: Default::default(), } } diff --git a/gateway/src/error.rs b/gateway/src/error.rs index fbd85c4654..474d2cbf64 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -3,7 +3,7 @@ use nym_authenticator::error::AuthenticatorError; use nym_gateway_stats_storage::error::StatsStorageError; -use nym_gateway_storage::error::StorageError; +use nym_gateway_storage::error::GatewayStorageError; use nym_ip_packet_router::error::IpPacketRouterError; use nym_network_requester::error::{ClientCoreError, NetworkRequesterError}; use nym_validator_client::nyxd::error::NyxdError; @@ -38,7 +38,7 @@ pub enum GatewayError { #[error("storage failure: {source}")] StorageError { #[from] - source: StorageError, + source: GatewayStorageError, }, #[error("stats storage failure: {source}")] @@ -74,14 +74,8 @@ pub enum GatewayError { source: AuthenticatorError, }, - #[error("failed to startup local network requester")] - NetworkRequesterStartupFailure, - - #[error("failed to startup local ip packet router")] - IpPacketRouterStartupFailure, - - #[error("failed to startup local authenticator")] - AuthenticatorStartupFailure, + #[error("failed to startup local {typ}")] + ServiceProviderStartupFailure { typ: &'static str }, #[error("there are no nym API endpoints available")] NoNymApisAvailable, diff --git a/gateway/src/lib.rs b/gateway/src/lib.rs index 0f19d6f7c5..abac82f783 100644 --- a/gateway/src/lib.rs +++ b/gateway/src/lib.rs @@ -9,4 +9,4 @@ pub mod error; pub mod node; pub use error::GatewayError; -pub use node::Gateway; +pub use node::GatewayTasksBuilder; diff --git a/gateway/src/node/client_handling/active_clients.rs b/gateway/src/node/client_handling/active_clients.rs index fbf83ce92c..3dacfd3f63 100644 --- a/gateway/src/node/client_handling/active_clients.rs +++ b/gateway/src/node/client_handling/active_clients.rs @@ -5,8 +5,6 @@ use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender use crate::node::client_handling::embedded_clients::LocalEmbeddedClientHandle; use dashmap::DashMap; use nym_sphinx::DestinationAddressBytes; -use nym_statistics_common::gateways; -use nym_statistics_common::gateways::GatewayStatsReporter; use std::sync::Arc; use tracing::warn; @@ -34,10 +32,9 @@ impl ActiveClient { } } -#[derive(Clone)] -pub(crate) struct ActiveClientsStore { +#[derive(Clone, Default)] +pub struct ActiveClientsStore { inner: Arc>, - stats_event_reporter: GatewayStatsReporter, } #[derive(Clone)] @@ -51,11 +48,8 @@ pub(crate) struct ClientIncomingChannels { impl ActiveClientsStore { /// Creates new instance of `ActiveClientsStore` to store in-memory handles to all currently connected clients. - pub(crate) fn new(stats_event_reporter: GatewayStatsReporter) -> Self { - ActiveClientsStore { - inner: Arc::new(DashMap::new()), - stats_event_reporter, - } + pub fn new() -> Self { + Self::default() } /// Tries to obtain sending channel to specified client. Note that if stale entry existed, it is @@ -64,7 +58,7 @@ impl ActiveClientsStore { /// # Arguments /// /// * `client`: address of the client for which to obtain the handle. - pub(crate) fn get_sender(&self, client: DestinationAddressBytes) -> Option { + pub fn get_sender(&self, client: DestinationAddressBytes) -> Option { let entry = self.inner.get(&client)?; let handle = entry.value().get_sender(); @@ -130,8 +124,6 @@ impl ActiveClientsStore { /// * `client`: address of the client for which to remove the handle. pub(crate) fn disconnect(&self, client: DestinationAddressBytes) { self.inner.remove(&client); - self.stats_event_reporter - .report(gateways::GatewayStatsEvent::new_session_stop(client)); } /// Insert new client handle into the store. @@ -153,12 +145,10 @@ impl ActiveClientsStore { if self.inner.insert(client, entry).is_some() { panic!("inserted a duplicate remote client") } - self.stats_event_reporter - .report(gateways::GatewayStatsEvent::new_session_start(client)); } /// Inserts a handle to the embedded client - pub(crate) fn insert_embedded(&self, local_client_handle: LocalEmbeddedClientHandle) { + pub fn insert_embedded(&self, local_client_handle: LocalEmbeddedClientHandle) { let key = local_client_handle.client_destination(); let entry = ActiveClient::Embedded(local_client_handle); if self.inner.insert(key, entry).is_some() { diff --git a/gateway/src/node/client_handling/embedded_clients/mod.rs b/gateway/src/node/client_handling/embedded_clients/mod.rs index 7974bb9c41..95af759ed9 100644 --- a/gateway/src/node/client_handling/embedded_clients/mod.rs +++ b/gateway/src/node/client_handling/embedded_clients/mod.rs @@ -9,10 +9,11 @@ use nym_network_requester::{GatewayPacketRouter, PacketRouter}; use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::DestinationAddressBytes; use nym_task::TaskClient; +use tokio::task::JoinHandle; use tracing::{debug, error, trace}; #[derive(Debug)] -pub(crate) struct LocalEmbeddedClientHandle { +pub struct LocalEmbeddedClientHandle { /// Nym address of the embedded client. pub(crate) address: Recipient, @@ -52,8 +53,8 @@ impl MessageRouter { } } - pub(crate) fn start_with_shutdown(self, shutdown: TaskClient) { - tokio::spawn(self.run_with_shutdown(shutdown)); + pub(crate) fn start_with_shutdown(self, shutdown: TaskClient) -> JoinHandle<()> { + tokio::spawn(self.run_with_shutdown(shutdown)) } fn handle_received_messages(&self, messages: Vec>) { diff --git a/gateway/src/node/client_handling/websocket/common_state.rs b/gateway/src/node/client_handling/websocket/common_state.rs index ec0a3eb1d1..4ff8b8991a 100644 --- a/gateway/src/node/client_handling/websocket/common_state.rs +++ b/gateway/src/node/client_handling/websocket/common_state.rs @@ -1,18 +1,23 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::node::ActiveClientsStore; use nym_credential_verification::{ecash::EcashManager, BandwidthFlushingBehaviourConfig}; use nym_crypto::asymmetric::identity; -use nym_statistics_common::gateways::GatewayStatsReporter; +use nym_gateway_storage::GatewayStorage; +use nym_mixnet_client::forwarder::MixForwardingSender; +use nym_node_metrics::events::MetricEventsSender; use std::sync::Arc; // I can see this being possible expanded with say storage or client store #[derive(Clone)] -pub(crate) struct CommonHandlerState { - pub(crate) ecash_verifier: Arc>, - pub(crate) storage: S, +pub(crate) struct CommonHandlerState { + pub(crate) ecash_verifier: Arc, + pub(crate) storage: GatewayStorage, pub(crate) local_identity: Arc, pub(crate) only_coconut_credentials: bool, pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig, - pub(crate) stats_event_reporter: GatewayStatsReporter, + pub(crate) metrics_sender: MetricEventsSender, + pub(crate) outbound_mix_sender: MixForwardingSender, + pub(crate) active_clients_store: ActiveClientsStore, } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index fd4f200177..d7bc9dd32e 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -24,9 +24,10 @@ use nym_gateway_requests::{ ClientControlRequest, ClientRequest, GatewayRequestsError, SensitiveServerResponse, SimpleGatewayRequestsError, }; -use nym_gateway_storage::{error::StorageError, Storage}; +use nym_gateway_storage::error::GatewayStorageError; +use nym_node_metrics::events::MetricsEvent; use nym_sphinx::forwarding::packet::MixPacket; -use nym_statistics_common::gateways; +use nym_statistics_common::gateways::GatewaySessionEvent; use nym_task::TaskClient; use nym_validator_client::coconut::EcashApiError; use rand::{random, CryptoRng, Rng}; @@ -39,7 +40,7 @@ use tracing::*; #[derive(Debug, Error)] pub enum RequestHandlingError { #[error("Internal gateway storage error")] - StorageError(#[from] StorageError), + StorageError(#[from] GatewayStorageError), #[error( "the database entry for bandwidth of the registered client {client_address} is missing!" @@ -136,9 +137,9 @@ impl IntoWSMessage for ServerResponse { } } -pub(crate) struct AuthenticatedHandler { - inner: FreshHandler, - bandwidth_storage_manager: BandwidthStorageManager, +pub(crate) struct AuthenticatedHandler { + inner: FreshHandler, + bandwidth_storage_manager: BandwidthStorageManager, client: ClientDetails, mix_receiver: MixMessageReceiver, // Occasionally the handler is requested to ping the connected client for confirm that it's @@ -149,20 +150,13 @@ pub(crate) struct AuthenticatedHandler { } // explicitly remove handle from the global store upon being dropped -impl Drop for AuthenticatedHandler { +impl Drop for AuthenticatedHandler { fn drop(&mut self) { - self.inner - .active_clients_store - .disconnect(self.client.address) + self.disconnect_client() } } -impl AuthenticatedHandler -where - // TODO: those trait bounds here don't really make sense.... - R: Rng + CryptoRng, - St: Storage + Clone + 'static, -{ +impl AuthenticatedHandler { /// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated /// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or /// sending sphinx packets. @@ -173,7 +167,7 @@ where /// * `client`: details (i.e. address and shared keys) of the registered client /// * `mix_receiver`: channel used for receiving messages from the mixnet destined for this client. pub(crate) async fn upgrade( - fresh: FreshHandler, + fresh: FreshHandler, client: ClientDetails, mix_receiver: MixMessageReceiver, is_active_request_receiver: IsActiveRequestReceiver, @@ -191,7 +185,7 @@ where client_address: client.address.as_base58_string(), })?; - Ok(AuthenticatedHandler { + let handler = AuthenticatedHandler { bandwidth_storage_manager: BandwidthStorageManager::new( fresh.shared_state.storage.clone(), ClientBandwidth::new(bandwidth.into()), @@ -204,14 +198,24 @@ where mix_receiver, is_active_request_receiver, is_active_ping_pending_reply: None, - }) + }; + handler.send_metrics(GatewaySessionEvent::new_session_start( + handler.client.address, + )); + + Ok(handler) } - /// Explicitly removes handle from the global store. - fn disconnect(self) { + fn disconnect_client(&mut self) { self.inner + .shared_state .active_clients_store - .disconnect(self.client.address) + .disconnect(self.client.address); + self.send_metrics(GatewaySessionEvent::new_session_stop(self.client.address)); + } + + fn send_metrics(&self, event: impl Into) { + self.inner.send_metrics(event) } /// Forwards the received mix packet from the client into the mix network. @@ -220,7 +224,12 @@ where /// /// * `mix_packet`: packet received from the client that should get forwarded into the network. fn forward_packet(&self, mix_packet: MixPacket) { - if let Err(err) = self.inner.outbound_mix_sender.unbounded_send(mix_packet) { + if let Err(err) = self + .inner + .shared_state + .outbound_mix_sender + .forward_packet(mix_packet) + { error!("We failed to forward requested mix packet - {err}. Presumably our mix forwarder has crashed. We cannot continue."); process::exit(1); } @@ -259,8 +268,8 @@ where trace!("available total bandwidth: {available_total}"); if let Ok(ticket_type) = maybe_ticket_type { - self.inner.shared_state.stats_event_reporter.report( - gateways::GatewayStatsEvent::new_ecash_ticket(self.client.address, ticket_type), + self.inner.shared_state.metrics_sender.report_unchecked( + GatewaySessionEvent::new_ecash_ticket(self.client.address, ticket_type), ); } else { error!("Somehow verified a ticket with an unknown ticket type"); @@ -372,7 +381,10 @@ where /// # Arguments /// /// * `raw_request`: raw message to handle. - async fn handle_text(&mut self, raw_request: String) -> Message { + async fn handle_text(&mut self, raw_request: String) -> Message + where + R: Rng + CryptoRng, + { trace!("text request"); let request = match ClientControlRequest::try_from(raw_request) { @@ -470,7 +482,10 @@ where client = %self.client.address.as_base58_string() ) )] - async fn handle_request(&mut self, raw_request: Message) -> Option { + async fn handle_request(&mut self, raw_request: Message) -> Option + where + R: Rng + CryptoRng, + { trace!("new request"); // apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore @@ -542,8 +557,8 @@ where /// and for sphinx packets received from the mix network that should be sent back to the client. pub(crate) async fn listen_for_requests(mut self, mut shutdown: TaskClient) where + R: Rng + CryptoRng, S: AsyncRead + AsyncWrite + Unpin, - St: Storage, { trace!("Started listening for ALL incoming requests..."); @@ -612,7 +627,6 @@ where } } - self.disconnect(); trace!("The stream was closed!"); } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index a723a5ca2d..188699c64c 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -3,14 +3,9 @@ use crate::node::client_handling::websocket::common_state::CommonHandlerState; use crate::node::client_handling::websocket::connection_handler::INITIAL_MESSAGE_TIMEOUT; -use crate::node::client_handling::{ - active_clients::ActiveClientsStore, - websocket::{ - connection_handler::{ - AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream, - }, - message_receiver::{IsActive, IsActiveRequestSender}, - }, +use crate::node::client_handling::websocket::{ + connection_handler::{AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream}, + message_receiver::{IsActive, IsActiveRequestSender}, }; use futures::{ channel::{mpsc, oneshot}, @@ -27,11 +22,11 @@ use nym_gateway_requests::{ types::{ClientControlRequest, ServerResponse}, BinaryResponse, SharedGatewayKey, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION, }; -use nym_gateway_storage::{error::StorageError, Storage}; -use nym_mixnet_client::forwarder::MixForwardingSender; +use nym_gateway_storage::error::GatewayStorageError; +use nym_node_metrics::events::MetricsEvent; use nym_sphinx::DestinationAddressBytes; use nym_task::TaskClient; -use rand::{CryptoRng, Rng}; +use rand::CryptoRng; use std::net::SocketAddr; use std::time::Duration; use thiserror::Error; @@ -43,7 +38,7 @@ use tracing::*; #[derive(Debug, Error)] pub(crate) enum InitialAuthenticationError { #[error("Internal gateway storage error")] - StorageError(#[from] StorageError), + StorageError(#[from] GatewayStorageError), #[error( "our datastore is corrupted. the stored key for client {client_id} is malformed: {source}" @@ -51,7 +46,7 @@ pub(crate) enum InitialAuthenticationError { MalformedStoredSharedKey { client_id: String, #[source] - source: StorageError, + source: GatewayStorageError, }, #[error("Failed to perform registration handshake: {0}")] @@ -107,11 +102,9 @@ pub(crate) enum InitialAuthenticationError { EmptyClientDetails, } -pub(crate) struct FreshHandler { +pub(crate) struct FreshHandler { rng: R, - pub(crate) shared_state: CommonHandlerState, - pub(crate) active_clients_store: ActiveClientsStore, - pub(crate) outbound_mix_sender: MixForwardingSender, + pub(crate) shared_state: CommonHandlerState, pub(crate) socket_connection: SocketStream, pub(crate) peer_address: SocketAddr, pub(crate) shutdown: TaskClient, @@ -120,11 +113,7 @@ pub(crate) struct FreshHandler { pub(crate) negotiated_protocol: Option, } -impl FreshHandler -where - R: Rng + CryptoRng, - St: Storage + Clone + 'static, -{ +impl FreshHandler { // for time being we assume handle is always constructed from raw socket. // if we decide we want to change it, that's not too difficult // also at this point I'm not entirely sure how to deal with this warning without @@ -133,16 +122,12 @@ where pub(crate) fn new( rng: R, conn: S, - outbound_mix_sender: MixForwardingSender, - active_clients_store: ActiveClientsStore, - shared_state: CommonHandlerState, + shared_state: CommonHandlerState, peer_address: SocketAddr, shutdown: TaskClient, ) -> Self { FreshHandler { rng, - active_clients_store, - outbound_mix_sender, socket_connection: SocketStream::RawTcp(conn), peer_address, negotiated_protocol: None, @@ -151,6 +136,10 @@ where } } + pub(crate) fn send_metrics(&self, event: impl Into) { + self.shared_state.metrics_sender.report_unchecked(event) + } + /// Attempts to perform websocket handshake with the remote and upgrades the raw TCP socket /// to the framed WebSocket. pub(crate) async fn perform_websocket_handshake(&mut self) -> Result<(), WsError> @@ -494,7 +483,7 @@ where // The other handler reported that the client is not active, so we can // disconnect the other client and continue with this connection. debug!("Other handler reports it is not active"); - self.active_clients_store.disconnect(address); + self.shared_state.active_clients_store.disconnect(address); } IsActive::Active => { // The other handled reported a positive reply, so we have to assume it's @@ -513,14 +502,14 @@ where Ok(Err(_)) => { // Other channel failed to reply (the channel sender probably dropped) info!("Other connection failed to reply, disconnecting it in favour of this new connection"); - self.active_clients_store.disconnect(address); + self.shared_state.active_clients_store.disconnect(address); } Err(_) => { // Timeout waiting for reply warn!( "Other connection timed out, disconnecting it in favour of this new connection" ); - self.active_clients_store.disconnect(address); + self.shared_state.active_clients_store.disconnect(address); } } Ok(()) @@ -562,7 +551,11 @@ where .map_err(InitialAuthenticationError::MalformedIV)?; // Check for duplicate clients - if let Some(client_tx) = self.active_clients_store.get_remote_client(address) { + if let Some(client_tx) = self + .shared_state + .active_clients_store + .get_remote_client(address) + { warn!("Detected duplicate connection for client: {address}"); self.handle_duplicate_client(address, client_tx.is_active_request_sender) .await?; @@ -678,7 +671,11 @@ where debug!(remote_client = %remote_identity); - if self.active_clients_store.is_active(remote_address) { + if self + .shared_state + .active_clients_store + .is_active(remote_address) + { return Err(InitialAuthenticationError::DuplicateConnection); } @@ -786,7 +783,7 @@ where pub(crate) async fn handle_until_authenticated_or_failure( mut self, shutdown: &mut TaskClient, - ) -> Option> + ) -> Option> where S: AsyncRead + AsyncWrite + Unpin + Send, R: CryptoRng + RngCore + Send, @@ -822,7 +819,7 @@ where let (mix_sender, mix_receiver) = mpsc::unbounded(); // Channel for handlers to ask other handlers if they are still active. let (is_active_request_sender, is_active_request_receiver) = mpsc::unbounded(); - self.active_clients_store.insert_remote( + self.shared_state.active_clients_store.insert_remote( registration_details.address, mix_sender, is_active_request_sender, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index caac41fba3..478b0ed6eb 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -5,7 +5,6 @@ use crate::config::Config; use nym_credential_verification::BandwidthFlushingBehaviourConfig; use nym_gateway_requests::shared_key::SharedGatewayKey; use nym_gateway_requests::ServerResponse; -use nym_gateway_storage::Storage; use nym_sphinx::DestinationAddressBytes; use rand::{CryptoRng, Rng}; use std::time::Duration; @@ -90,11 +89,10 @@ impl InitialAuthResult { // imo there's no point in including the peer address in anything higher than debug #[instrument(level = "debug", skip_all, fields(peer = %handle.peer_address))] -pub(crate) async fn handle_connection(mut handle: FreshHandler) +pub(crate) async fn handle_connection(mut handle: FreshHandler) where R: Rng + CryptoRng + Send, S: AsyncRead + AsyncWrite + Unpin + Send, - St: Storage + Clone + 'static, { // don't accept any new requests if we have already received shutdown if handle.shutdown.is_shutdown() { diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 425123d3e9..ed484d17b6 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -1,41 +1,37 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::websocket::common_state::CommonHandlerState; use crate::node::client_handling::websocket::connection_handler::FreshHandler; -use nym_gateway_storage::Storage; -use nym_mixnet_client::forwarder::MixForwardingSender; +use nym_task::TaskClient; use rand::rngs::OsRng; use std::net::SocketAddr; use std::process; use tokio::task::JoinHandle; use tracing::*; -pub(crate) struct Listener { +pub struct Listener { address: SocketAddr, - shared_state: CommonHandlerState, + shared_state: CommonHandlerState, + shutdown: TaskClient, } -impl Listener -where - S: Storage + Send + Sync + Clone + 'static, -{ - pub(crate) fn new(address: SocketAddr, shared_state: CommonHandlerState) -> Self { +impl Listener { + pub(crate) fn new( + address: SocketAddr, + shared_state: CommonHandlerState, + shutdown: TaskClient, + ) -> Self { Listener { address, shared_state, + shutdown, } } // TODO: change the signature to pub(crate) async fn run(&self, handler: Handler) - pub(crate) async fn run( - &mut self, - outbound_mix_sender: MixForwardingSender, - active_clients_store: ActiveClientsStore, - mut shutdown: nym_task::TaskClient, - ) { + pub(crate) async fn run(&mut self) { info!("Starting websocket listener at {}", self.address); let tcp_listener = match tokio::net::TcpListener::bind(self.address).await { Ok(listener) => listener, @@ -45,24 +41,22 @@ where } }; - while !shutdown.is_shutdown() { + while !self.shutdown.is_shutdown() { tokio::select! { biased; - _ = shutdown.recv() => { + _ = self.shutdown.recv() => { trace!("client_handling::Listener: received shutdown"); } connection = tcp_listener.accept() => { match connection { Ok((socket, remote_addr)) => { - let shutdown = shutdown.clone().named(format!("ClientConnectionHandler_{remote_addr}")); + let shutdown = self.shutdown.fork(format!("websocket-handler-{remote_addr}")); trace!("received a socket connection from {remote_addr}"); // TODO: I think we *REALLY* need a mechanism for having a maximum number of connected // clients or spawned tokio tasks -> perhaps a worker system? let handle = FreshHandler::new( OsRng, socket, - outbound_mix_sender.clone(), - active_clients_store.clone(), self.shared_state.clone(), remote_addr, shutdown, @@ -77,15 +71,7 @@ where } } - pub(crate) fn start( - mut self, - outbound_mix_sender: MixForwardingSender, - active_clients_store: ActiveClientsStore, - shutdown: nym_task::TaskClient, - ) -> JoinHandle<()> { - tokio::spawn(async move { - self.run(outbound_mix_sender, active_clients_store, shutdown) - .await - }) + pub fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { self.run().await }) } } diff --git a/gateway/src/node/internal_service_providers.rs b/gateway/src/node/internal_service_providers.rs new file mode 100644 index 0000000000..b26d9f562f --- /dev/null +++ b/gateway/src/node/internal_service_providers.rs @@ -0,0 +1,226 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, MessageRouter}; +use crate::node::client_handling::websocket::message_receiver::{ + MixMessageReceiver, MixMessageSender, +}; +use crate::GatewayError; +use async_trait::async_trait; +use futures::channel::{mpsc, oneshot}; +use nym_authenticator::Authenticator; +use nym_crypto::asymmetric::ed25519; +use nym_ip_packet_router::error::IpPacketRouterError; +use nym_ip_packet_router::IpPacketRouter; +use nym_mixnet_client::forwarder::MixForwardingSender; +use nym_network_requester::error::NetworkRequesterError; +use nym_network_requester::NRServiceProviderBuilder; +use nym_sdk::mixnet::Recipient; +use nym_sdk::{GatewayTransceiver, LocalGateway, PacketRouter}; +use nym_task::TaskClient; +use std::fmt::Display; +use tokio::task::JoinHandle; +use tracing::error; + +pub trait LocalRecipient { + fn address(&self) -> Recipient; +} + +impl LocalRecipient for nym_network_requester::core::OnStartData { + fn address(&self) -> Recipient { + self.address + } +} + +impl LocalRecipient for nym_ip_packet_router::OnStartData { + fn address(&self) -> Recipient { + self.address + } +} + +impl LocalRecipient for nym_authenticator::OnStartData { + fn address(&self) -> Recipient { + self.address + } +} + +#[async_trait] +pub trait RunnableServiceProvider { + const NAME: &'static str; + + type OnStartData: LocalRecipient; + type Error; + async fn run_service_provider(self) -> Result<(), Self::Error>; +} + +#[async_trait] +impl RunnableServiceProvider for NRServiceProviderBuilder { + const NAME: &'static str = "network requester"; + type OnStartData = nym_network_requester::core::OnStartData; + type Error = NetworkRequesterError; + + async fn run_service_provider(self) -> Result<(), Self::Error> { + self.run_service_provider().await + } +} + +#[async_trait] +impl RunnableServiceProvider for IpPacketRouter { + const NAME: &'static str = "ip router"; + type OnStartData = nym_ip_packet_router::OnStartData; + type Error = IpPacketRouterError; + + async fn run_service_provider(self) -> Result<(), Self::Error> { + self.run_service_provider().await + } +} + +#[async_trait] +impl RunnableServiceProvider for Authenticator { + const NAME: &'static str = "authenticator"; + type OnStartData = nym_authenticator::OnStartData; + type Error = nym_authenticator::error::AuthenticatorError; + + async fn run_service_provider(self) -> Result<(), Self::Error> { + self.run_service_provider().await + } +} + +pub struct ServiceProviderBeingBuilt { + on_start_rx: oneshot::Receiver, + sp_builder: T, + sp_message_router_builder: SpMessageRouterBuilder, +} + +pub struct StartedServiceProvider { + pub sp_join_handle: JoinHandle<()>, + pub message_router_join_handle: JoinHandle<()>, + pub on_start_data: T::OnStartData, + pub handle: LocalEmbeddedClientHandle, +} + +impl ServiceProviderBeingBuilt +where + T: RunnableServiceProvider + Send + Sync + 'static, + T::Error: Display + Send + Sync + 'static, +{ + pub(crate) fn new( + on_start_rx: oneshot::Receiver, + sp_builder: T, + sp_message_router_builder: SpMessageRouterBuilder, + ) -> Self { + ServiceProviderBeingBuilt { + on_start_rx, + sp_builder, + sp_message_router_builder, + } + } + + pub async fn start_service_provider( + mut self, + ) -> Result, GatewayError> { + let sp_join_handle = tokio::task::spawn(async move { + if let Err(err) = self.sp_builder.run_service_provider().await { + error!( + "the {} service provider encountered an error: {err}", + T::NAME + ) + } + }); + + let on_start_data = self + .on_start_rx + .await + .map_err(|_| GatewayError::ServiceProviderStartupFailure { typ: T::NAME })?; + + // this should be instantaneous since the data is sent on this channel before the on start is called; + // the failure should be impossible + let Ok(Some(packet_router)) = self.sp_message_router_builder.router_receiver.try_recv() + else { + return Err(GatewayError::ServiceProviderStartupFailure { typ: T::NAME }); + }; + + let mix_sender = self.sp_message_router_builder.mix_sender(); + let message_router_join_handle = self + .sp_message_router_builder + .start_message_router(packet_router); + + Ok(StartedServiceProvider { + sp_join_handle, + message_router_join_handle, + handle: LocalEmbeddedClientHandle::new(on_start_data.address(), mix_sender), + on_start_data, + }) + } +} + +pub struct ExitServiceProviders { + pub(crate) network_requester: ServiceProviderBeingBuilt, + pub(crate) ip_router: ServiceProviderBeingBuilt, +} + +impl ExitServiceProviders { + pub async fn start_service_providers( + self, + ) -> Result< + ( + StartedServiceProvider, + StartedServiceProvider, + ), + GatewayError, + > { + let started_nr = self.network_requester.start_service_provider().await?; + let started_ipr = self.ip_router.start_service_provider().await?; + + Ok((started_nr, started_ipr)) + } +} + +pub struct SpMessageRouterBuilder { + mix_sender: Option, + mix_receiver: MixMessageReceiver, + router_receiver: oneshot::Receiver, + gateway_transceiver: Option, + shutdown: TaskClient, +} + +impl SpMessageRouterBuilder { + pub(crate) fn new( + node_identity: ed25519::PublicKey, + forwarding_channel: MixForwardingSender, + shutdown: TaskClient, + ) -> Self { + let (mix_sender, mix_receiver) = mpsc::unbounded(); + let (router_tx, router_rx) = oneshot::channel(); + + let transceiver = LocalGateway::new(node_identity, forwarding_channel, router_tx); + + SpMessageRouterBuilder { + mix_sender: Some(mix_sender), + mix_receiver, + router_receiver: router_rx, + gateway_transceiver: Some(transceiver), + shutdown, + } + } + + #[allow(clippy::expect_used)] + pub(crate) fn gateway_transceiver(&mut self) -> Box { + Box::new( + self.gateway_transceiver + .take() + .expect("attempting to use the same gateway transceiver twice"), + ) + } + + #[allow(clippy::expect_used)] + fn mix_sender(&mut self) -> MixMessageSender { + self.mix_sender + .take() + .expect("attempting to use the same mix sender twice") + } + + fn start_message_router(self, packet_router: PacketRouter) -> JoinHandle<()> { + MessageRouter::new(self.mix_receiver, packet_router).start_with_shutdown(self.shutdown) + } +} diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 4cb1a23c70..62e8274af8 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -6,7 +6,7 @@ use crate::node::client_handling::websocket::message_receiver::MixMessageSender; use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor; use futures::channel::mpsc::SendError; use futures::StreamExt; -use nym_gateway_storage::{error::StorageError, Storage}; +use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage}; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_sphinx::forwarding::packet::MixPacket; use nym_sphinx::framing::codec::NymCodec; @@ -30,7 +30,7 @@ enum CriticalPacketProcessingError { AckForwardingFailure { source: SendError }, } -pub(crate) struct ConnectionHandler { +pub(crate) struct ConnectionHandler { packet_processor: PacketProcessor, // TODO: investigate performance trade-offs for whether this cache even makes sense @@ -39,11 +39,11 @@ pub(crate) struct ConnectionHandler { // and each `get` internally copies the channel, however, is it really that expensive? clients_store_cache: HashMap, active_clients_store: ActiveClientsStore, - storage: St, + storage: GatewayStorage, ack_sender: MixForwardingSender, } -impl Clone for ConnectionHandler { +impl Clone for ConnectionHandler { fn clone(&self) -> Self { // remove stale entries from the cache while cloning let mut clients_store_cache = HashMap::with_capacity(self.clients_store_cache.capacity()); @@ -63,10 +63,10 @@ impl Clone for ConnectionHandler { } } -impl ConnectionHandler { +impl ConnectionHandler { pub(crate) fn new( packet_processor: PacketProcessor, - storage: St, + storage: GatewayStorage, ack_sender: MixForwardingSender, active_clients_store: ActiveClientsStore, ) -> Self { @@ -123,7 +123,7 @@ impl ConnectionHandler { &self, client_address: DestinationAddressBytes, message: Vec, - ) -> Result<(), StorageError> { + ) -> Result<(), GatewayStorageError> { debug!("Storing received message for {client_address} on the disk...",); self.storage.store_message(client_address, message).await @@ -137,14 +137,9 @@ impl ConnectionHandler { if let Some(forward_ack) = forward_ack { let next_hop = forward_ack.next_hop(); trace!("Sending ack from packet for {client_address} to {next_hop}",); - self.ack_sender - .unbounded_send(forward_ack) - .map_err( - |source| CriticalPacketProcessingError::AckForwardingFailure { - source: source.into_send_error(), - }, - )?; + .forward_packet(forward_ack) + .map_err(|source| CriticalPacketProcessingError::AckForwardingFailure { source })?; } Ok(()) } diff --git a/gateway/src/node/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs index 86b99108ef..17dfe00cd8 100644 --- a/gateway/src/node/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; -use nym_gateway_storage::Storage; use nym_task::TaskClient; use std::net::SocketAddr; use std::process; @@ -20,10 +19,7 @@ impl Listener { Listener { address, shutdown } } - pub(crate) async fn run(&mut self, connection_handler: ConnectionHandler) - where - St: Storage + Clone + 'static, - { + pub(crate) async fn run(&mut self, connection_handler: ConnectionHandler) { info!("Starting mixnet listener at {}", self.address); let tcp_listener = match tokio::net::TcpListener::bind(self.address).await { Ok(listener) => listener, @@ -52,10 +48,7 @@ impl Listener { } } - pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> - where - St: Storage + Clone + 'static, - { + pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> { info!("Running mix listener on {:?}", self.address.to_string()); tokio::spawn(async move { self.run(connection_handler).await }) diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 90e60f5bfc..84b1990351 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -1,60 +1,43 @@ -// Copyright 2020-2023 - Nym Technologies SA +// Copyright 2020-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::GatewayError; -use crate::node::client_handling::active_clients::ActiveClientsStore; -use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, MessageRouter}; use crate::node::client_handling::websocket; -use crate::node::helpers::GatewayTopologyProvider; -use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; -use futures::channel::{mpsc, oneshot}; +use crate::node::internal_service_providers::{ + ExitServiceProviders, ServiceProviderBeingBuilt, SpMessageRouterBuilder, +}; +use futures::channel::oneshot; +use nym_authenticator::Authenticator; use nym_credential_verification::ecash::{ credential_sender::CredentialHandlerConfig, EcashManager, }; -use nym_crypto::asymmetric::{encryption, identity}; -use nym_mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; +use nym_crypto::asymmetric::ed25519; +use nym_gateway_storage::models::WireguardPeer; +use nym_ip_packet_router::IpPacketRouter; +use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_defaults::NymNetworkDetails; -use nym_network_requester::{LocalGateway, NRServiceProviderBuilder}; -use nym_node_http_api::state::metrics::SharedSessionStats; -use nym_statistics_common::gateways::{self, GatewayStatsReporter}; -use nym_task::{TaskClient, TaskHandle, TaskManager}; -use nym_topology::NetworkAddress; -use nym_validator_client::client::NodeId; +use nym_network_requester::NRServiceProviderBuilder; +use nym_node_metrics::events::MetricEventsSender; +use nym_task::TaskClient; +use nym_topology::TopologyProvider; use nym_validator_client::nyxd::{Coin, CosmWasmClient}; -use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, UserAgent}; +use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; use rand::seq::SliceRandom; use rand::thread_rng; -use statistics::GatewayStatisticsCollector; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::net::IpAddr; use std::path::PathBuf; -use std::process; use std::sync::Arc; -use std::time::Duration; use tracing::*; +use zeroize::Zeroizing; pub(crate) mod client_handling; -pub(crate) mod helpers; -pub(crate) mod mixnet_handling; -pub(crate) mod statistics; +mod internal_service_providers; +pub use client_handling::active_clients::ActiveClientsStore; pub use nym_gateway_stats_storage::PersistentStatsStorage; -pub use nym_gateway_storage::{PersistentStorage, Storage}; - -// TODO: should this struct live here? -struct StartedNetworkRequester { - /// Handle to interact with the local network requester - handle: LocalEmbeddedClientHandle, -} - -// TODO: should this struct live here? -#[allow(unused)] -struct StartedAuthenticator { - wg_api: Arc, - - /// Handle to interact with the local authenticator - handle: LocalEmbeddedClientHandle, -} +pub use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage}; +pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; #[derive(Debug, Clone)] pub struct LocalNetworkRequesterOpts { @@ -77,448 +60,101 @@ pub struct LocalAuthenticatorOpts { pub custom_mixnet_path: Option, } -pub struct Gateway { +pub struct GatewayTasksBuilder { config: Config, network_requester_opts: Option, ip_packet_router_opts: Option, - // Use None when wireguard feature is not enabled too - #[allow(dead_code)] authenticator_opts: Option, - /// ed25519 keypair used to assert one's identity. - identity_keypair: Arc, - - /// x25519 keypair used for Diffie-Hellman. Currently only used for sphinx key derivation. - sphinx_keypair: Arc, - - client_storage: St, - - user_agent: UserAgent, - - stats_storage: PersistentStatsStorage, - + // TODO: combine with authenticator, since you have to start both wireguard_data: Option, - session_stats: Option, + /// ed25519 keypair used to assert one's identity. + identity_keypair: Arc, - task_client: Option, + storage: GatewayStorage, + + mix_packet_sender: MixForwardingSender, + + metrics_sender: MetricEventsSender, + + mnemonic: Arc>, + + shutdown: TaskClient, + + // populated and cached as necessary + ecash_manager: Option>, + + wireguard_peers: Option>, + + wireguard_networks: Option>, } -impl Gateway { - #[allow(clippy::too_many_arguments)] - pub fn new_loaded( +impl Drop for GatewayTasksBuilder { + fn drop(&mut self) { + // disarm the shutdown as it was already used to construct relevant tasks and we don't want the builder + // to cause shutdown + self.shutdown.disarm(); + } +} + +impl GatewayTasksBuilder { + pub fn new( config: Config, - network_requester_opts: Option, - ip_packet_router_opts: Option, - authenticator_opts: Option, - identity_keypair: Arc, - sphinx_keypair: Arc, - client_storage: St, - user_agent: UserAgent, - stats_storage: PersistentStatsStorage, - ) -> Self { - Gateway { + identity: Arc, + storage: GatewayStorage, + mix_packet_sender: MixForwardingSender, + metrics_sender: MetricEventsSender, + mnemonic: Arc>, + shutdown: TaskClient, + ) -> GatewayTasksBuilder { + GatewayTasksBuilder { config, - network_requester_opts, - ip_packet_router_opts, - authenticator_opts, - identity_keypair, - sphinx_keypair, - client_storage, - user_agent, - stats_storage, + network_requester_opts: None, + ip_packet_router_opts: None, + authenticator_opts: None, wireguard_data: None, - session_stats: None, - task_client: None, + identity_keypair: identity, + storage, + mix_packet_sender, + metrics_sender, + mnemonic, + shutdown, + ecash_manager: None, + wireguard_peers: None, + wireguard_networks: None, } } - pub fn set_task_client(&mut self, task_client: TaskClient) { - self.task_client = Some(task_client) + pub fn set_network_requester_opts( + &mut self, + network_requester_opts: Option, + ) { + self.network_requester_opts = network_requester_opts; } - pub fn set_session_stats(&mut self, session_stats: SharedSessionStats) { - self.session_stats = Some(session_stats); + pub fn set_ip_packet_router_opts( + &mut self, + ip_packet_router_opts: Option, + ) { + self.ip_packet_router_opts = ip_packet_router_opts; + } + + pub fn set_authenticator_opts(&mut self, authenticator_opts: Option) { + self.authenticator_opts = authenticator_opts; } pub fn set_wireguard_data(&mut self, wireguard_data: nym_wireguard::WireguardData) { self.wireguard_data = Some(wireguard_data) } - fn gateway_topology_provider(&self) -> GatewayTopologyProvider { - // TODO: make topology ttl configurable - // (to be done in reeses with the final smooshing) - GatewayTopologyProvider::new( - self.as_topology_node(), - Duration::from_secs(5 * 60), - self.user_agent.clone(), - self.config.gateway.nym_api_urls.clone(), - ) - } - - fn as_topology_node(&self) -> nym_topology::gateway::LegacyNode { - let ip = self - .config - .host - .public_ips - .first() - .copied() - .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); - let mix_host = SocketAddr::new(ip, self.config.gateway.mix_port); - - nym_topology::gateway::LegacyNode { - // those fields are irrelevant for the purposes of routing so it's fine if they're inaccurate. - // the only thing that matters is the identity key (and maybe version) - node_id: NodeId::MAX, - mix_host, - host: NetworkAddress::IpAddr(ip), - clients_ws_port: self.config.gateway.clients_port, - clients_wss_port: self.config.gateway.clients_wss_port, - sphinx_key: *self.sphinx_keypair.public_key(), - - identity_key: *self.identity_keypair.public_key(), - version: env!("CARGO_PKG_VERSION").into(), - } - } - - fn start_mix_socket_listener( + // if this is to be used anywhere else, we might need some wrapper around it + async fn build_nyxd_signing_client( &self, - ack_sender: MixForwardingSender, - active_clients_store: ActiveClientsStore, - shutdown: TaskClient, - ) where - St: Storage + Clone + 'static, - { - info!("Starting mix socket listener..."); - - let packet_processor = - mixnet_handling::PacketProcessor::new(self.sphinx_keypair.private_key()); - - let connection_handler = ConnectionHandler::new( - packet_processor, - self.client_storage.clone(), - ack_sender, - active_clients_store, - ); - - let listening_address = SocketAddr::new( - self.config.gateway.listening_address, - self.config.gateway.mix_port, - ); - - mixnet_handling::Listener::new(listening_address, shutdown).start(connection_handler); - } - - #[cfg(target_os = "linux")] - async fn start_authenticator( - &mut self, - forwarding_channel: MixForwardingSender, - topology_provider: GatewayTopologyProvider, - shutdown: TaskClient, - ecash_verifier: Arc>, - ) -> Result> - where - St: Storage + Clone + 'static, - { - let opts = self - .authenticator_opts - .as_ref() - .ok_or(GatewayError::UnspecifiedAuthenticatorConfig)?; - let (router_tx, mut router_rx) = oneshot::channel(); - let (auth_mix_sender, auth_mix_receiver) = mpsc::unbounded(); - let router_shutdown = shutdown.fork("message_router"); - let transceiver = LocalGateway::new( - *self.identity_keypair.public_key(), - forwarding_channel, - router_tx, - ); - let mut used_private_network_ips = vec![]; - let mut all_peers = vec![]; - for wireguard_peer in self - .client_storage - .get_all_wireguard_peers() - .await? - .into_iter() - { - let mut peer = defguard_wireguard_rs::host::Peer::try_from(wireguard_peer.clone())?; - let Some(peer) = peer.allowed_ips.pop() else { - tracing::warn!( - "Peer {} has empty allowed ips. It will be removed", - peer.public_key - ); - self.client_storage - .remove_wireguard_peer(&peer.public_key.to_string()) - .await?; - continue; - }; - used_private_network_ips.push(peer.ip); - all_peers.push(wireguard_peer); - } - - if let Some(wireguard_data) = self.wireguard_data.take() { - let (on_start_tx, on_start_rx) = oneshot::channel(); - let mut authenticator_server = nym_authenticator::Authenticator::new( - opts.config.clone(), - wireguard_data.inner.clone(), - used_private_network_ips, - ) - .with_ecash_verifier(ecash_verifier) - .with_custom_gateway_transceiver(Box::new(transceiver)) - .with_shutdown(shutdown.fork("authenticator")) - .with_wait_for_gateway(true) - .with_minimum_gateway_performance(0) - .with_custom_topology_provider(Box::new(topology_provider)) - .with_on_start(on_start_tx); - - if let Some(custom_mixnet) = &opts.custom_mixnet_path { - authenticator_server = authenticator_server.with_stored_topology(custom_mixnet)? - } - - tokio::spawn(async move { - if let Err(e) = authenticator_server.run_service_provider().await { - error!("Run authenticator server - {e}"); - } - }); - - let start_data = on_start_rx - .await - .map_err(|_| GatewayError::AuthenticatorStartupFailure)?; - - // this should be instantaneous since the data is sent on this channel before the on start is called; - // the failure should be impossible - let Ok(Some(packet_router)) = router_rx.try_recv() else { - return Err(Box::new(GatewayError::AuthenticatorStartupFailure)); - }; - - MessageRouter::new(auth_mix_receiver, packet_router) - .start_with_shutdown(router_shutdown); - - let wg_api = nym_wireguard::start_wireguard( - self.client_storage.clone(), - all_peers, - shutdown, - wireguard_data, - ) - .await?; - - Ok(StartedAuthenticator { - wg_api, - handle: LocalEmbeddedClientHandle::new(start_data.address, auth_mix_sender), - }) - } else { - Err(Box::new(GatewayError::InternalWireguardError( - "wireguard not set".to_string(), - ))) - } - } - - #[cfg(not(target_os = "linux"))] - async fn start_authenticator( - &self, - _forwarding_channel: MixForwardingSender, - _topology_provider: GatewayTopologyProvider, - _shutdown: TaskClient, - _ecash_verifier: Arc>, - ) -> Result> { - todo!("Authenticator is currently only supported on Linux"); - } - - fn start_client_websocket_listener( - &self, - forwarding_channel: MixForwardingSender, - active_clients_store: ActiveClientsStore, - shutdown: TaskClient, - ecash_verifier: Arc>, - stats_event_reporter: GatewayStatsReporter, - ) where - St: Storage + Send + Sync + Clone + 'static, - { - info!("Starting client [web]socket listener..."); - - let listening_address = SocketAddr::new( - self.config.gateway.listening_address, - self.config.gateway.clients_port, - ); - - let shared_state = websocket::CommonHandlerState { - ecash_verifier, - storage: self.client_storage.clone(), - local_identity: Arc::clone(&self.identity_keypair), - only_coconut_credentials: self.config.gateway.only_coconut_credentials, - bandwidth_cfg: (&self.config).into(), - stats_event_reporter, - }; - - websocket::Listener::new(listening_address, shared_state).start( - forwarding_channel, - active_clients_store, - shutdown, - ); - } - - fn start_packet_forwarder(&self, shutdown: TaskClient) -> MixForwardingSender { - info!("Starting mix packet forwarder..."); - - let (mut packet_forwarder, packet_sender) = PacketForwarder::new( - self.config.debug.packet_forwarding_initial_backoff, - self.config.debug.packet_forwarding_maximum_backoff, - self.config.debug.initial_connection_timeout, - self.config.debug.maximum_connection_buffer_size, - self.config.debug.use_legacy_framed_packet_version, - shutdown, - ); - - tokio::spawn(async move { packet_forwarder.run().await }); - packet_sender - } - - fn start_stats_collector( - &self, - shared_session_stats: SharedSessionStats, - shutdown: TaskClient, - ) -> gateways::GatewayStatsReporter { - info!("Starting gateway stats collector..."); - - let (mut stats_collector, stats_event_sender) = - GatewayStatisticsCollector::new(shared_session_stats, self.stats_storage.clone()); - tokio::spawn(async move { stats_collector.run(shutdown).await }); - stats_event_sender - } - - // TODO: rethink the logic in this function... - async fn start_network_requester( - &self, - forwarding_channel: MixForwardingSender, - topology_provider: GatewayTopologyProvider, - shutdown: TaskClient, - ) -> Result { - info!("Starting network requester..."); - - // if network requester is enabled, configuration file must be provided! - let Some(nr_opts) = &self.network_requester_opts else { - return Err(GatewayError::UnspecifiedNetworkRequesterConfig); - }; - - // this gateway, whenever it has anything to send to its local NR will use fake_client_tx - let (nr_mix_sender, nr_mix_receiver) = mpsc::unbounded(); - let router_shutdown = shutdown.fork("message_router"); - - let (router_tx, mut router_rx) = oneshot::channel(); - - let transceiver = LocalGateway::new( - *self.identity_keypair.public_key(), - forwarding_channel, - router_tx, - ); - - let (on_start_tx, on_start_rx) = oneshot::channel(); - let mut nr_builder = NRServiceProviderBuilder::new(nr_opts.config.clone()) - .with_shutdown(shutdown) - .with_custom_gateway_transceiver(Box::new(transceiver)) - .with_wait_for_gateway(true) - .with_minimum_gateway_performance(0) - .with_custom_topology_provider(Box::new(topology_provider)) - .with_on_start(on_start_tx); - - if let Some(custom_mixnet) = &nr_opts.custom_mixnet_path { - nr_builder = nr_builder.with_stored_topology(custom_mixnet)? - } - - tokio::spawn(async move { - if let Err(err) = nr_builder.run_service_provider().await { - // no need to panic as we have passed a task client to the NR so we're most likely - // already in the process of shutting down - error!("network requester has failed: {err}") - } - }); - - let start_data = on_start_rx - .await - .map_err(|_| GatewayError::NetworkRequesterStartupFailure)?; - - // this should be instantaneous since the data is sent on this channel before the on start is called; - // the failure should be impossible - let Ok(Some(packet_router)) = router_rx.try_recv() else { - return Err(GatewayError::NetworkRequesterStartupFailure); - }; - - MessageRouter::new(nr_mix_receiver, packet_router).start_with_shutdown(router_shutdown); - let address = start_data.address; - - info!("the local network requester is running on {address}",); - Ok(StartedNetworkRequester { - handle: LocalEmbeddedClientHandle::new(address, nr_mix_sender), - }) - } - - async fn start_ip_packet_router( - &self, - forwarding_channel: MixForwardingSender, - topology_provider: GatewayTopologyProvider, - shutdown: TaskClient, - ) -> Result { - info!("Starting IP packet provider..."); - - // if network requester is enabled, configuration file must be provided! - let Some(ip_opts) = &self.ip_packet_router_opts else { - return Err(GatewayError::UnspecifiedIpPacketRouterConfig); - }; - - // this gateway, whenever it has anything to send to its local NR will use fake_client_tx - let (ipr_mix_sender, ipr_mix_receiver) = mpsc::unbounded(); - let router_shutdown = shutdown.fork("message_router"); - - let (router_tx, mut router_rx) = oneshot::channel(); - - let transceiver = LocalGateway::new( - *self.identity_keypair.public_key(), - forwarding_channel, - router_tx, - ); - - let (on_start_tx, on_start_rx) = oneshot::channel(); - let mut ip_packet_router = - nym_ip_packet_router::IpPacketRouter::new(ip_opts.config.clone()) - .with_shutdown(shutdown) - .with_custom_gateway_transceiver(Box::new(transceiver)) - .with_wait_for_gateway(true) - .with_minimum_gateway_performance(0) - .with_custom_topology_provider(Box::new(topology_provider)) - .with_on_start(on_start_tx); - - if let Some(custom_mixnet) = &ip_opts.custom_mixnet_path { - ip_packet_router = ip_packet_router.with_stored_topology(custom_mixnet)? - } - - tokio::spawn(async move { - if let Err(err) = ip_packet_router.run_service_provider().await { - // no need to panic as we have passed a task client to the ip packet router so - // we're most likely already in the process of shutting down - error!("ip packet router has failed: {err}") - } - }); - - let start_data = on_start_rx - .await - .map_err(|_| GatewayError::IpPacketRouterStartupFailure)?; - - // this should be instantaneous since the data is sent on this channel before the on start is called; - // the failure should be impossible - let Ok(Some(packet_router)) = router_rx.try_recv() else { - return Err(GatewayError::IpPacketRouterStartupFailure); - }; - - MessageRouter::new(ipr_mix_receiver, packet_router).start_with_shutdown(router_shutdown); - let address = start_data.address; - - info!("the local ip packet router is running on {address}"); - Ok(LocalEmbeddedClientHandle::new(address, ipr_mix_sender)) - } - - fn random_nyxd_client(&self) -> Result { + ) -> Result { let endpoints = self.config.get_nyxd_urls(); let validator_nyxd = endpoints .choose(&mut thread_rng()) @@ -527,81 +163,38 @@ impl Gateway { let network_details = NymNetworkDetails::new_from_env(); let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?; - DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( + let nyxd_client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( client_config, validator_nyxd.as_ref(), - self.config.get_cosmos_mnemonic(), - ) - .map_err(Into::into) - } + (**self.mnemonic).clone(), + )?; - async fn check_if_bonded(&self) -> bool { - // TODO: if anything, this should be getting data directly from the contract - // as opposed to the validator API - for api_url in self.config.get_nym_api_endpoints() { - let client = nym_validator_client::NymApiClient::new(api_url.clone()); - match client.get_all_basic_nodes(None).await { - Ok(nodes) => { - return nodes.iter().any(|node| { - &node.ed25519_identity_pubkey == self.identity_keypair.public_key() - }) - } - Err(err) => { - error!("failed to grab initial network gateways from {api_url}: {err}",); - } - } - } + let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone(); + let account = nyxd_client.address(); + let balance = nyxd_client + .get_balance(&account, mix_denom_base.clone()) + .await? + .unwrap_or(Coin::new(0, mix_denom_base)); - error!( - "failed to grab initial network gateways from any of the available apis. Please try to startup again in few minutes", - ); - process::exit(1); - } - - pub async fn run(mut self) -> Result<(), GatewayError> - where - St: Storage + Clone + 'static, - { - info!("Starting nym gateway!"); - - if self.check_if_bonded().await { - warn!("You seem to have bonded your gateway before starting it - that's highly unrecommended as in the future it might result in slashing"); - } - - let shutdown = self - .task_client - .take() - .map(Into::::into) - .unwrap_or_else(|| TaskHandle::Internal(TaskManager::new(10))) - .name_if_unnamed("gateway"); - - let nyxd_client = self.random_nyxd_client()?; - - if self.config.gateway.only_coconut_credentials { - debug!("the gateway is running in coconut-only mode - making sure it has enough tokens for credential redemption"); - let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone(); - - let account = nyxd_client.address(); - let balance = nyxd_client - .get_balance(&account, mix_denom_base.clone()) - .await? - .unwrap_or(Coin::new(0, mix_denom_base)); - - error!("this gateway does not have enough tokens for covering transaction fees for credential redemption"); - - // see if we have at least 1nym (i.e. 1'000'000unym) - if balance.amount < 1_000_000 { + // see if we have at least 1nym (i.e. 1'000'000unym) + if balance.amount < 1_000_000 { + // don't allow constructing the client of we have to use zknym and don't have sufficient balance + if self.config.gateway.enforce_zk_nyms { return Err(GatewayError::InsufficientNodeBalance { account, balance }); } + + // TODO: this has to be enforced **ALL THE TIME in ENTRY mode**, + // because even if we don't demand zknym, somebody may send them and we need sufficient tokens for + // transaction fees for submitting redemption proposals + // but we're not going to introduce this check now as it would break a lot of existing gateways, + // so for now just log this error + error!("this gateway ({account}) has insufficient balance for possible zk-nym redemption transaction fees. it only has {balance} available.") } - let shared_session_stats = self.session_stats.take().unwrap_or_default(); - let stats_event_sender = self.start_stats_collector( - shared_session_stats, - shutdown.fork("statistics::GatewayStatisticsCollector"), - ); - let topology_provider = self.gateway_topology_provider(); + Ok(nyxd_client) + } + async fn build_ecash_manager(&self) -> Result, GatewayError> { let handler_config = CredentialHandlerConfig { revocation_bandwidth_penalty: self .config @@ -618,89 +211,260 @@ impl Gateway { .maximum_time_between_redemption, }; - let ecash_verifier = Arc::new( + let nyxd_client = self.build_nyxd_signing_client().await?; + let ecash_manager = Arc::new( EcashManager::new( handler_config, nyxd_client, self.identity_keypair.public_key().to_bytes(), - shutdown.fork("EcashVerifier"), - self.client_storage.clone(), + self.shutdown.fork("ecash-manager"), + self.storage.clone(), ) .await?, ); + Ok(ecash_manager) + } - let mix_forwarding_channel = self.start_packet_forwarder(shutdown.fork("PacketForwarder")); + async fn ecash_manager(&mut self) -> Result, GatewayError> { + match self.ecash_manager.clone() { + Some(cached) => Ok(cached), + None => { + let manager = self.build_ecash_manager().await?; + self.ecash_manager = Some(manager.clone()); + Ok(manager) + } + } + } - let active_clients_store = ActiveClientsStore::new(stats_event_sender.clone()); - self.start_mix_socket_listener( - mix_forwarding_channel.clone(), - active_clients_store.clone(), - shutdown.fork("mixnet_handling::Listener"), - ); - - self.start_client_websocket_listener( - mix_forwarding_channel.clone(), - active_clients_store.clone(), - shutdown.fork("websocket::Listener"), - ecash_verifier.clone(), - stats_event_sender.clone(), - ); - - if self.config.network_requester.enabled { - let embedded_nr = self - .start_network_requester( - mix_forwarding_channel.clone(), - topology_provider.clone(), - shutdown.fork("NetworkRequester"), - ) - .await?; - // insert information about embedded NR to the active clients store - active_clients_store.insert_embedded(embedded_nr.handle); - } else { - info!("embedded network requester is disabled"); + pub async fn build_websocket_listener( + &mut self, + active_clients_store: ActiveClientsStore, + ) -> Result { + let shared_state = websocket::CommonHandlerState { + ecash_verifier: self.ecash_manager().await?, + storage: self.storage.clone(), + local_identity: Arc::clone(&self.identity_keypair), + only_coconut_credentials: self.config.gateway.enforce_zk_nyms, + bandwidth_cfg: (&self.config).into(), + metrics_sender: self.metrics_sender.clone(), + outbound_mix_sender: self.mix_packet_sender.clone(), + active_clients_store: active_clients_store.clone(), }; - if self.config.ip_packet_router.enabled { - let embedded_ip_sp = self - .start_ip_packet_router( - mix_forwarding_channel.clone(), - topology_provider.clone(), - shutdown.fork("ip_service_provider"), - ) - .await?; - active_clients_store.insert_embedded(embedded_ip_sp); - } else { - info!("embedded ip packet router is disabled"); + Ok(websocket::Listener::new( + self.config.gateway.websocket_bind_address, + shared_state, + self.shutdown.fork("websocket"), + )) + } + + fn build_network_requester( + &mut self, + topology_provider: Box, + ) -> Result, GatewayError> { + // if network requester is enabled, configuration file must be provided! + let Some(nr_opts) = &self.network_requester_opts else { + return Err(GatewayError::UnspecifiedNetworkRequesterConfig); }; - let _wg_api = if self.wireguard_data.is_some() { - let embedded_auth = self - .start_authenticator( - mix_forwarding_channel, - topology_provider, - shutdown.fork("authenticator"), - ecash_verifier, - ) - .await - .map_err(|source| GatewayError::AuthenticatorStartError { source })?; - active_clients_store.insert_embedded(embedded_auth.handle); - Some(embedded_auth.wg_api) - } else { - None - }; - - info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!"); - - info!( - "Public key: {:?}", - self.identity_keypair.public_key().to_string() + let mut message_router_builder = SpMessageRouterBuilder::new( + *self.identity_keypair.public_key(), + self.mix_packet_sender.clone(), + self.shutdown.fork("network-requester-message-router"), ); + let transceiver = message_router_builder.gateway_transceiver(); - if let Err(source) = shutdown.wait_for_shutdown().await { - // that's a nasty workaround, but anyhow errors are generally nicer, especially on exit - return Err(GatewayError::ShutdownFailure { source }); + let (on_start_tx, on_start_rx) = oneshot::channel(); + let mut nr_builder = NRServiceProviderBuilder::new(nr_opts.config.clone()) + .with_shutdown(self.shutdown.fork("network-requester-sp")) + .with_custom_gateway_transceiver(transceiver) + .with_wait_for_gateway(true) + .with_minimum_gateway_performance(0) + .with_custom_topology_provider(topology_provider) + .with_on_start(on_start_tx); + + if let Some(custom_mixnet) = &nr_opts.custom_mixnet_path { + nr_builder = nr_builder.with_stored_topology(custom_mixnet)? } - Ok(()) + Ok(ServiceProviderBeingBuilt::new( + on_start_rx, + nr_builder, + message_router_builder, + )) + } + + fn build_ip_router( + &mut self, + topology_provider: Box, + ) -> Result, GatewayError> { + let Some(ip_opts) = &self.ip_packet_router_opts else { + return Err(GatewayError::UnspecifiedIpPacketRouterConfig); + }; + + let mut message_router_builder = SpMessageRouterBuilder::new( + *self.identity_keypair.public_key(), + self.mix_packet_sender.clone(), + self.shutdown.fork("ipr-message-router"), + ); + let transceiver = message_router_builder.gateway_transceiver(); + + let (on_start_tx, on_start_rx) = oneshot::channel(); + let mut ip_packet_router = IpPacketRouter::new(ip_opts.config.clone()) + .with_shutdown(self.shutdown.fork("ipr-sp")) + .with_custom_gateway_transceiver(Box::new(transceiver)) + .with_wait_for_gateway(true) + .with_minimum_gateway_performance(0) + .with_custom_topology_provider(topology_provider) + .with_on_start(on_start_tx); + + if let Some(custom_mixnet) = &ip_opts.custom_mixnet_path { + ip_packet_router = ip_packet_router.with_stored_topology(custom_mixnet)? + } + + Ok(ServiceProviderBeingBuilt::new( + on_start_rx, + ip_packet_router, + message_router_builder, + )) + } + + pub fn build_exit_service_providers( + &mut self, + // TODO: redesign the trait to allow cloning more easily + // (or use concrete types) + nr_topology_provider: Box, + ipr_topology_provider: Box, + ) -> Result { + Ok(ExitServiceProviders { + network_requester: self.build_network_requester(nr_topology_provider)?, + ip_router: self.build_ip_router(ipr_topology_provider)?, + }) + } + + async fn build_wireguard_peers_and_networks( + &self, + ) -> Result<(Vec, Vec), GatewayError> { + let mut used_private_network_ips = vec![]; + let mut all_peers = vec![]; + for wireguard_peer in self.storage.get_all_wireguard_peers().await?.into_iter() { + let mut peer = defguard_wireguard_rs::host::Peer::try_from(wireguard_peer.clone())?; + let Some(peer) = peer.allowed_ips.pop() else { + let peer_identity = &peer.public_key; + warn!("Peer {peer_identity} has empty allowed ips. It will be removed",); + self.storage + .remove_wireguard_peer(&peer_identity.to_string()) + .await?; + continue; + }; + used_private_network_ips.push(peer.ip); + all_peers.push(wireguard_peer); + } + + Ok((all_peers, used_private_network_ips)) + } + + // only used under linux + #[allow(dead_code)] + async fn get_wireguard_peers(&mut self) -> Result, GatewayError> { + if let Some(cached) = self.wireguard_peers.take() { + return Ok(cached); + } + + let (peers, used_private_network_ips) = self.build_wireguard_peers_and_networks().await?; + // cache private networks for the other task + + self.wireguard_networks = Some(used_private_network_ips); + Ok(peers) + } + + async fn get_wireguard_networks(&mut self) -> Result, GatewayError> { + if let Some(cached) = self.wireguard_networks.take() { + return Ok(cached); + } + + let (peers, used_private_network_ips) = self.build_wireguard_peers_and_networks().await?; + // cache peers for the other task + + self.wireguard_peers = Some(peers); + Ok(used_private_network_ips) + } + + pub async fn build_wireguard_authenticator( + &mut self, + topology_provider: Box, + ) -> Result, GatewayError> { + let ecash_manager = self.ecash_manager().await?; + let used_private_network_ips = self.get_wireguard_networks().await?; + + let Some(opts) = &self.authenticator_opts else { + return Err(GatewayError::UnspecifiedAuthenticatorConfig); + }; + let Some(wireguard_data) = &self.wireguard_data else { + return Err(GatewayError::InternalWireguardError( + "wireguard not set".to_string(), + )); + }; + + let mut message_router_builder = SpMessageRouterBuilder::new( + *self.identity_keypair.public_key(), + self.mix_packet_sender.clone(), + self.shutdown.fork("authenticator-message-router"), + ); + let transceiver = message_router_builder.gateway_transceiver(); + + let (on_start_tx, on_start_rx) = oneshot::channel(); + + let mut authenticator_server = Authenticator::new( + opts.config.clone(), + wireguard_data.inner.clone(), + used_private_network_ips, + ) + .with_ecash_verifier(ecash_manager) + .with_custom_gateway_transceiver(transceiver) + .with_shutdown(self.shutdown.fork("authenticator-sp")) + .with_wait_for_gateway(true) + .with_minimum_gateway_performance(0) + .with_custom_topology_provider(topology_provider) + .with_on_start(on_start_tx); + + if let Some(custom_mixnet) = &opts.custom_mixnet_path { + authenticator_server = authenticator_server.with_stored_topology(custom_mixnet)? + } + + Ok(ServiceProviderBeingBuilt::new( + on_start_rx, + authenticator_server, + message_router_builder, + )) + } + + #[cfg(not(target_os = "linux"))] + pub async fn try_start_wireguard( + &mut self, + ) -> Result, Box> { + unimplemented!("wireguard is not supported on this platform") + } + + #[cfg(target_os = "linux")] + pub async fn try_start_wireguard( + &mut self, + ) -> Result, Box> { + let all_peers = self.get_wireguard_peers().await?; + + let Some(wireguard_data) = self.wireguard_data.take() else { + return Err( + GatewayError::InternalWireguardError("wireguard not set".to_string()).into(), + ); + }; + + let wg_handle = nym_wireguard::start_wireguard( + self.storage.clone(), + all_peers, + self.shutdown.fork("wireguard"), + wireguard_data, + ) + .await?; + Ok(wg_handle) } } diff --git a/gateway/src/node/statistics/mod.rs b/gateway/src/node/statistics/mod.rs deleted file mode 100644 index 8b60c934a7..0000000000 --- a/gateway/src/node/statistics/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_gateway_stats_storage::PersistentStatsStorage; -use nym_node_http_api::state::metrics::SharedSessionStats; -use nym_statistics_common::gateways::{ - GatewayStatsEvent, GatewayStatsReceiver, GatewayStatsReporter, -}; -use nym_task::TaskClient; -use sessions::SessionStatsHandler; -use std::time::Duration; -use time::OffsetDateTime; -use tracing::{error, trace, warn}; - -pub mod sessions; - -const STATISTICS_UPDATE_TIMER_INTERVAL: Duration = Duration::from_secs(3600); //update timer, no need to check everytime - -pub(crate) struct GatewayStatisticsCollector { - stats_event_rx: GatewayStatsReceiver, - session_stats: SessionStatsHandler, - //here goes additionnal stats handler -} - -impl GatewayStatisticsCollector { - pub fn new( - shared_session_stats: SharedSessionStats, - stats_storage: PersistentStatsStorage, - ) -> (GatewayStatisticsCollector, GatewayStatsReporter) { - let (stats_event_tx, stats_event_rx) = tokio::sync::mpsc::unbounded_channel(); - - let session_stats = SessionStatsHandler::new(shared_session_stats, stats_storage); - let collector = GatewayStatisticsCollector { - stats_event_rx, - session_stats, - }; - let reporter = GatewayStatsReporter::new(stats_event_tx); - (collector, reporter) - } - - async fn update_shared_state(&mut self, update_time: OffsetDateTime) { - if let Err(e) = self - .session_stats - .maybe_update_shared_state(update_time) - .await - { - error!("Failed to update session stats - {e}"); - } - //here goes additionnal stats handler update - } - - async fn on_start(&mut self) { - if let Err(e) = self.session_stats.on_start().await { - error!("Failed to cleanup session stats handler - {e}"); - } - //here goes additionnal stats handler start cleanup - } - - pub async fn run(&mut self, mut shutdown: TaskClient) { - self.on_start().await; - let mut update_interval = tokio::time::interval(STATISTICS_UPDATE_TIMER_INTERVAL); - while !shutdown.is_shutdown() { - tokio::select! { - biased; - _ = shutdown.recv() => { - trace!("StatisticsCollector: Received shutdown"); - }, - _ = update_interval.tick() => { - let now = OffsetDateTime::now_utc(); - self.update_shared_state(now).await; - }, - - Some(stat_event) = self.stats_event_rx.recv() => { - //dispatching event to proper handler - match stat_event { - GatewayStatsEvent::SessionStatsEvent(event) => { - if let Err(e) = self.session_stats.handle_event(event).await{ - warn!("Session event handling error - {e}"); - }}, - } - }, - - } - } - } -} diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml deleted file mode 100644 index 9255e9e589..0000000000 --- a/mixnode/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2020 - Nym Technologies SA -# SPDX-License-Identifier: GPL-3.0-only - -[package] -name = "nym-mixnode" -license = "GPL-3.0" -version = "1.1.37" -authors = [ - "Dave Hrycyszyn ", - "Jędrzej Stuczyński ", - "Drazen Urch ", -] -description = "Implementation of a Loopix-based Mixnode" -edition = "2021" -rust-version = "1.70" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -colored = { workspace = true } -futures = { workspace = true } -time.workspace = true -tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } -tokio-util = { workspace = true, features = ["codec"] } -url = { workspace = true, features = ["serde"] } -tracing = { workspace = true } -thiserror = { workspace = true } - -# internal -nym-crypto = { path = "../common/crypto" } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } -nym-http-api-common = { path = "../common/http-api-common" } -nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } -nym-mixnode-common = { path = "../common/mixnode-common" } -nym-metrics = { path = "../common/nym-metrics" } -nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } -nym-node-http-api = { path = "../nym-node/nym-node-http-api" } -nym-sphinx = { path = "../common/nymsphinx" } -nym-sphinx-params = { path = "../common/nymsphinx/params" } -nym-task = { path = "../common/task" } -nym-types = { path = "../common/types" } -nym-topology = { path = "../common/topology" } -nym-validator-client = { path = "../common/client-libs/validator-client" } - -[dev-dependencies] -tokio = { workspace = true, features = [ - "rt-multi-thread", - "net", - "signal", - "test-util", -] } - -nym-sphinx-types = { path = "../common/nymsphinx/types" } -nym-sphinx-params = { path = "../common/nymsphinx/params" } diff --git a/mixnode/README.md b/mixnode/README.md deleted file mode 100644 index 8d9680669b..0000000000 --- a/mixnode/README.md +++ /dev/null @@ -1,75 +0,0 @@ - - -# Nym Mixnode - -A Rust mixnode implementation. - -## License - -Copyright (C) 2020 Nym Technologies SA - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -## Usage - -* `nym-mixnode` prints a help message showing usage options -* `nym-mixnode run --help` prints a help message showing usage options for the run command -* `nym-mixnode run --layer 1 --host x.x.x.x` will start the mixnode in layer 1 and bind to the specified host IP address. Coordinate with other people in your network to find out which layer needs coverage. - -By default, the Nym Mixnode will start on port 1789. If desired, you can change the port using the `--port` option. - -## Install debian - -```bash -sudo curl -s --compressed "http://apt.nymtech.net.s3-website.eu-central-1.amazonaws.com/nymtech.gpg" | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/nymtech.gpg > /dev/null -sudo echo "deb [signed-by=/etc/apt/trusted.gpg.d/nymtech.gpg] http://apt.nymtech.net.s3-website.eu-central-1.amazonaws.com/ squeeze main" > /etc/apt/sources.list.d/nymtech.list - -sudo apt-get update -sudo apt-get install nym-mixnode - -# See below for starting and managing the node -``` - -## Systemd support - -```bash -sudo systemctl enable nym-mixnode - -# Run -sudo systemctl start nym-mixnode - -# Check status -sudo systemctl status nym-mixnode - -# Logs -journalctl -f -u nym-mixnode - -``` - -## Build debian package - -```bash -# cargo install cargo-deb - -# Build package -cargo deb -p nym-mixnode - -# Install - -# This will init the mixnode to `/etc/nym` as `nym` user, and create a systemd service -sudo dpkg -i target/debian/ -``` \ No newline at end of file diff --git a/mixnode/src/config.rs b/mixnode/src/config.rs deleted file mode 100644 index d20f891ec5..0000000000 --- a/mixnode/src/config.rs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use std::net::{IpAddr, SocketAddr}; -use std::path::PathBuf; -use std::time::Duration; -use url::Url; - -// 'RTT MEASUREMENT' -const DEFAULT_PACKETS_PER_NODE: usize = 100; -const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); -const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); -const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); -const DEFAULT_BATCH_SIZE: usize = 50; -const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); -const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); - -// 'DEBUG' -const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); -const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000); -const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); -const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); -const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); -const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; - -#[derive(Debug, PartialEq)] -pub struct Config { - pub host: Host, - - pub http: Http, - - pub mixnode: MixNode, - - pub verloc: Verloc, - - pub debug: Debug, -} - -impl Config { - pub fn externally_loaded( - host: impl Into, - http: impl Into, - mixnode: impl Into, - verloc: impl Into, - debug: impl Into, - ) -> Self { - Config { - host: host.into(), - http: http.into(), - mixnode: mixnode.into(), - verloc: verloc.into(), - debug: debug.into(), - } - } - - // builder methods - pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec) -> Self { - self.mixnode.nym_api_urls = nym_api_urls; - self - } - - pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self { - self.mixnode.listening_address = listening_address; - - let http_port = self.http.bind_address.port(); - self.http.bind_address = SocketAddr::new(listening_address, http_port); - - self - } - - pub fn with_mix_port(mut self, port: u16) -> Self { - self.mixnode.mix_port = port; - self - } - - pub fn with_verloc_port(mut self, port: u16) -> Self { - self.mixnode.verloc_port = port; - self - } - - pub fn with_http_api_port(mut self, port: u16) -> Self { - let http_ip = self.http.bind_address.ip(); - self.http.bind_address = SocketAddr::new(http_ip, port); - self - } - - pub fn get_nym_api_endpoints(&self) -> Vec { - self.mixnode.nym_api_urls.clone() - } - - pub fn with_metrics_key(mut self, metrics_key: String) -> Self { - self.http.metrics_key = Some(metrics_key); - self - } - - pub fn metrics_key(&self) -> Option<&String> { - self.http.metrics_key.as_ref() - } -} - -// TODO: this is very much a WIP. we need proper ssl certificate support here -#[derive(Debug, PartialEq)] -pub struct Host { - /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. - pub public_ips: Vec, - - /// Optional hostname of this node, for example nymtech.net. - // TODO: this is temporary. to be replaced by pulling the data directly from the certs. - pub hostname: Option, -} - -impl Host { - pub fn validate(&self) -> bool { - if self.public_ips.is_empty() { - return false; - } - - true - } -} - -#[derive(Debug, PartialEq)] -pub struct Http { - /// Socket address this node will use for binding its http API. - /// default: `0.0.0.0:8000` - pub bind_address: SocketAddr, - - /// Path to assets directory of custom landing page of this node. - pub landing_page_assets_path: Option, - - pub metrics_key: Option, -} - -#[derive(Debug, PartialEq)] -pub struct MixNode { - /// Version of the mixnode for which this configuration was created. - pub version: String, - - /// ID specifies the human readable ID of this particular mixnode. - pub id: String, - - /// Address to which this mixnode will bind to and will be listening for packets. - pub listening_address: IpAddr, - - /// Port used for listening for all mixnet traffic. - /// (default: 1789) - pub mix_port: u16, - - /// Port used for listening for verloc traffic. - /// (default: 1790) - pub verloc_port: u16, - - /// Addresses to nym APIs from which the node gets the view of the network. - pub nym_api_urls: Vec, -} - -#[derive(Debug, PartialEq)] -pub struct Verloc { - /// Specifies number of echo packets sent to each node during a measurement run. - pub packets_per_node: usize, - - /// Specifies maximum amount of time to wait for the connection to get established. - pub connection_timeout: Duration, - - /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. - pub packet_timeout: Duration, - - /// Specifies delay between subsequent test packets being sent (after receiving a reply). - pub delay_between_packets: Duration, - - /// Specifies number of nodes being tested at once. - pub tested_nodes_batch_size: usize, - - /// Specifies delay between subsequent test runs. - pub testing_interval: Duration, - - /// Specifies delay between attempting to run the measurement again if the previous run failed - /// due to being unable to get the list of nodes. - pub retry_timeout: Duration, -} - -impl Default for Verloc { - fn default() -> Self { - Verloc { - packets_per_node: DEFAULT_PACKETS_PER_NODE, - connection_timeout: DEFAULT_CONNECTION_TIMEOUT, - packet_timeout: DEFAULT_PACKET_TIMEOUT, - delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS, - tested_nodes_batch_size: DEFAULT_BATCH_SIZE, - testing_interval: DEFAULT_TESTING_INTERVAL, - retry_timeout: DEFAULT_RETRY_TIMEOUT, - } - } -} - -#[derive(Debug, PartialEq)] -pub struct Debug { - /// Delay between each subsequent node statistics being logged to the console - pub node_stats_logging_delay: Duration, - - /// Delay between each subsequent node statistics being updated - pub node_stats_updating_delay: Duration, - - /// Initial value of an exponential backoff to reconnect to dropped TCP connection when - /// forwarding sphinx packets. - pub packet_forwarding_initial_backoff: Duration, - - /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when - /// forwarding sphinx packets. - pub packet_forwarding_maximum_backoff: Duration, - - /// Timeout for establishing initial connection when trying to forward a sphinx packet. - pub initial_connection_timeout: Duration, - - /// Maximum number of packets that can be stored waiting to get sent to a particular connection. - pub maximum_connection_buffer_size: usize, - - /// Specifies whether the mixnode should be using the legacy framing for the sphinx packets. - // it's set to true by default. The reason for that decision is to preserve compatibility with the - // existing nodes whilst everyone else is upgrading and getting the code for handling the new field. - // It shall be disabled in the subsequent releases. - pub use_legacy_framed_packet_version: bool, -} - -impl Default for Debug { - fn default() -> Self { - Debug { - node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY, - node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY, - packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, - packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, - initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, - maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, - use_legacy_framed_packet_version: false, - } - } -} diff --git a/mixnode/src/lib.rs b/mixnode/src/lib.rs deleted file mode 100644 index 8cc9effac6..0000000000 --- a/mixnode/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -pub mod config; -pub mod node; - -pub use node::MixNode; diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs deleted file mode 100644 index 8d04330e72..0000000000 --- a/mixnode/src/node/listener/connection_handler/mod.rs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::node::listener::connection_handler::packet_processing::PacketProcessor; -use crate::node::packet_delayforwarder::PacketDelayForwardSender; -use crate::node::TaskClient; -use futures::StreamExt; -use nym_metrics::nanos; -use nym_sphinx::forwarding::packet::MixPacket; -use nym_sphinx::framing::codec::NymCodec; -use nym_sphinx::framing::packet::FramedNymPacket; -use nym_sphinx::framing::processing::MixProcessingResult; -use nym_sphinx::Delay as SphinxDelay; -use packet_processing::process_received_packet; -use std::net::SocketAddr; -use tokio::net::TcpStream; -use tokio::time::Instant; -use tokio_util::codec::Framed; -use tracing::{debug, error, info, trace, warn}; - -pub(crate) mod packet_processing; - -#[derive(Clone)] -pub(crate) struct ConnectionHandler { - packet_processor: PacketProcessor, - delay_forwarding_channel: PacketDelayForwardSender, -} - -impl ConnectionHandler { - pub(crate) fn new( - packet_processor: PacketProcessor, - delay_forwarding_channel: PacketDelayForwardSender, - ) -> Self { - ConnectionHandler { - packet_processor, - delay_forwarding_channel, - } - } - - pub fn packet_processor(&self) -> &PacketProcessor { - &self.packet_processor - } - - fn delay_and_forward_packet(&self, mix_packet: MixPacket, delay: Option) { - // determine instant at which packet should get forwarded. this way we minimise effect of - // being stuck in the queue [of the channel] to get inserted into the delay queue - let forward_instant = delay.map(|delay| Instant::now() + delay.to_duration()); - - // if unbounded_send() failed it means that the receiver channel was disconnected - // and hence something weird must have happened without a way of recovering - self.delay_forwarding_channel - .unbounded_send((mix_packet, forward_instant)) - .expect("the delay-forwarder has died!"); - } - - fn handle_received_packet(&self, framed_sphinx_packet: FramedNymPacket) { - // - // TODO: here be replay attack detection - it will require similar key cache to the one in - // packet processor for vpn packets, - // question: can it also be per connection vs global? - // - - // all processing such, key caching, etc. was done. - // however, if it was a forward hop, we still need to delay it - nanos!("handle_received_packet", { - self.packet_processor - .node_stats_update_sender() - .report_received(); - match process_received_packet(framed_sphinx_packet, self.packet_processor().inner()) { - Err(err) => debug!("We failed to process received sphinx packet - {err}"), - Ok(res) => match res { - MixProcessingResult::ForwardHop(forward_packet, delay) => { - self.delay_and_forward_packet(forward_packet, delay) - } - MixProcessingResult::FinalHop(..) => { - warn!("Somehow processed a loop cover message that we haven't implemented yet!") - } - }, - } - }) - } - - pub(crate) async fn handle_connection( - self, - conn: TcpStream, - remote: SocketAddr, - mut shutdown: TaskClient, - ) { - debug!("Starting connection handler for {:?}", remote); - shutdown.disarm(); - let mut framed_conn = Framed::new(conn, NymCodec); - while !shutdown.is_shutdown() { - tokio::select! { - biased; - _ = shutdown.recv() => { - trace!("ConnectionHandler: received shutdown"); - } - framed_sphinx_packet = framed_conn.next() => { - match framed_sphinx_packet { - Some(Ok(framed_sphinx_packet)) => { - // TODO: benchmark spawning tokio task with full processing vs just processing it - // synchronously (without delaying inside of course, - // delay is moved to a global DelayQueue) - // under higher load in single and multi-threaded situation. - - // in theory we could process multiple sphinx packet from the same connection in parallel, - // but we already handle multiple concurrent connections so if anything, making - // that change would only slow things down - self.handle_received_packet(framed_sphinx_packet); - } - Some(Err(err)) => { - error!( - "{remote:?} - The socket connection got corrupted with error: {err}. Closing the socket", - ); - return; - } - None => break, // stream got closed by remote - } - }, - } - } - - info!( - "Closing connection from {:?}", - framed_conn.into_inner().peer_addr() - ); - trace!("ConnectionHandler: Exiting"); - } -} diff --git a/mixnode/src/node/listener/connection_handler/packet_processing.rs b/mixnode/src/node/listener/connection_handler/packet_processing.rs deleted file mode 100644 index 9e6742a6b8..0000000000 --- a/mixnode/src/node/listener/connection_handler/packet_processing.rs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::node::node_statistics; -use nym_crypto::asymmetric::encryption; -use nym_mixnode_common::packet_processor::error::MixProcessingError; -use nym_mixnode_common::packet_processor::processor::SphinxPacketProcessor; -use nym_sphinx::framing::packet::FramedNymPacket; -use nym_sphinx::framing::processing::{process_framed_packet, MixProcessingResult}; - -// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets -#[derive(Clone)] -pub(crate) struct PacketProcessor { - /// Responsible for performing unwrapping - inner_processor: SphinxPacketProcessor, - - /// Responsible for updating metrics data - node_stats_update_sender: node_statistics::UpdateSender, -} - -impl PacketProcessor { - pub(crate) fn new( - encryption_key: &encryption::PrivateKey, - node_stats_update_sender: node_statistics::UpdateSender, - ) -> Self { - PacketProcessor { - inner_processor: SphinxPacketProcessor::new(encryption_key.into()), - node_stats_update_sender, - } - } - - pub fn inner(&self) -> &SphinxPacketProcessor { - &self.inner_processor - } - - pub fn node_stats_update_sender(&self) -> &node_statistics::UpdateSender { - &self.node_stats_update_sender - } -} - -pub fn process_received_packet( - packet: FramedNymPacket, - inner_processor: &SphinxPacketProcessor, -) -> Result { - Ok(process_framed_packet(packet, inner_processor.sphinx_key())?) -} diff --git a/mixnode/src/node/listener/mod.rs b/mixnode/src/node/listener/mod.rs deleted file mode 100644 index a532ffc5d0..0000000000 --- a/mixnode/src/node/listener/mod.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::node::listener::connection_handler::ConnectionHandler; -use std::net::SocketAddr; -use std::process; -use tokio::net::TcpListener; -use tokio::task::JoinHandle; -use tracing::{error, info, trace, warn}; - -use super::TaskClient; - -pub(crate) mod connection_handler; - -pub(crate) struct Listener { - address: SocketAddr, - shutdown: TaskClient, -} - -impl Listener { - pub(crate) fn new(address: SocketAddr, shutdown: TaskClient) -> Self { - Listener { address, shutdown } - } - - async fn run(&mut self, connection_handler: ConnectionHandler) { - trace!("Starting Listener"); - let listener = match TcpListener::bind(self.address).await { - Ok(listener) => listener, - Err(err) => { - error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address); - process::exit(1); - } - }; - - while !self.shutdown.is_shutdown() { - tokio::select! { - biased; - _ = self.shutdown.recv() => { - trace!("Listener: Received shutdown"); - } - connection = listener.accept() => { - match connection { - Ok((socket, remote_addr)) => { - let handler = connection_handler.clone(); - tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone())); - } - Err(err) => warn!("Failed to accept incoming connection - {err}"), - } - }, - }; - } - trace!("Listener: Exiting"); - } - - pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> { - info!("Running mix listener on {:?}", self.address.to_string()); - - tokio::spawn(async move { self.run(connection_handler).await }) - } -} diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs deleted file mode 100644 index 9e6358de57..0000000000 --- a/mixnode/src/node/mod.rs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::config::Config; -use crate::node::listener::connection_handler::packet_processing::PacketProcessor; -use crate::node::listener::connection_handler::ConnectionHandler; -use crate::node::listener::Listener; -use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender}; -use nym_crypto::asymmetric::{encryption, identity}; -use nym_mixnode_common::verloc; -use nym_mixnode_common::verloc::VerlocMeasurer; -use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats}; -use nym_task::{TaskClient, TaskHandle}; -use std::net::SocketAddr; -use std::process; -use std::sync::Arc; -use tracing::{error, info, warn}; - -mod listener; -mod node_statistics; -mod packet_delayforwarder; - -// the MixNode will live for whole duration of this program -pub struct MixNode { - config: Config, - identity_keypair: Arc, - sphinx_keypair: Arc, - - task_client: Option, - mixing_stats: Option, - verloc_stats: Option, -} - -impl MixNode { - pub fn new_loaded( - config: Config, - identity_keypair: Arc, - sphinx_keypair: Arc, - ) -> Self { - MixNode { - task_client: None, - config, - identity_keypair, - sphinx_keypair, - mixing_stats: None, - verloc_stats: None, - } - } - - pub fn set_task_client(&mut self, task_client: TaskClient) { - self.task_client = Some(task_client) - } - - pub fn set_mixing_stats(&mut self, mixing_stats: SharedMixingStats) { - self.mixing_stats = Some(mixing_stats); - } - - pub fn set_verloc_stats(&mut self, verloc_stats: SharedVerlocStats) { - self.verloc_stats = Some(verloc_stats) - } - - fn start_node_stats_controller( - &mut self, - shutdown: TaskClient, - ) -> (SharedMixingStats, node_statistics::UpdateSender) { - info!("Starting node stats controller..."); - let mixing_stats = self.mixing_stats.take().unwrap_or_default(); - - let controller = node_statistics::Controller::new( - self.config.debug.node_stats_logging_delay, - self.config.debug.node_stats_updating_delay, - mixing_stats.clone(), - shutdown, - ); - let update_sender = controller.start(); - - (mixing_stats, update_sender) - } - - fn start_socket_listener( - &self, - node_stats_update_sender: node_statistics::UpdateSender, - delay_forwarding_channel: PacketDelayForwardSender, - shutdown: TaskClient, - ) { - info!("Starting socket listener..."); - - let packet_processor = - PacketProcessor::new(self.sphinx_keypair.private_key(), node_stats_update_sender); - - let connection_handler = ConnectionHandler::new(packet_processor, delay_forwarding_channel); - - let listening_address = SocketAddr::new( - self.config.mixnode.listening_address, - self.config.mixnode.mix_port, - ); - - Listener::new(listening_address, shutdown).start(connection_handler); - } - - fn start_packet_delay_forwarder( - &mut self, - node_stats_update_sender: node_statistics::UpdateSender, - shutdown: TaskClient, - ) -> PacketDelayForwardSender { - info!("Starting packet delay-forwarder..."); - - let client_config = nym_mixnet_client::Config::new( - self.config.debug.packet_forwarding_initial_backoff, - self.config.debug.packet_forwarding_maximum_backoff, - self.config.debug.initial_connection_timeout, - self.config.debug.maximum_connection_buffer_size, - self.config.debug.use_legacy_framed_packet_version, - ); - - let mut packet_forwarder = DelayForwarder::new( - nym_mixnet_client::Client::new(client_config), - node_stats_update_sender, - shutdown, - ); - - let packet_sender = packet_forwarder.sender(); - - tokio::spawn(async move { packet_forwarder.run().await }); - packet_sender - } - - fn start_verloc_measurements(&mut self, shutdown: TaskClient) -> SharedVerlocStats { - info!("Starting the round-trip-time measurer..."); - - // use the same binding address with the HARDCODED port for time being (I don't like that approach personally) - let listening_address = SocketAddr::new( - self.config.mixnode.listening_address, - self.config.mixnode.verloc_port, - ); - - let config = verloc::ConfigBuilder::new() - .listening_address(listening_address) - .packets_per_node(self.config.verloc.packets_per_node) - .connection_timeout(self.config.verloc.connection_timeout) - .packet_timeout(self.config.verloc.packet_timeout) - .delay_between_packets(self.config.verloc.delay_between_packets) - .tested_nodes_batch_size(self.config.verloc.tested_nodes_batch_size) - .testing_interval(self.config.verloc.testing_interval) - .retry_timeout(self.config.verloc.retry_timeout) - .nym_api_urls(self.config.get_nym_api_endpoints()) - .build(); - - let verloc_state = self.verloc_stats.take().unwrap_or_default(); - let mut verloc_measurer = - VerlocMeasurer::new(config, Arc::clone(&self.identity_keypair), shutdown); - verloc_measurer.set_shared_state(verloc_state.clone()); - - tokio::spawn(async move { verloc_measurer.run().await }); - verloc_state - } - - async fn check_if_bonded(&self) -> bool { - // TODO: if anything, this should be getting data directly from the contract - // as opposed to the validator API - for api_url in self.config.get_nym_api_endpoints() { - let client = nym_validator_client::NymApiClient::new(api_url.clone()); - match client.get_all_basic_nodes(None).await { - Ok(nodes) => { - return nodes.iter().any(|node| { - &node.ed25519_identity_pubkey == self.identity_keypair.public_key() - }) - } - Err(err) => { - error!("failed to grab initial network mixnodes from {api_url}: {err}",); - } - } - } - - error!( - "failed to grab initial network mixnodes from any of the available apis. Please try to startup again in few minutes", - ); - process::exit(1); - } - - async fn wait_for_interrupt(&self, shutdown: TaskHandle) { - let _res = shutdown.wait_for_shutdown().await; - info!("Stopping nym mixnode"); - } - - pub async fn run(&mut self) { - info!("Starting nym mixnode"); - - if self.check_if_bonded().await { - warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing"); - } - - // Shutdown notifier for signalling tasks to stop - let shutdown = self - .task_client - .take() - .map(Into::::into) - .unwrap_or_default() - .name_if_unnamed("mixnode"); - - let (_, node_stats_update_sender) = - self.start_node_stats_controller(shutdown.fork("node_statistics::Controller")); - let delay_forwarding_channel = self.start_packet_delay_forwarder( - node_stats_update_sender.clone(), - shutdown.fork("DelayForwarder"), - ); - self.start_socket_listener( - node_stats_update_sender, - delay_forwarding_channel, - shutdown.fork("Listener"), - ); - self.start_verloc_measurements(shutdown.fork("VerlocMeasurer")); - - info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!"); - self.wait_for_interrupt(shutdown).await; - } -} diff --git a/mixnode/src/node/node_statistics.rs b/mixnode/src/node/node_statistics.rs deleted file mode 100644 index 3e3f84cd87..0000000000 --- a/mixnode/src/node/node_statistics.rs +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_metrics::inc_by; - -use super::TaskClient; -use futures::channel::mpsc; -use futures::lock::Mutex; -use futures::StreamExt; -use nym_node_http_api::state::metrics::SharedMixingStats; -use std::collections::HashMap; -use std::ops::DerefMut; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; -use std::time::Duration; -use time::OffsetDateTime; -use tracing::{debug, info, trace}; - -// convenience aliases -type PacketsMap = HashMap; -type PacketDataReceiver = mpsc::UnboundedReceiver; -type PacketDataSender = mpsc::UnboundedSender; - -trait MixingStatsUpdateExt { - async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap); -} - -impl MixingStatsUpdateExt for SharedMixingStats { - async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap) { - let mut guard = self.write().await; - let snapshot_time = OffsetDateTime::now_utc(); - - guard.previous_update_time = guard.update_time; - guard.update_time = snapshot_time; - - guard.packets_received_since_startup += new_received; - for count in new_sent.values() { - guard.packets_sent_since_startup_all += count; - } - - for count in new_dropped.values() { - guard.packets_dropped_since_startup_all += count; - } - - inc_by!("packets_received_since_startup", new_received); - inc_by!( - "packets_sent_since_startup_all", - new_sent.values().sum::() - ); - inc_by!( - "packets_dropped_since_startup_all", - new_dropped.values().sum::() - ); - - guard.packets_received_since_last_update = new_received; - guard.packets_sent_since_last_update = new_sent; - guard.packets_explicitly_dropped_since_last_update = new_dropped; - } -} - -pub(crate) enum PacketEvent { - Sent(String), - Received, - Dropped(String), -} - -#[derive(Debug, Clone)] -struct CurrentPacketData { - inner: Arc, -} - -#[derive(Debug)] -struct PacketDataInner { - received: AtomicU64, - sent: Mutex, - dropped: Mutex, -} - -impl CurrentPacketData { - pub(crate) fn new() -> Self { - CurrentPacketData { - inner: Arc::new(PacketDataInner { - received: AtomicU64::new(0), - sent: Mutex::new(HashMap::new()), - dropped: Mutex::new(HashMap::new()), - }), - } - } - - fn increment_received(&self) { - self.inner.received.fetch_add(1, Ordering::SeqCst); - } - - async fn increment_sent(&self, destination: String) { - let mut unlocked = self.inner.sent.lock().await; - let receiver_count = unlocked.entry(destination).or_insert(0); - *receiver_count += 1; - } - - async fn increment_dropped(&self, destination: String) { - let mut unlocked = self.inner.dropped.lock().await; - let dropped_count = unlocked.entry(destination).or_insert(0); - *dropped_count += 1; - } - - async fn acquire_and_reset(&self) -> (u64, PacketsMap, PacketsMap) { - let mut unlocked_sent = self.inner.sent.lock().await; - let mut unlocked_dropped = self.inner.dropped.lock().await; - let received = self.inner.received.swap(0, Ordering::SeqCst); - - let sent = std::mem::take(unlocked_sent.deref_mut()); - let dropped = std::mem::take(unlocked_dropped.deref_mut()); - - (received, sent, dropped) - } -} - -// Worker that listens to a channel and updates the shared current packet data -struct UpdateHandler { - current_data: CurrentPacketData, - update_receiver: PacketDataReceiver, - shutdown: TaskClient, -} - -impl UpdateHandler { - fn new( - current_data: CurrentPacketData, - update_receiver: PacketDataReceiver, - shutdown: TaskClient, - ) -> Self { - UpdateHandler { - current_data, - update_receiver, - shutdown, - } - } - - async fn run(&mut self) { - trace!("Starting UpdateHandler"); - while !self.shutdown.is_shutdown() { - tokio::select! { - Some(packet_data) = self.update_receiver.next() => { - match packet_data { - PacketEvent::Received => self.current_data.increment_received(), - PacketEvent::Sent(destination) => { - self.current_data.increment_sent(destination).await - } - PacketEvent::Dropped(destination) => { - self.current_data.increment_dropped(destination).await - } - } - } - _ = self.shutdown.recv() => { - trace!("UpdateHandler: Received shutdown"); - break; - } - } - } - - trace!("UpdateHandler: Exiting"); - } -} - -// Channel to report statistics -#[derive(Clone)] -pub struct UpdateSender(PacketDataSender); - -impl UpdateSender { - pub(crate) fn new(update_sender: PacketDataSender) -> Self { - UpdateSender(update_sender) - } - - pub(crate) fn report_sent(&self, destination: String) { - // in unbounded_send() failed it means that the receiver channel was disconnected - // and hence something weird must have happened without a way of recovering - self.0 - .unbounded_send(PacketEvent::Sent(destination)) - .unwrap() - } - - // TODO: in the future this could be slightly optimised to get rid of the channel - // in favour of incrementing value directly - pub(crate) fn report_received(&self) { - // in unbounded_send() failed it means that the receiver channel was disconnected - // and hence something weird must have happened without a way of recovering - self.0.unbounded_send(PacketEvent::Received).unwrap() - } - - pub(crate) fn report_dropped(&self, destination: String) { - // in unbounded_send() failed it means that the receiver channel was disconnected - // and hence something weird must have happened without a way of recovering - self.0 - .unbounded_send(PacketEvent::Dropped(destination)) - .unwrap() - } -} - -// Worker that periodically updates the shared node stats from the current packet data buffer that -// the `UpdateHandler` updates. -struct StatsUpdater { - updating_delay: Duration, - current_packet_data: CurrentPacketData, - current_stats: SharedMixingStats, - shutdown: TaskClient, -} - -impl StatsUpdater { - fn new( - updating_delay: Duration, - current_packet_data: CurrentPacketData, - current_stats: SharedMixingStats, - shutdown: TaskClient, - ) -> Self { - StatsUpdater { - updating_delay, - current_packet_data, - current_stats, - shutdown, - } - } - - async fn update_stats(&self) { - // grab new data since last update - let (received, sent, dropped) = self.current_packet_data.acquire_and_reset().await; - self.current_stats.update(received, sent, dropped).await; - } - - async fn run(&mut self) { - while !self.shutdown.is_shutdown() { - tokio::select! { - _ = tokio::time::sleep(self.updating_delay) => self.update_stats().await, - _ = self.shutdown.recv() => { - trace!("StatsUpdater: Received shutdown"); - } - } - } - trace!("StatsUpdater: Exiting"); - } -} - -// TODO: question: should this data still be logged to the console or should we perhaps remove it -// since we have the http endpoint now? -struct PacketStatsConsoleLogger { - logging_delay: Duration, - stats: SharedMixingStats, - shutdown: TaskClient, -} - -impl PacketStatsConsoleLogger { - fn new(logging_delay: Duration, stats: SharedMixingStats, shutdown: TaskClient) -> Self { - PacketStatsConsoleLogger { - logging_delay, - stats, - shutdown, - } - } - - async fn log_running_stats(&mut self) { - let stats = self.stats.read().await; - - // it's super unlikely this will ever fail, but anything involving time is super weird - // so let's just guard against it - let time_difference = stats.update_time - stats.previous_update_time; - if time_difference.is_positive() { - // we honestly don't care if it was 30.000828427s or 30.002461449s, 30s is enough - let difference_secs = time_difference.whole_seconds(); - - info!( - "Since startup mixed {} packets! ({} in last {} seconds)", - stats.packets_sent_since_startup_all, - stats.packets_sent_since_last_update.values().sum::(), - difference_secs, - ); - if stats.packets_dropped_since_startup_all > 0 { - info!( - "Since startup dropped {} packets! ({} in last {} seconds)", - stats.packets_dropped_since_startup_all, - stats - .packets_explicitly_dropped_since_last_update - .values() - .sum::(), - difference_secs, - ); - } - - debug!( - "Since startup received {} packets ({} in last {} seconds)", - stats.packets_received_since_startup, - stats.packets_received_since_last_update, - difference_secs, - ); - trace!( - "Since startup sent packets to the following: \n{:#?} \n And in last {} seconds: {:#?})", - stats.packets_sent_since_startup_all, - difference_secs, - stats.packets_sent_since_last_update - ); - } else { - info!( - "Since startup mixed {} packets!", - stats.packets_sent_since_startup_all, - ); - if stats.packets_dropped_since_startup_all > 0 { - info!( - "Since startup dropped {} packets!", - stats.packets_dropped_since_startup_all, - ); - } - - debug!( - "Since startup received {} packets", - stats.packets_received_since_startup - ); - trace!( - "Since startup sent packets {}", - stats.packets_sent_since_startup_all - ); - } - } - - async fn run(&mut self) { - trace!("Starting PacketStatsConsoleLogger"); - while !self.shutdown.is_shutdown() { - tokio::select! { - _ = tokio::time::sleep(self.logging_delay) => self.log_running_stats().await, - _ = self.shutdown.recv() => { - trace!("PacketStatsConsoleLogger: Received shutdown"); - } - }; - } - trace!("PacketStatsConsoleLogger: Exiting"); - } -} - -// basically an easy single entry point to start all of the required tasks -pub struct Controller { - /// Responsible for handling data coming from UpdateSender - update_handler: UpdateHandler, - - /// Wrapper around channel sending information about new packet being received or sent - update_sender: UpdateSender, - - /// Responsible for logging stats to the console at given interval - console_logger: PacketStatsConsoleLogger, - - /// Responsible for updating stats at given interval - stats_updater: StatsUpdater, -} - -impl Controller { - pub(crate) fn new( - logging_delay: Duration, - stats_updating_delay: Duration, - mixing_stats: SharedMixingStats, - shutdown: TaskClient, - ) -> Self { - let (sender, receiver) = mpsc::unbounded(); - let shared_packet_data = CurrentPacketData::new(); - - Controller { - update_handler: UpdateHandler::new( - shared_packet_data.clone(), - receiver, - shutdown.clone(), - ), - update_sender: UpdateSender::new(sender), - console_logger: PacketStatsConsoleLogger::new( - logging_delay, - mixing_stats.clone(), - shutdown.clone(), - ), - stats_updater: StatsUpdater::new( - stats_updating_delay, - shared_packet_data, - mixing_stats.clone(), - shutdown, - ), - } - } - - // reporter is how node is going to be accessing the metrics data - pub(crate) fn start(self) -> UpdateSender { - // move out of self - let mut update_handler = self.update_handler; - let mut stats_updater = self.stats_updater; - let mut console_logger = self.console_logger; - - tokio::spawn(async move { update_handler.run().await }); - tokio::spawn(async move { stats_updater.run().await }); - tokio::spawn(async move { console_logger.run().await }); - - self.update_sender - } -} - -#[cfg(test)] -mod tests { - use super::*; - use nym_metrics::metrics; - use nym_task::TaskManager; - - #[tokio::test] - async fn node_stats_reported_are_received() { - let logging_delay = Duration::from_millis(20); - let stats_updating_delay = Duration::from_millis(10); - let shutdown = TaskManager::default(); - let stats = SharedMixingStats::new(); - let node_stats_controller = Controller::new( - logging_delay, - stats_updating_delay, - stats.clone(), - shutdown.subscribe(), - ); - - let update_sender = node_stats_controller.start(); - tokio::time::pause(); - - // Pass input - update_sender.report_sent("foo".to_string()); - update_sender.report_sent("foo".to_string()); - tokio::task::yield_now().await; - - tokio::time::advance(Duration::from_secs(1)).await; - tokio::task::yield_now().await; - - // Get output (stats) - let stats = stats.read().await; - assert_eq!(&stats.packets_sent_since_startup_all, &2); - assert_eq!(&stats.packets_sent_since_last_update.get("foo"), &Some(&2)); - assert_eq!(&stats.packets_sent_since_last_update.len(), &1); - assert_eq!(&stats.packets_received_since_startup, &0); - assert_eq!(&stats.packets_dropped_since_startup_all, &0); - assert_eq!(metrics!(), "# HELP nym_mixnode_packets_dropped_since_startup_all nym_mixnode_packets_dropped_since_startup_all\n# TYPE nym_mixnode_packets_dropped_since_startup_all counter\nnym_mixnode_packets_dropped_since_startup_all 0\n# HELP nym_mixnode_packets_received_since_startup nym_mixnode_packets_received_since_startup\n# TYPE nym_mixnode_packets_received_since_startup counter\nnym_mixnode_packets_received_since_startup 0\n# HELP nym_mixnode_packets_sent_since_startup_all nym_mixnode_packets_sent_since_startup_all\n# TYPE nym_mixnode_packets_sent_since_startup_all counter\nnym_mixnode_packets_sent_since_startup_all 2\n") - } -} diff --git a/mixnode/src/node/packet_delayforwarder.rs b/mixnode/src/node/packet_delayforwarder.rs deleted file mode 100644 index 99a61820ac..0000000000 --- a/mixnode/src/node/packet_delayforwarder.rs +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use super::TaskClient; -use crate::node::node_statistics::UpdateSender; -use futures::channel::mpsc; -use futures::StreamExt; -use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue}; -use nym_sphinx::forwarding::packet::MixPacket; -use std::io; -use tokio::time::Instant; -use tracing::trace; - -// Delay + MixPacket vs Instant + MixPacket - -// rather than using Duration directly, we use an Instant, this way we minimise skew due to -// time packet spent waiting in the queue to get delayed -pub(crate) type PacketDelayForwardSender = mpsc::UnboundedSender<(MixPacket, Option)>; -type PacketDelayForwardReceiver = mpsc::UnboundedReceiver<(MixPacket, Option)>; - -/// Entity responsible for delaying received sphinx packet and forwarding it to next node. -pub(crate) struct DelayForwarder -where - C: nym_mixnet_client::SendWithoutResponse, -{ - delay_queue: NonExhaustiveDelayQueue, - mixnet_client: C, - packet_sender: PacketDelayForwardSender, - packet_receiver: PacketDelayForwardReceiver, - node_stats_update_sender: UpdateSender, - shutdown: TaskClient, -} - -impl DelayForwarder -where - C: nym_mixnet_client::SendWithoutResponse, -{ - pub(crate) fn new( - client: C, - node_stats_update_sender: UpdateSender, - shutdown: TaskClient, - ) -> DelayForwarder { - let (packet_sender, packet_receiver) = mpsc::unbounded(); - - DelayForwarder:: { - delay_queue: NonExhaustiveDelayQueue::new(), - mixnet_client: client, - packet_sender, - packet_receiver, - node_stats_update_sender, - shutdown, - } - } - - pub(crate) fn sender(&self) -> PacketDelayForwardSender { - self.packet_sender.clone() - } - - fn forward_packet(&mut self, packet: MixPacket) { - let next_hop = packet.next_hop(); - let packet_type = packet.packet_type(); - let packet = packet.into_packet(); - - if let Err(err) = self - .mixnet_client - .send_without_response(next_hop, packet, packet_type) - { - if err.kind() == io::ErrorKind::WouldBlock { - // we only know for sure if we dropped a packet if our sending queue was full - // in any other case the connection might still be re-established (or created for the first time) - // and the packet might get sent, but we won't know about it - self.node_stats_update_sender - .report_dropped(next_hop.to_string()) - } else if err.kind() == io::ErrorKind::NotConnected { - // let's give the benefit of the doubt and assume we manage to establish connection - self.node_stats_update_sender - .report_sent(next_hop.to_string()); - } - } else { - self.node_stats_update_sender - .report_sent(next_hop.to_string()); - } - } - - /// Upon packet being finished getting delayed, forward it to the mixnet. - fn handle_done_delaying(&mut self, packet: Expired) { - let delayed_packet = packet.into_inner(); - self.forward_packet(delayed_packet) - } - - fn handle_new_packet(&mut self, new_packet: (MixPacket, Option)) { - // in case of a zero delay packet, don't bother putting it in the delay queue, - // just forward it immediately - if let Some(instant) = new_packet.1 { - // check if the delay has already expired, if so, don't bother putting it through - // the delay queue only to retrieve it immediately. Just forward it. - if instant.checked_duration_since(Instant::now()).is_none() { - self.forward_packet(new_packet.0) - } else { - self.delay_queue.insert_at(new_packet.0, instant); - } - } else { - self.forward_packet(new_packet.0) - } - } - - pub(crate) async fn run(&mut self) { - trace!("Starting DelayForwarder"); - loop { - tokio::select! { - delayed = self.delay_queue.next() => { - self.handle_done_delaying(delayed.unwrap()); - } - new_packet = self.packet_receiver.next() => { - // this one is impossible to ever panic - the object itself contains a sender - // and hence it can't happen that ALL senders are dropped - self.handle_new_packet(new_packet.unwrap()) - } - _ = self.shutdown.recv() => { - trace!("DelayForwarder: Received shutdown"); - break; - } - } - } - trace!("DelayForwarder: Exiting"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use std::sync::{Arc, Mutex}; - use std::time::Duration; - - use nym_sphinx::NymPacket; - use nym_task::TaskManager; - - use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; - use nym_sphinx_params::packet_sizes::PacketSize; - use nym_sphinx_params::PacketType; - use nym_sphinx_types::{ - crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, - DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH, - }; - - #[derive(Default)] - struct TestClient { - pub packets_sent: Arc>>, - } - - impl nym_mixnet_client::SendWithoutResponse for TestClient { - fn send_without_response( - &mut self, - address: NymNodeRoutingAddress, - packet: NymPacket, - packet_type: PacketType, - ) -> io::Result<()> { - self.packets_sent - .lock() - .unwrap() - .push((address, packet, packet_type)); - Ok(()) - } - } - - fn make_valid_sphinx_packet(size: PacketSize) -> NymPacket { - let (_, node1_pk) = crypto::keygen(); - let node1 = Node::new( - NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]), - node1_pk, - ); - let (_, node2_pk) = crypto::keygen(); - let node2 = Node::new( - NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]), - node2_pk, - ); - let (_, node3_pk) = crypto::keygen(); - let node3 = Node::new( - NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), - node3_pk, - ); - - let route = [node1, node2, node3]; - let destination = Destination::new( - DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), - [4u8; IDENTIFIER_LENGTH], - ); - let delays = vec![ - SphinxDelay::new_from_nanos(42), - SphinxDelay::new_from_nanos(42), - SphinxDelay::new_from_nanos(42), - ]; - NymPacket::sphinx_build(size.payload_size(), b"foomp", &route, &destination, &delays) - .unwrap() - } - - fn make_valid_outfox_packet(size: PacketSize) -> NymPacket { - let (_, node1_pk) = crypto::keygen(); - let node1 = Node::new( - NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]), - node1_pk, - ); - let (_, node2_pk) = crypto::keygen(); - let node2 = Node::new( - NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]), - node2_pk, - ); - let (_, node3_pk) = crypto::keygen(); - let node3 = Node::new( - NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), - node3_pk, - ); - - let (_, node4_pk) = crypto::keygen(); - let node4 = Node::new( - NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), - node4_pk, - ); - - let destination = Destination::new( - DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), - [4u8; IDENTIFIER_LENGTH], - ); - - let route = &[node1, node2, node3, node4]; - - let payload = vec![1; 48]; - - NymPacket::outfox_build(payload, route, &destination, Some(size.plaintext_size())).unwrap() - } - - #[tokio::test] - async fn packets_received_are_forwarded() { - // Wire up the DelayForwarder - let (stats_sender, _stats_receiver) = mpsc::unbounded(); - let node_stats_update_sender = UpdateSender::new(stats_sender); - let client = TestClient::default(); - let client_packets_sent = client.packets_sent.clone(); - let shutdown = TaskManager::default(); - let mut delay_forwarder = - DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe()); - let packet_sender = delay_forwarder.sender(); - - // Spawn the worker, listening on packet_sender channel - tokio::spawn(async move { delay_forwarder.run().await }); - - // Send a `MixPacket` down the channel without any delay attached. - let next_hop = - NymNodeRoutingAddress::from(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 42)); - let mix_packet = MixPacket::new( - next_hop, - make_valid_sphinx_packet(PacketSize::default()), - PacketType::default(), - ); - let forward_instant = None; - packet_sender - .unbounded_send((mix_packet, forward_instant)) - .unwrap(); - - // Give the the worker a chance to act - tokio::time::sleep(Duration::from_millis(10)).await; - - // The client should have forwarded the packet straight away - assert_eq!( - client_packets_sent - .lock() - .unwrap() - .iter() - .map(|(a, _, _)| *a) - .collect::>(), - vec![next_hop] - ); - } - - #[tokio::test] - async fn outfox_packets_received_are_forwarded() { - // Wire up the DelayForwarder - let (stats_sender, _stats_receiver) = mpsc::unbounded(); - let node_stats_update_sender = UpdateSender::new(stats_sender); - let client = TestClient::default(); - let client_packets_sent = client.packets_sent.clone(); - let shutdown = TaskManager::default(); - let mut delay_forwarder = - DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe()); - let packet_sender = delay_forwarder.sender(); - - // Spawn the worker, listening on packet_sender channel - tokio::spawn(async move { delay_forwarder.run().await }); - - // Send a `MixPacket` down the channel without any delay attached. - let next_hop = - NymNodeRoutingAddress::from(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 42)); - let mix_packet = MixPacket::new( - next_hop, - make_valid_outfox_packet(PacketSize::default()), - PacketType::default(), - ); - let forward_instant = None; - packet_sender - .unbounded_send((mix_packet, forward_instant)) - .unwrap(); - - // Give the the worker a chance to act - tokio::time::sleep(Duration::from_millis(10)).await; - - // The client should have forwarded the packet straight away - assert_eq!( - client_packets_sent - .lock() - .unwrap() - .iter() - .map(|(a, _, _)| *a) - .collect::>(), - vec![next_hop] - ); - } -} diff --git a/nym-api/src/support/cli/init.rs b/nym-api/src/support/cli/init.rs index 71efedbea4..d93e90545c 100644 --- a/nym-api/src/support/cli/init.rs +++ b/nym-api/src/support/cli/init.rs @@ -68,6 +68,9 @@ pub(crate) struct Args { /// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release` #[clap(long)] pub(crate) bind_address: Option, + + #[clap(hide = true, long, default_value_t = false)] + pub(crate) allow_illegal_ips: bool, // #[clap(short, long, default_value_t = OutputFormat::default())] // output: OutputFormat, } diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index 6436f19be1..63d4979efe 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -100,6 +100,9 @@ pub(crate) struct Args { /// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release` #[clap(long)] pub(crate) bind_address: Option, + + #[clap(hide = true, long, default_value_t = false)] + pub(crate) allow_illegal_ips: bool, } async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result { diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index 5a07c223be..4d1e171573 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -175,6 +175,9 @@ impl Config { if let Some(http_bind_address) = args.bind_address { self.base.bind_address = http_bind_address } + if args.allow_illegal_ips { + self.topology_cacher.debug.node_describe_allow_illegal_ips = true + } self } diff --git a/nym-api/src/support/config/override.rs b/nym-api/src/support/config/override.rs index 059d0f8136..a2f6d9c76c 100644 --- a/nym-api/src/support/config/override.rs +++ b/nym-api/src/support/config/override.rs @@ -31,6 +31,8 @@ pub(crate) struct OverrideConfig { /// Socket address this api will use for binding its http API. /// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release` pub(crate) bind_address: Option, + + pub(crate) allow_illegal_ips: bool, } impl From for OverrideConfig { @@ -44,6 +46,7 @@ impl From for OverrideConfig { announce_address: args.announce_address, monitor_credentials_mode: Some(args.monitor_credentials_mode), bind_address: args.bind_address, + allow_illegal_ips: args.allow_illegal_ips, } } } @@ -59,6 +62,7 @@ impl From for OverrideConfig { announce_address: args.announce_address, monitor_credentials_mode: args.monitor_credentials_mode, bind_address: args.bind_address, + allow_illegal_ips: args.allow_illegal_ips, } } } diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index f5b4c40202..44c61b455f 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -21,13 +21,13 @@ cosmwasm-std = { workspace = true } envy = { workspace = true } futures-util = { workspace = true } moka = { workspace = true, features = ["future"] } -nym-bin-common = { path = "../../common/bin-common", features = ["models"]} +nym-bin-common = { path = "../../common/bin-common", features = ["models"] } nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } nym-explorer-client = { path = "../../explorer-api/explorer-client" } nym-network-defaults = { path = "../../common/network-defaults" } nym-serde-helpers = { path = "../../common/serde-helpers"} -nym-statistics-common = { path = "../../common/statistics"} +nym-statistics-common = { path = "../../common/statistics" } nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-task = { path = "../../common/task" } nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } @@ -55,12 +55,15 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } # utoipauto = { git = "https://github.com/ProbablyClem/utoipauto", rev = "eb04cba" } utoipauto = { workspace = true } +nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } + + [build-dependencies] anyhow = { workspace = true } tokio = { workspace = true, features = ["macros"] } sqlx = { workspace = true, features = [ - "runtime-tokio-rustls", - "sqlite", - "macros", - "migrate", + "runtime-tokio-rustls", + "sqlite", + "macros", + "migrate", ] } diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs index fb8cca4c29..8d0664316f 100644 --- a/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs @@ -1,9 +1,7 @@ use crate::db::{models::GatewaySessionsRecord, queries, DbPool}; use error::NodeScraperError; - use nym_network_defaults::{NymNetworkDetails, DEFAULT_NYM_NODE_HTTP_PORT}; use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::SessionStats}; -use nym_statistics_common::gateways::SessionType; use nym_validator_client::{ client::{NodeId, NymNodeDetails}, models::{DescribedNodeType, NymNodeDescription}, @@ -11,6 +9,7 @@ use nym_validator_client::{ }; use time::OffsetDateTime; +use nym_node_metrics::entry::SessionType; use std::collections::HashMap; use tokio::time::Duration; use tracing::instrument; diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 92dec65fea..3f09db2f32 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -14,21 +14,27 @@ license = "GPL-3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = { workspace = true } anyhow.workspace = true bip39 = { workspace = true, features = ["zeroize"] } bs58.workspace = true celes = { workspace = true } # country codes colored = { workspace = true } clap = { workspace = true, features = ["cargo", "env"] } +dashmap = { workspace = true } +futures = { workspace = true } humantime-serde = { workspace = true } +human-repr = { workspace = true } ipnetwork = { workspace = true } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +si-scale = { workspace = true } thiserror.workspace = true tracing.workspace = true tracing-subscriber.workspace = true -tokio = { workspace = true, features = ["macros", "sync"] } +tokio = { workspace = true, features = ["macros", "sync", "rt-multi-thread"] } +tokio-util = { workspace = true, features = ["codec"] } toml = { workspace = true } url = { workspace = true, features = ["serde"] } zeroize = { workspace = true, features = ["zeroize_derive"] } @@ -47,23 +53,47 @@ nym-bin-common = { path = "../common/bin-common", features = [ nym-client-core-config-types = { path = "../common/client-core/config-types" } nym-config = { path = "../common/config" } nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } -nym-node-http-api = { path = "nym-node-http-api" } +nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } +nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } nym-pemstore = { path = "../common/pemstore" } nym-sphinx-acknowledgements = { path = "../common/nymsphinx/acknowledgements" } nym-sphinx-addressing = { path = "../common/nymsphinx/addressing" } +nym-sphinx-framing = { path = "../common/nymsphinx/framing" } +nym-sphinx-types = { path = "../common/nymsphinx/types" } +nym-sphinx-forwarding = { path = "../common/nymsphinx/forwarding" } nym-task = { path = "../common/task" } nym-types = { path = "../common/types" } nym-validator-client = { path = "../common/client-libs/validator-client" } nym-wireguard = { path = "../common/wireguard" } nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } +nym-verloc = { path = "../common/verloc" } +nym-metrics = { path = "../common/nym-metrics" } +nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" } +nym-topology = { path = "../common/topology" } + + +# http server +# useful for `#[axum_macros::debug_handler]` +#axum-macros = "0.3.8" +axum.workspace = true +axum-extra = { workspace = true, features = ["typed-header"] } +headers.workspace = true +time = { workspace = true, features = ["serde"] } +tower-http = { workspace = true, features = ["fs"] } +utoipa = { workspace = true, features = ["axum_extras", "time"] } +utoipa-swagger-ui = { workspace = true, features = ["axum"] } + +nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] } +nym-node-requests = { path = "nym-node-requests", default-features = false, features = ["openapi"] } +nym-node-metrics = { path = "nym-node-metrics" } # nodes: -nym-mixnode = { path = "../mixnode" } nym-gateway = { path = "../gateway" } nym-authenticator = { path = "../service-providers/authenticator" } nym-network-requester = { path = "../service-providers/network-requester" } nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } + [build-dependencies] # temporary bonding information v1 (to grab and parse nym-mixnode and nym-gateway package versions) cargo_metadata = { workspace = true } diff --git a/nym-node/nym-node-http-api/Cargo.toml b/nym-node/nym-node-http-api/Cargo.toml deleted file mode 100644 index 27b3fb53d5..0000000000 --- a/nym-node/nym-node-http-api/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "nym-node-http-api" -version = "0.1.0" -edition = "2021" -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -axum.workspace = true -axum-extra = { workspace = true, features = ["typed-header"] } -headers.workspace = true - -# useful for `#[axum_macros::debug_handler]` -#axum-macros = "0.3.8" -thiserror.workspace = true -time = { workspace = true, features = ["serde"] } -tokio = { workspace = true, features = ["macros"] } -tower-http = { workspace = true, features = ["fs"] } -tracing.workspace = true -utoipa = { workspace = true, features = ["axum_extras", "time"] } -utoipa-swagger-ui = { workspace = true, features = ["axum"] } - -colored = { workspace = true } -ipnetwork = { workspace = true } -rand = { workspace = true } - -# Wireguard: -fastrand = { workspace = true } - -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } -nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa"] } -nym-node-requests = { path = "../nym-node-requests", default-features = false, features = ["openapi"] } -nym-task = { path = "../../common/task" } - -nym-metrics = { path = "../../common/nym-metrics" } -nym-wireguard = { path = "../../common/wireguard" } - -[dev-dependencies] -base64 = { workspace = true } -hyper.workspace = true -dashmap.workspace = true -serde_json.workspace = true - -hmac = { workspace = true } -tower = { workspace = true } -x25519-dalek = { workspace = true } diff --git a/nym-node/nym-node-http-api/src/lib.rs b/nym-node/nym-node-http-api/src/lib.rs deleted file mode 100644 index 2621a30eda..0000000000 --- a/nym-node/nym-node-http-api/src/lib.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use axum::extract::connect_info::IntoMakeServiceWithConnectInfo; -use axum::extract::ConnectInfo; -use axum::middleware::AddExtension; -use axum::serve::Serve; -use axum::Router; -use nym_task::TaskClient; -use std::net::SocketAddr; -use tracing::{debug, error}; - -pub mod error; -pub mod middleware; -pub mod router; -pub mod state; - -pub use error::NymNodeHttpError; -pub use router::{api, landing_page, Config, NymNodeRouter}; - -// I guess this wasn't really meant to be extracted into separate type haha -type InnerService = IntoMakeServiceWithConnectInfo; -type ConnectInfoExt = AddExtension>; -pub type ServeService = Serve; - -pub struct NymNodeHTTPServer { - task_client: Option, - inner: ServeService, -} - -impl NymNodeHTTPServer { - pub(crate) fn new(inner: ServeService) -> Self { - NymNodeHTTPServer { - task_client: None, - inner, - } - } - - #[must_use] - pub fn with_task_client(mut self, task_client: TaskClient) -> Self { - self.task_client = Some(task_client); - self - } - - async fn run_server_forever(server: ServeService) { - if let Err(err) = server.await { - error!("the HTTP server has terminated with the error: {err}"); - } else { - error!("the HTTP server has terminated with producing any errors"); - } - } - - pub async fn run(self) { - if let Some(mut task_client) = self.task_client { - tokio::select! { - _ = task_client.recv_with_delay() => { - debug!("NymNodeHTTPServer: Received shutdown"); - } - _ = Self::run_server_forever(self.inner) => { } - } - } else { - Self::run_server_forever(self.inner).await - } - - debug!("NymNodeHTTPServer: Exiting"); - } -} diff --git a/nym-node/nym-node-http-api/src/middleware/logging.rs b/nym-node/nym-node-http-api/src/middleware/logging.rs deleted file mode 100644 index ae3c5f1d4e..0000000000 --- a/nym-node/nym-node-http-api/src/middleware/logging.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2023-2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use axum::{ - extract::{ConnectInfo, Request}, - http::{ - header::{HOST, USER_AGENT}, - HeaderValue, - }, - middleware::Next, - response::IntoResponse, -}; -use colored::*; -use std::net::SocketAddr; -use tracing::info; - -/// Simple logger for requests -pub async fn logger( - ConnectInfo(addr): ConnectInfo, - req: Request, - next: Next, -) -> impl IntoResponse { - let method = req.method().to_string().green(); - let uri = req.uri().to_string().blue(); - let agent = header_map( - req.headers().get(USER_AGENT), - "Unknown User Agent".to_string(), - ); - - let host = header_map(req.headers().get(HOST), "Unknown Host".to_string()); - - let res = next.run(req).await; - let status = res.status(); - let print_status = if status.is_client_error() || status.is_server_error() { - status.to_string().red() - } else if status.is_success() { - status.to_string().green() - } else { - status.to_string().yellow() - }; - - info!(target: "incoming request", "[{addr} -> {host}] {method} '{uri}': {print_status} / agent: {agent}"); - - res -} - -fn header_map(header: Option<&HeaderValue>, msg: String) -> String { - header - .map(|x| x.to_str().unwrap_or(&msg).to_string()) - .unwrap_or(msg) -} diff --git a/nym-node/nym-node-http-api/src/mod.rs b/nym-node/nym-node-http-api/src/mod.rs deleted file mode 100644 index 77cb66ba14..0000000000 --- a/nym-node/nym-node-http-api/src/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use axum::extract::connect_info::IntoMakeServiceWithConnectInfo; -use axum::Router; -use hyper::server::conn::AddrIncoming; -use hyper::Server; -use nym_task::TaskClient; -use std::net::SocketAddr; -use tracing::{debug, error, info}; - -pub mod middleware; -pub mod router; -pub mod state; - -pub use router::{api, landing_page, Config, NymNodeRouter}; - -pub struct NymNodeHTTPServer { - task_client: Option, - inner: Server>, -} - -impl NymNodeHTTPServer { - pub(crate) fn new( - inner: Server>, - ) -> Self { - NymNodeHTTPServer { - task_client: None, - inner, - } - } - - #[must_use] - pub fn with_task_client(mut self, task_client: TaskClient) -> Self { - self.task_client = Some(task_client); - self - } - - async fn run_server_forever( - server: Server>, - ) { - if let Err(err) = server.await { - error!("the HTTP server has terminated with the error: {err}"); - } else { - error!("the HTTP server has terminated with producing any errors"); - } - } - - pub async fn run(self) { - info!("Started NymNodeHTTPServer on {}", self.inner.local_addr()); - if let Some(mut task_client) = self.task_client { - tokio::select! { - _ = task_client.recv_with_delay() => { - debug!("NymNodeHTTPServer: Received shutdown"); - } - _ = Self::run_server_forever(self.inner) => { } - } - } else { - Self::run_server_forever(self.inner).await - } - - debug!("NymNodeHTTPServer: Exiting"); - } -} diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs deleted file mode 100644 index 8ae07c61e6..0000000000 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::state::metrics::MetricsAppState; -use axum::extract::{Query, State}; -use nym_http_api_common::{FormattedResponse, OutputParams}; -use nym_node_requests::api::v1::metrics::models::MixingStats; - -/// If applicable, returns mixing statistics information of this node. -/// This information is **PURELY** self-reported and in no way validated. -#[utoipa::path( - get, - path = "/mixing", - context_path = "/api/v1/metrics", - tag = "Metrics", - responses( - (status = 200, content( - ("application/json" = MixingStats), - ("application/yaml" = MixingStats) - )) - ), - params(OutputParams), -)] -pub(crate) async fn mixing_stats( - Query(output): Query, - State(metrics_state): State, -) -> MixingStatsResponse { - let output = output.output.unwrap_or_default(); - let response = metrics_state.mixing_stats.read().await.as_response(); - output.to_response(response) -} - -pub type MixingStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs deleted file mode 100644 index 59eceb8f89..0000000000 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::state::metrics::MetricsAppState; -use axum::extract::{Query, State}; -use nym_http_api_common::{FormattedResponse, OutputParams}; -use nym_node_requests::api::v1::metrics::models::SessionStats; - -/// If applicable, returns sessions statistics information of this node. -/// This information is **PURELY** self-reported and in no way validated. -#[utoipa::path( - get, - path = "/sessions", - context_path = "/api/v1/metrics", - tag = "Metrics", - responses( - (status = 200, content( - ("application/json" = SessionStats), - ("application/yaml" = SessionStats) - )) - ), - params(OutputParams), -)] -pub(crate) async fn sessions_stats( - Query(output): Query, - State(metrics_state): State, -) -> SessionStatsResponse { - let output = output.output.unwrap_or_default(); - let response = metrics_state.session_stats.read().await.as_response(); - output.to_response(response) -} - -pub type SessionStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs deleted file mode 100644 index c622ccf123..0000000000 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::state::metrics::MetricsAppState; -use axum::extract::{Query, State}; -use nym_http_api_common::{FormattedResponse, OutputParams}; -use nym_node_requests::api::v1::metrics::models::VerlocStats; - -/// If applicable, returns verloc statistics information of this node. -#[utoipa::path( - get, - path = "/verloc", - context_path = "/api/v1/metrics", - tag = "Metrics", - responses( - (status = 200, content( - ("application/json" = VerlocStats), - ("application/yaml" = VerlocStats) - )) - ), - params(OutputParams), -)] -pub(crate) async fn verloc_stats( - Query(output): Query, - State(metrics_state): State, -) -> VerlocStatsResponse { - let output = output.output.unwrap_or_default(); - let response = metrics_state.verloc.read().await.as_response(); - output.to_response(response) -} - -pub type VerlocStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/state/metrics.rs b/nym-node/nym-node-http-api/src/state/metrics.rs deleted file mode 100644 index 4caaada790..0000000000 --- a/nym-node/nym-node-http-api/src/state/metrics.rs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::state::AppState; -use axum::extract::FromRef; -use nym_node_requests::api::v1::metrics::models::{ - MixingStats, Session, SessionStats, VerlocResult, VerlocResultData, VerlocStats, -}; -use std::collections::HashMap; -use std::sync::Arc; -use time::macros::time; -use time::{Date, OffsetDateTime}; -use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - -pub use nym_node_requests::api::v1::metrics::models::{VerlocMeasurement, VerlocNodeResult}; - -type PacketsMap = HashMap; - -#[derive(Clone, Debug, Default)] -pub struct SharedMixingStats { - inner: Arc>, -} - -impl SharedMixingStats { - pub fn new() -> SharedMixingStats { - let now = OffsetDateTime::now_utc(); - - SharedMixingStats { - inner: Arc::new(RwLock::new(MixingStatsState { - update_time: now, - previous_update_time: now, - ..Default::default() - })), - } - } - - pub async fn read(&self) -> RwLockReadGuard<'_, MixingStatsState> { - self.inner.read().await - } - - pub async fn write(&self) -> RwLockWriteGuard<'_, MixingStatsState> { - self.inner.write().await - } -} - -#[derive(Debug)] -pub struct MixingStatsState { - pub update_time: OffsetDateTime, - - pub previous_update_time: OffsetDateTime, - - pub packets_received_since_startup: u64, - pub packets_sent_since_startup_all: u64, - pub packets_dropped_since_startup_all: u64, - pub packets_received_since_last_update: u64, - - // note: sent does not imply forwarded. We don't know if it was delivered successfully - pub packets_sent_since_last_update: PacketsMap, - - // we know for sure we dropped packets to those destinations - pub packets_explicitly_dropped_since_last_update: PacketsMap, -} - -impl MixingStatsState { - pub fn as_response(&self) -> MixingStats { - MixingStats { - update_time: self.update_time, - previous_update_time: self.previous_update_time, - received_since_startup: self.packets_received_since_startup, - sent_since_startup: self.packets_sent_since_startup_all, - dropped_since_startup: self.packets_dropped_since_startup_all, - received_since_last_update: self.packets_received_since_last_update, - sent_since_last_update: self.packets_sent_since_last_update.values().sum(), - dropped_since_last_update: self - .packets_explicitly_dropped_since_last_update - .values() - .sum(), - } - } -} - -impl Default for MixingStatsState { - fn default() -> Self { - MixingStatsState { - update_time: OffsetDateTime::UNIX_EPOCH, - previous_update_time: OffsetDateTime::UNIX_EPOCH, - packets_received_since_startup: 0, - packets_sent_since_startup_all: 0, - packets_dropped_since_startup_all: 0, - packets_received_since_last_update: 0, - packets_sent_since_last_update: Default::default(), - packets_explicitly_dropped_since_last_update: Default::default(), - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct SharedVerlocStats { - inner: Arc>, -} - -#[derive(Clone, Debug, Default)] -pub struct VerlocStatsState { - pub current_run_data: VerlocResultData, - pub previous_run_data: VerlocResultData, -} - -impl SharedVerlocStats { - pub async fn read(&self) -> RwLockReadGuard<'_, VerlocStatsState> { - self.inner.read().await - } - - pub async fn write(&self) -> RwLockWriteGuard<'_, VerlocStatsState> { - self.inner.write().await - } -} - -impl VerlocStatsState { - pub fn as_response(&self) -> VerlocStats { - let previous = if !self.previous_run_data.run_finished() { - VerlocResult::Unavailable - } else { - VerlocResult::Data(self.previous_run_data.clone()) - }; - - let current = if !self.current_run_data.run_finished() { - VerlocResult::MeasurementInProgress - } else { - VerlocResult::Data(self.current_run_data.clone()) - }; - - VerlocStats { previous, current } - } -} - -#[derive(Clone, Debug, Default)] -pub struct SharedSessionStats { - inner: Arc>, -} - -impl SharedSessionStats { - pub fn new() -> SharedSessionStats { - SharedSessionStats { - inner: Arc::new(RwLock::new(Default::default())), - } - } - - pub async fn read(&self) -> RwLockReadGuard<'_, SessionStatsState> { - self.inner.read().await - } - - pub async fn write(&self) -> RwLockWriteGuard<'_, SessionStatsState> { - self.inner.write().await - } -} - -type FinishedSessions = Vec<(u64, String)>; - -#[derive(Debug, Clone)] -pub struct SessionStatsState { - pub update_time: Date, - pub unique_active_users_count: u32, - pub unique_active_users_hashes: Vec, - pub session_started: u32, - pub sessions: FinishedSessions, -} - -impl SessionStatsState { - pub fn as_response(&self) -> SessionStats { - let sessions = self - .sessions - .clone() - .into_iter() - .map(|(duration_ms, typ)| Session { duration_ms, typ }) - .collect(); - SessionStats { - update_time: self.update_time.with_time(time!(0:00)).assume_utc(), - unique_active_users: self.unique_active_users_count, - unique_active_users_hashes: self.unique_active_users_hashes.clone(), - sessions, - sessions_started: self.session_started, - sessions_finished: self.sessions.len() as u32, - } - } -} - -impl Default for SessionStatsState { - fn default() -> Self { - SessionStatsState { - update_time: OffsetDateTime::UNIX_EPOCH.date(), - unique_active_users_count: 0, - unique_active_users_hashes: Default::default(), - session_started: 0, - sessions: Default::default(), - } - } -} - -#[derive(Debug, Clone, Default)] -pub struct MetricsAppState { - pub(crate) prometheus_access_token: Option, - - pub(crate) mixing_stats: SharedMixingStats, - - pub(crate) session_stats: SharedSessionStats, - - pub(crate) verloc: SharedVerlocStats, -} - -impl FromRef for MetricsAppState { - fn from_ref(app_state: &AppState) -> Self { - app_state.metrics.clone() - } -} diff --git a/nym-node/nym-node-http-api/src/state/mod.rs b/nym-node/nym-node-http-api/src/state/mod.rs deleted file mode 100644 index 67f16d1bd3..0000000000 --- a/nym-node/nym-node-http-api/src/state/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023-2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::state::metrics::{ - MetricsAppState, SharedMixingStats, SharedSessionStats, SharedVerlocStats, -}; -use tokio::time::Instant; - -pub mod metrics; - -#[derive(Debug, Clone)] -pub struct AppState { - pub(crate) startup_time: Instant, - - pub(crate) metrics: MetricsAppState, -} - -impl AppState { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - AppState { - // is it 100% accurate? - // no. - // does it have to be? - // also no. - startup_time: Instant::now(), - metrics: Default::default(), - } - } - - #[must_use] - pub fn with_mixing_stats(mut self, mixing_stats: SharedMixingStats) -> Self { - self.metrics.mixing_stats = mixing_stats; - self - } - - #[must_use] - pub fn with_sessions_stats(mut self, session_stats: SharedSessionStats) -> Self { - self.metrics.session_stats = session_stats; - self - } - - #[must_use] - pub fn with_verloc_stats(mut self, verloc_stats: SharedVerlocStats) -> Self { - self.metrics.verloc = verloc_stats; - self - } - - #[must_use] - pub fn with_metrics_key(mut self, bearer_token: impl Into>) -> Self { - self.metrics.prometheus_access_token = bearer_token.into(); - self - } -} diff --git a/nym-node/nym-node-metrics/Cargo.toml b/nym-node/nym-node-metrics/Cargo.toml new file mode 100644 index 0000000000..7797040114 --- /dev/null +++ b/nym-node/nym-node-metrics/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nym-node-metrics" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +dashmap = { workspace = true } +futures = { workspace = true } +tokio = { workspace = true, features = ["sync"] } +time = { workspace = true } +strum = { workspace = true } +tracing = { workspace = true } + +nym-metrics = { path = "../../common/nym-metrics" } +nym-statistics-common = { path = "../../common/statistics" } \ No newline at end of file diff --git a/nym-node/nym-node-metrics/src/entry.rs b/nym-node/nym-node-metrics/src/entry.rs new file mode 100644 index 0000000000..ab8da08fd6 --- /dev/null +++ b/nym-node/nym-node-metrics/src/entry.rs @@ -0,0 +1,112 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_statistics_common::hash_identifier; +use std::time::Duration; +use time::{Date, OffsetDateTime}; +use tokio::sync::{RwLock, RwLockReadGuard}; + +#[derive(Default)] +pub struct EntryStats { + sessions: RwLock, +} + +impl EntryStats { + pub async fn update_client_sessions(&self, new: ClientSessions) { + *self.sessions.write().await = new + } + + pub async fn client_sessions(&self) -> RwLockReadGuard { + self.sessions.read().await + } +} + +pub struct ClientSessions { + pub update_time: Date, + pub unique_users: Vec, + pub sessions_started: u32, + pub finished_sessions: Vec, +} + +impl Default for ClientSessions { + fn default() -> Self { + ClientSessions { + update_time: OffsetDateTime::UNIX_EPOCH.date(), + unique_users: vec![], + sessions_started: 0, + finished_sessions: vec![], + } + } +} + +impl ClientSessions { + pub fn new( + update_time: Date, + unique_users: Vec, + sessions_started: u32, + sessions: Vec, + ) -> Self { + ClientSessions { + update_time, + unique_users: unique_users.into_iter().map(hash_identifier).collect(), + sessions_started, + finished_sessions: sessions, + } + } +} + +pub struct FinishedSession { + pub duration: Duration, + pub typ: SessionType, +} + +impl FinishedSession { + pub fn new(duration: Duration, typ: SessionType) -> Self { + FinishedSession { duration, typ } + } +} + +#[derive(PartialEq, Copy, Clone, strum::Display, strum::EnumString)] +pub enum SessionType { + Vpn, + Mixnet, + Unknown, +} + +impl SessionType { + pub fn from_string>(s: S) -> Self { + s.as_ref().parse().unwrap_or(Self::Unknown) + } +} + +pub struct ActiveSession { + pub start: OffsetDateTime, + pub typ: SessionType, +} + +impl ActiveSession { + pub fn new(start_time: OffsetDateTime) -> Self { + ActiveSession { + start: start_time, + typ: SessionType::Unknown, + } + } + + pub fn set_type(&mut self, typ: SessionType) { + self.typ = typ; + } + + pub fn end_at(self, stop_time: OffsetDateTime) -> Option { + let session_duration = stop_time - self.start; + //ensure duration is positive to fit in a u64 + //u64::max milliseconds is 500k millenia so no overflow issue + if session_duration > Duration::ZERO { + Some(FinishedSession { + duration: session_duration.unsigned_abs(), + typ: self.typ, + }) + } else { + None + } + } +} diff --git a/nym-node/nym-node-metrics/src/events.rs b/nym-node/nym-node-metrics/src/events.rs new file mode 100644 index 0000000000..92fa3dbb94 --- /dev/null +++ b/nym-node/nym-node-metrics/src/events.rs @@ -0,0 +1,53 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use futures::channel::mpsc; +use futures::channel::mpsc::SendError; +pub use nym_statistics_common::gateways::GatewaySessionEvent; +use tracing::error; + +pub fn events_channels() -> (MetricEventsSender, MetricEventsReceiver) { + let (tx, rx) = mpsc::unbounded(); + (tx.into(), rx) +} + +#[derive(Clone)] +pub struct MetricEventsSender(mpsc::UnboundedSender); + +impl From> for MetricEventsSender { + fn from(tx: mpsc::UnboundedSender) -> Self { + MetricEventsSender(tx) + } +} + +impl MetricEventsSender { + pub fn report(&self, metric: impl Into) -> Result<(), SendError> { + self.0 + .unbounded_send(metric.into()) + .map_err(|err| err.into_send_error()) + } + + pub fn report_unchecked(&self, metric: impl Into) { + if let Err(err) = self.report(metric) { + error!("failed to send metric information: {err}") + } + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.0.len() + } +} + +pub type MetricEventsReceiver = mpsc::UnboundedReceiver; + +// please create a new variant per "category" of metrics +pub enum MetricsEvent { + GatewayClientSession(GatewaySessionEvent), +} + +impl From for MetricsEvent { + fn from(gateway_stats: GatewaySessionEvent) -> Self { + MetricsEvent::GatewayClientSession(gateway_stats) + } +} diff --git a/nym-node/nym-node-metrics/src/lib.rs b/nym-node/nym-node-metrics/src/lib.rs new file mode 100644 index 0000000000..58ab0f77f7 --- /dev/null +++ b/nym-node/nym-node-metrics/src/lib.rs @@ -0,0 +1,39 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::entry::EntryStats; +use crate::mixnet::MixingStats; +use crate::network::NetworkStats; +use std::ops::Deref; +use std::sync::Arc; + +pub mod entry; +pub mod events; +pub mod mixnet; +pub mod network; + +#[derive(Clone, Default)] +pub struct NymNodeMetrics { + inner: Arc, +} + +impl NymNodeMetrics { + pub fn new() -> Self { + NymNodeMetrics::default() + } +} + +impl Deref for NymNodeMetrics { + type Target = NymNodeMetricsInner; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[derive(Default)] +pub struct NymNodeMetricsInner { + pub mixnet: MixingStats, + pub entry: EntryStats, + + pub network: NetworkStats, +} diff --git a/nym-node/nym-node-metrics/src/mixnet.rs b/nym-node/nym-node-metrics/src/mixnet.rs new file mode 100644 index 0000000000..043d2d1e7c --- /dev/null +++ b/nym-node/nym-node-metrics/src/mixnet.rs @@ -0,0 +1,292 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use dashmap::DashMap; +use std::net::{IpAddr, SocketAddr}; +use std::sync::atomic::{AtomicI64, AtomicUsize, Ordering}; +use time::OffsetDateTime; + +#[derive(Default)] +pub struct MixingStats { + // updated on each packet + pub ingress: IngressMixingStats, + + // updated on each packet + pub egress: EgressMixingStats, + + // updated on a timer + pub legacy: LegacyMixingStats, +} + +impl MixingStats { + pub fn update_legacy_stats( + &self, + received_since_last_update: usize, + sent_since_last_update: usize, + dropped_since_last_update: usize, + update_timestamp: i64, + ) { + self.legacy + .received_since_last_update + .store(received_since_last_update, Ordering::Relaxed); + self.legacy + .sent_since_last_update + .store(sent_since_last_update, Ordering::Relaxed); + self.legacy + .dropped_since_last_update + .store(dropped_since_last_update, Ordering::Relaxed); + + let old_last = self.legacy.last_update_ts.load(Ordering::Acquire); + self.legacy + .previous_update_ts + .store(old_last, Ordering::Release); + self.legacy + .last_update_ts + .store(update_timestamp, Ordering::Release); + } + + pub fn ingress_malformed_packet(&self, source: IpAddr) { + self.ingress + .malformed_packets_received + .fetch_add(1, Ordering::Relaxed); + self.ingress.senders.entry(source).or_default().malformed += 1; + } + + pub fn ingress_received_forward_packet(&self, source: IpAddr) { + self.ingress + .forward_hop_packets_received + .fetch_add(1, Ordering::Relaxed); + self.ingress + .senders + .entry(source) + .or_default() + .forward_packets + .received += 1; + } + + pub fn ingress_received_final_hop_packet(&self, source: IpAddr) { + self.ingress + .final_hop_packets_received + .fetch_add(1, Ordering::Relaxed); + self.ingress + .senders + .entry(source) + .or_default() + .final_hop_packets + .received += 1; + } + + pub fn ingress_excessive_delay_packet(&self) { + self.ingress + .excessive_delay_packets + .fetch_add(1, Ordering::Relaxed); + } + + pub fn ingress_dropped_forward_packet(&self, source: IpAddr) { + self.ingress + .forward_hop_packets_dropped + .fetch_add(1, Ordering::Relaxed); + self.ingress + .senders + .entry(source) + .or_default() + .forward_packets + .dropped += 1; + } + + pub fn ingress_dropped_final_hop_packet(&self, source: IpAddr) { + self.ingress + .final_hop_packets_dropped + .fetch_add(1, Ordering::Relaxed); + self.ingress + .senders + .entry(source) + .or_default() + .final_hop_packets + .dropped += 1; + } + + pub fn egress_sent_forward_packet(&self, target: SocketAddr) { + self.egress + .forward_hop_packets_sent + .fetch_add(1, Ordering::Relaxed); + self.egress + .forward_recipients + .entry(target) + .or_default() + .sent += 1; + } + + pub fn egress_sent_ack(&self) { + self.egress.ack_packets_sent.fetch_add(1, Ordering::Relaxed); + } + + pub fn egress_dropped_forward_packet(&self, target: SocketAddr) { + self.egress + .forward_hop_packets_dropped + .fetch_add(1, Ordering::Relaxed); + self.egress + .forward_recipients + .entry(target) + .or_default() + .dropped += 1; + } + + pub fn egress_dropped_final_hop_packet(&self) { + todo!() + // self.egress + // .final_hop_packets_dropped + // .fetch_add(1, Ordering::Relaxed); + } +} + +#[derive(Clone, Copy, Default, PartialEq)] +pub struct EgressRecipientStats { + pub dropped: usize, + pub sent: usize, +} + +#[derive(Default)] +pub struct EgressMixingStats { + // this includes ACKS! + forward_hop_packets_sent: AtomicUsize, + + ack_packets_sent: AtomicUsize, + + forward_hop_packets_dropped: AtomicUsize, + + forward_recipients: DashMap, +} + +impl EgressMixingStats { + pub fn forward_hop_packets_sent(&self) -> usize { + self.forward_hop_packets_sent.load(Ordering::Relaxed) + } + + pub fn ack_packets_sent(&self) -> usize { + self.ack_packets_sent.load(Ordering::Relaxed) + } + + pub fn forward_hop_packets_dropped(&self) -> usize { + self.forward_hop_packets_dropped.load(Ordering::Relaxed) + } + + pub fn forward_recipients(&self) -> &DashMap { + &self.forward_recipients + } + + pub fn remove_stale_forward_recipient(&self, recipient: SocketAddr) { + self.forward_recipients.remove(&recipient); + } +} + +#[derive(Clone, Copy, Default, PartialEq)] +pub struct IngressPacketsStats { + pub dropped: usize, + pub received: usize, +} + +#[derive(Clone, Copy, Default, PartialEq)] +pub struct IngressRecipientStats { + pub forward_packets: IngressPacketsStats, + pub final_hop_packets: IngressPacketsStats, + pub malformed: usize, +} + +#[derive(Default)] +pub struct IngressMixingStats { + // forward hop packets (i.e. to mixnode) + forward_hop_packets_received: AtomicUsize, + + // final hop packets (i.e. to gateway) + final_hop_packets_received: AtomicUsize, + + // packets that failed to get unwrapped + malformed_packets_received: AtomicUsize, + + // (forward) packets that had invalid, i.e. too large, delays + excessive_delay_packets: AtomicUsize, + + // forward hop packets (i.e. to mixnode) + forward_hop_packets_dropped: AtomicUsize, + + // final hop packets (i.e. to gateway) + final_hop_packets_dropped: AtomicUsize, + + senders: DashMap, +} + +impl IngressMixingStats { + pub fn forward_hop_packets_received(&self) -> usize { + self.forward_hop_packets_received.load(Ordering::Relaxed) + } + + pub fn final_hop_packets_received(&self) -> usize { + self.final_hop_packets_received.load(Ordering::Relaxed) + } + + pub fn malformed_packets_received(&self) -> usize { + self.malformed_packets_received.load(Ordering::Relaxed) + } + + pub fn excessive_delay_packets(&self) -> usize { + self.excessive_delay_packets.load(Ordering::Relaxed) + } + + pub fn forward_hop_packets_dropped(&self) -> usize { + self.forward_hop_packets_dropped.load(Ordering::Relaxed) + } + + pub fn final_hop_packets_dropped(&self) -> usize { + self.final_hop_packets_dropped.load(Ordering::Relaxed) + } + + pub fn senders(&self) -> &DashMap { + &self.senders + } + + pub fn remove_stale_sender(&self, sender: IpAddr) { + self.senders.remove(&sender); + } +} + +#[derive(Debug, Default)] +pub struct LegacyMixingStats { + last_update_ts: AtomicI64, + previous_update_ts: AtomicI64, + + received_since_last_update: AtomicUsize, + + // note: sent does not imply forwarded. We don't know if it was delivered successfully + sent_since_last_update: AtomicUsize, + + // we know for sure we dropped those packets + dropped_since_last_update: AtomicUsize, +} + +impl LegacyMixingStats { + pub fn last_update(&self) -> OffsetDateTime { + // SAFETY: all values put here are guaranteed to be valid timestamps + #[allow(clippy::unwrap_used)] + OffsetDateTime::from_unix_timestamp(self.last_update_ts.load(Ordering::Relaxed)).unwrap() + } + + pub fn previous_update(&self) -> OffsetDateTime { + // SAFETY: all values put here are guaranteed to be valid timestamps + #[allow(clippy::unwrap_used)] + OffsetDateTime::from_unix_timestamp(self.previous_update_ts.load(Ordering::Relaxed)) + .unwrap() + } + + pub fn received_since_last_update(&self) -> usize { + self.received_since_last_update.load(Ordering::Relaxed) + } + + pub fn sent_since_last_update(&self) -> usize { + self.sent_since_last_update.load(Ordering::Relaxed) + } + + pub fn dropped_since_last_update(&self) -> usize { + self.dropped_since_last_update.load(Ordering::Relaxed) + } +} diff --git a/nym-node/nym-node-metrics/src/network.rs b/nym-node/nym-node-metrics/src/network.rs new file mode 100644 index 0000000000..de00d78560 --- /dev/null +++ b/nym-node/nym-node-metrics/src/network.rs @@ -0,0 +1,27 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[derive(Default)] +pub struct NetworkStats { + // for now just experiment with basic data, we could always extend it + active_ingress_mixnet_connections: AtomicUsize, +} + +impl NetworkStats { + pub fn new_active_ingress_mixnet_client(&self) { + self.active_ingress_mixnet_connections + .fetch_add(1, Ordering::Relaxed); + } + + pub fn disconnected_ingress_mixnet_client(&self) { + self.active_ingress_mixnet_connections + .fetch_sub(1, Ordering::Relaxed); + } + + pub fn active_ingress_mixnet_connections_count(&self) -> usize { + self.active_ingress_mixnet_connections + .load(Ordering::Relaxed) + } +} diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs index 4620db05f9..8e430534c5 100644 --- a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs @@ -1,304 +1,177 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_crypto::asymmetric::identity::{self, serde_helpers::bs58_ed25519_pubkey}; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; -use std::fmt; -use std::fmt::{Display, Formatter}; -use std::time::Duration; -use time::OffsetDateTime; +pub use mixing::*; +pub use session::*; +pub use verloc::*; -#[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct MixingStats { - #[serde(with = "time::serde::rfc3339")] - pub update_time: OffsetDateTime, +pub mod packets { + use serde::{Deserialize, Serialize}; - #[serde(with = "time::serde::rfc3339")] - pub previous_update_time: OffsetDateTime, + #[derive(Serialize, Deserialize, Debug, Clone, Copy)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct PacketsStats { + pub ingress_mixing: IngressMixingStats, + pub egress_mixing: EgressMixingStats, + } - pub received_since_startup: u64, + #[derive(Serialize, Deserialize, Debug, Clone, Copy)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct IngressMixingStats { + // forward hop packets (i.e. to mixnode) + pub forward_hop_packets_received: usize, - // note: sent does not imply forwarded. We don't know if it was delivered successfully - pub sent_since_startup: u64, + // final hop packets (i.e. to gateway) + pub final_hop_packets_received: usize, - // we know for sure we dropped those packets - pub dropped_since_startup: u64, + // packets that failed to get unwrapped + pub malformed_packets_received: usize, - pub received_since_last_update: u64, + // (forward) packets that had invalid, i.e. too large, delays + pub excessive_delay_packets: usize, - // note: sent does not imply forwarded. We don't know if it was delivered successfully - pub sent_since_last_update: u64, + // forward hop packets (i.e. to mixnode) + pub forward_hop_packets_dropped: usize, - // we know for sure we dropped those packets - pub dropped_since_last_update: u64, -} + // final hop packets (i.e. to gateway) + pub final_hop_packets_dropped: usize, + } -#[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct Session { - pub duration_ms: u64, - pub typ: String, -} + #[derive(Serialize, Deserialize, Debug, Clone, Copy)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct EgressMixingStats { + pub forward_hop_packets_sent: usize, -#[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct SessionStats { - #[serde(with = "time::serde::rfc3339")] - pub update_time: OffsetDateTime, + pub forward_hop_packets_dropped: usize, - pub unique_active_users: u32, - - #[serde(default = "Vec::new")] // field was added later - pub unique_active_users_hashes: Vec, - - pub sessions: Vec, - - pub sessions_started: u32, - - pub sessions_finished: u32, -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct VerlocStats { - pub previous: VerlocResult, - pub current: VerlocResult, -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -#[serde(rename_all = "camelCase")] -pub enum VerlocResult { - Data(VerlocResultData), - MeasurementInProgress, - #[default] - Unavailable, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct VerlocResultData { - pub nodes_tested: usize, - - #[serde(with = "time::serde::rfc3339")] - pub run_started: OffsetDateTime, - - #[serde(with = "time::serde::rfc3339::option")] - pub run_finished: Option, - - pub results: Vec, -} - -impl Default for VerlocResultData { - fn default() -> Self { - VerlocResultData { - nodes_tested: 0, - run_started: OffsetDateTime::now_utc(), - run_finished: None, - results: vec![], - } + pub ack_packets_sent: usize, } } -impl VerlocResultData { - pub fn run_finished(&self) -> bool { - self.run_finished.is_some() +pub mod mixing { + use serde::{Deserialize, Serialize}; + use time::OffsetDateTime; + + #[derive(Serialize, Deserialize, Debug, Clone)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct LegacyMixingStats { + #[serde(with = "time::serde::rfc3339")] + pub update_time: OffsetDateTime, + + #[serde(with = "time::serde::rfc3339")] + pub previous_update_time: OffsetDateTime, + + pub received_since_startup: u64, + + // note: sent does not imply forwarded. We don't know if it was delivered successfully + pub sent_since_startup: u64, + + // we know for sure we dropped those packets + pub dropped_since_startup: u64, + + pub received_since_last_update: u64, + + // note: sent does not imply forwarded. We don't know if it was delivered successfully + pub sent_since_last_update: u64, + + // we know for sure we dropped those packets + pub dropped_since_last_update: u64, } } -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct VerlocNodeResult { - #[serde(with = "bs58_ed25519_pubkey")] - pub node_identity: identity::PublicKey, +pub mod verloc { + use nym_crypto::asymmetric::ed25519::{self, serde_helpers::bs58_ed25519_pubkey}; + use serde::{Deserialize, Serialize}; + use std::time::Duration; + use time::OffsetDateTime; - pub latest_measurement: Option, -} + #[derive(Serialize, Deserialize, Default, Debug, Clone)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct VerlocStats { + pub previous: VerlocResult, + pub current: VerlocResult, + } -impl VerlocNodeResult { - pub fn new( - node_identity: identity::PublicKey, - latest_measurement: Option, - ) -> Self { - VerlocNodeResult { - node_identity, - latest_measurement, - } + #[derive(Serialize, Deserialize, Default, Debug, Clone)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + #[serde(rename_all = "camelCase")] + pub enum VerlocResult { + Data(VerlocResultData), + MeasurementInProgress, + #[default] + Unavailable, + } + + #[derive(Serialize, Deserialize, Debug, Clone)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct VerlocResultData { + pub nodes_tested: usize, + + #[serde(with = "time::serde::rfc3339")] + pub run_started: OffsetDateTime, + + #[serde(with = "time::serde::rfc3339::option")] + pub run_finished: Option, + + pub results: Vec, + } + + #[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct VerlocNodeResult { + #[serde(with = "bs58_ed25519_pubkey")] + pub node_identity: ed25519::PublicKey, + + pub latest_measurement: Option, + } + + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct VerlocMeasurement { + /// Minimum RTT duration it took to receive an echo packet. + #[serde(serialize_with = "humantime_serde::serialize")] + pub minimum: Duration, + + /// Average RTT duration it took to receive the echo packets. + #[serde(serialize_with = "humantime_serde::serialize")] + pub mean: Duration, + + /// Maximum RTT duration it took to receive an echo packet. + #[serde(serialize_with = "humantime_serde::serialize")] + pub maximum: Duration, + + /// The standard deviation of the RTT duration it took to receive the echo packets. + #[serde(serialize_with = "humantime_serde::serialize")] + pub standard_deviation: Duration, } } -impl PartialOrd for VerlocNodeResult { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VerlocNodeResult { - fn cmp(&self, other: &Self) -> Ordering { - // if both have measurement, compare measurements - // then if only one have measurement, prefer that one - // completely ignore identity as it makes no sense to order by it - if let Some(self_measurement) = &self.latest_measurement { - if let Some(other_measurement) = &other.latest_measurement { - self_measurement.cmp(other_measurement) - } else { - Ordering::Less - } - } else if other.latest_measurement.is_some() { - Ordering::Greater - } else { - Ordering::Equal - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct VerlocMeasurement { - /// Minimum RTT duration it took to receive an echo packet. - #[serde(serialize_with = "humantime_serde::serialize")] - pub minimum: Duration, - - /// Average RTT duration it took to receive the echo packets. - #[serde(serialize_with = "humantime_serde::serialize")] - pub mean: Duration, - - /// Maximum RTT duration it took to receive an echo packet. - #[serde(serialize_with = "humantime_serde::serialize")] - pub maximum: Duration, - - /// The standard deviation of the RTT duration it took to receive the echo packets. - #[serde(serialize_with = "humantime_serde::serialize")] - pub standard_deviation: Duration, -} - -impl VerlocMeasurement { - pub fn new(raw_results: &[Duration]) -> Self { - let minimum = raw_results.iter().min().copied().unwrap_or_default(); - let maximum = raw_results.iter().max().copied().unwrap_or_default(); - - let mean = Self::duration_mean(raw_results); - let standard_deviation = Self::duration_standard_deviation(raw_results, mean); - - VerlocMeasurement { - minimum, - mean, - maximum, - standard_deviation, - } - } - - fn duration_mean(data: &[Duration]) -> Duration { - if data.is_empty() { - return Default::default(); - } - - let sum = data.iter().sum::(); - let count = data.len() as u32; - - sum / count - } - - fn duration_standard_deviation(data: &[Duration], mean: Duration) -> Duration { - if data.is_empty() { - return Default::default(); - } - - let variance_micros = data - .iter() - .map(|&value| { - // make sure we don't underflow - let diff = if mean > value { - mean - value - } else { - value - mean - }; - // we don't need nanos precision - let diff_micros = diff.as_micros(); - diff_micros * diff_micros - }) - .sum::() - / data.len() as u128; - - // we shouldn't really overflow as our differences shouldn't be larger than couple seconds at the worst possible case scenario - let std_deviation_micros = (variance_micros as f64).sqrt() as u64; - Duration::from_micros(std_deviation_micros) - } -} - -impl Display for VerlocMeasurement { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "rtt min/avg/max/mdev = {} / {} / {} / {}", - humantime::format_duration(self.minimum), - humantime::format_duration(self.mean), - humantime::format_duration(self.maximum), - humantime::format_duration(self.standard_deviation) - ) - } -} - -impl PartialOrd for VerlocMeasurement { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VerlocMeasurement { - fn cmp(&self, other: &Self) -> Ordering { - // minimum value is most important, then look at standard deviation, then mean and finally maximum - let min_cmp = self.minimum.cmp(&other.minimum); - if min_cmp != Ordering::Equal { - return min_cmp; - } - let std_dev_cmp = self.standard_deviation.cmp(&other.standard_deviation); - if std_dev_cmp != Ordering::Equal { - return std_dev_cmp; - } - let std_dev_cmp = self.mean.cmp(&other.mean); - if std_dev_cmp != Ordering::Equal { - return std_dev_cmp; - } - self.maximum.cmp(&other.maximum) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sorting_vec_of_verlocs() { - let some_identity = - identity::PublicKey::from_base58_string("Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt") - .unwrap(); - let no_measurement = VerlocNodeResult::new(some_identity, None); - let low_min = VerlocNodeResult::new( - some_identity, - Some(VerlocMeasurement { - minimum: Duration::from_millis(42), - mean: Duration::from_millis(43), - maximum: Duration::from_millis(44), - standard_deviation: Duration::from_millis(45), - }), - ); - let higher_min = VerlocNodeResult::new( - some_identity, - Some(VerlocMeasurement { - minimum: Duration::from_millis(420), - mean: Duration::from_millis(430), - maximum: Duration::from_millis(440), - standard_deviation: Duration::from_millis(450), - }), - ); - - let mut vec_verloc = vec![no_measurement, low_min, no_measurement, higher_min]; - vec_verloc.sort(); - - let expected_sorted = vec![low_min, higher_min, no_measurement, no_measurement]; - assert_eq!(expected_sorted, vec_verloc); +pub mod session { + use serde::{Deserialize, Serialize}; + use time::OffsetDateTime; + + #[derive(Serialize, Deserialize, Debug, Clone)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct Session { + pub duration_ms: u64, + pub typ: String, + } + + #[derive(Serialize, Deserialize, Debug, Clone)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct SessionStats { + #[serde(with = "time::serde::rfc3339")] + pub update_time: OffsetDateTime, + + pub unique_active_users: u32, + + #[serde(default = "Vec::new")] // field was added later + pub unique_active_users_hashes: Vec, + + pub sessions: Vec, + + pub sessions_started: u32, + + pub sessions_finished: u32, } } diff --git a/nym-node/nym-node-requests/src/lib.rs b/nym-node/nym-node-requests/src/lib.rs index 983b1a6aec..661936797a 100644 --- a/nym-node/nym-node-requests/src/lib.rs +++ b/nym-node/nym-node-requests/src/lib.rs @@ -64,12 +64,14 @@ pub mod routes { pub mod metrics { use super::*; - pub const MIXING: &str = "/mixing"; + pub const LEGACY_MIXING: &str = "/mixing"; + pub const PACKETS_STATS: &str = "/packets-stats"; pub const SESSIONS: &str = "/sessions"; pub const VERLOC: &str = "/verloc"; pub const PROMETHEUS: &str = "/prometheus"; - absolute_route!(mixing_absolute, metrics_absolute(), MIXING); + absolute_route!(legacy_mixing_absolute, metrics_absolute(), LEGACY_MIXING); + absolute_route!(packets_stats_absolute, metrics_absolute(), PACKETS_STATS); absolute_route!(sessions_absolute, metrics_absolute(), SESSIONS); absolute_route!(verloc_absolute, metrics_absolute(), VERLOC); absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS); diff --git a/nym-node/src/cli/commands/bonding_information.rs b/nym-node/src/cli/commands/bonding_information.rs index cefa4da954..a864e14cbb 100644 --- a/nym-node/src/cli/commands/bonding_information.rs +++ b/nym-node/src/cli/commands/bonding_information.rs @@ -2,25 +2,16 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::cli::helpers::ConfigArgs; -use crate::env::vars::NYMNODE_MODE_ARG; -use crate::node::bonding_information::BondingInformationV1; +use crate::config::upgrade_helpers::try_load_current_config; +use crate::error::NymNodeError; +use crate::node::bonding_information::BondingInformation; use nym_bin_common::output_format::OutputFormat; -use nym_node::config::upgrade_helpers::try_load_current_config; -use nym_node::config::NodeMode; -use nym_node::error::NymNodeError; #[derive(Debug, clap::Args)] pub struct Args { #[clap(flatten)] pub(crate) config: ConfigArgs, - #[clap( - long, - value_enum, - env = NYMNODE_MODE_ARG - )] - pub(crate) mode: Option, - /// Specify the output format of the bonding information (`text` or `json`) #[clap( short, @@ -32,10 +23,7 @@ pub struct Args { pub async fn execute(args: Args) -> Result<(), NymNodeError> { let config = try_load_current_config(args.config.config_path()).await?; - let mut info = BondingInformationV1::try_load(&config)?; - if let Some(mode) = args.mode { - info = info.with_mode(mode) - } + let info = BondingInformation::try_load(&config)?; args.output.to_stdout(&info); Ok(()) } diff --git a/nym-node/src/cli/commands/build_info.rs b/nym-node/src/cli/commands/build_info.rs index e8f28ba3c4..612f681e4e 100644 --- a/nym-node/src/cli/commands/build_info.rs +++ b/nym-node/src/cli/commands/build_info.rs @@ -1,9 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::error::NymNodeError; use nym_bin_common::bin_info_owned; use nym_bin_common::output_format::OutputFormat; -use nym_node::error::NymNodeError; #[derive(clap::Args, Debug)] pub(crate) struct Args { diff --git a/nym-node/src/cli/commands/migrate.rs b/nym-node/src/cli/commands/migrate.rs index efb721245d..b47ee6853c 100644 --- a/nym-node/src/cli/commands/migrate.rs +++ b/nym-node/src/cli/commands/migrate.rs @@ -1,9 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::error::NymNodeError; use colored::Color::TrueColor; use colored::Colorize; -use nym_node::error::NymNodeError; #[derive(clap::Args, Debug)] pub(crate) struct Args { diff --git a/nym-node/src/cli/commands/node_details.rs b/nym-node/src/cli/commands/node_details.rs index 64e6a4f3ac..1b8ec69360 100644 --- a/nym-node/src/cli/commands/node_details.rs +++ b/nym-node/src/cli/commands/node_details.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::cli::helpers::ConfigArgs; +use crate::config::upgrade_helpers::try_load_current_config; +use crate::error::NymNodeError; use crate::node::NymNode; use nym_bin_common::output_format::OutputFormat; -use nym_node::config::upgrade_helpers::try_load_current_config; -use nym_node::error::NymNodeError; #[derive(Debug, clap::Args)] pub(crate) struct Args { @@ -23,7 +23,7 @@ pub(crate) struct Args { pub async fn execute(args: Args) -> Result<(), NymNodeError> { let config = try_load_current_config(args.config.config_path()).await?; - let details = NymNode::new(config).await?.display_details(); + let details = NymNode::new(config).await?.display_details()?; args.output.to_stdout(&details); Ok(()) } diff --git a/nym-node/src/cli/commands/run/args.rs b/nym-node/src/cli/commands/run/args.rs index 68e59fd44c..d366d0169b 100644 --- a/nym-node/src/cli/commands/run/args.rs +++ b/nym-node/src/cli/commands/run/args.rs @@ -2,14 +2,14 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::cli::helpers::{ - ConfigArgs, EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, MixnetArgs, MixnodeArgs, - WireguardArgs, + ConfigArgs, EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, MetricsArgs, MixnetArgs, + VerlocArgs, WireguardArgs, }; +use crate::config::persistence::NymNodePaths; +use crate::config::{Config, ConfigBuilder, NodeMode, NodeModes}; use crate::env::vars::*; +use crate::error::NymNodeError; use nym_bin_common::output_format::OutputFormat; -use nym_node::config::persistence::NymNodePaths; -use nym_node::config::{Config, ConfigBuilder, NodeMode}; -use nym_node::error::NymNodeError; use std::path::PathBuf; use zeroize::Zeroizing; @@ -55,13 +55,25 @@ pub(crate) struct Args { )] pub(crate) local: bool, - /// Specifies the current mode of this nym-node. + /// Specifies the current mode(s) of this nym-node. #[clap( long, value_enum, - env = NYMNODE_MODE_ARG + env = NYMNODE_MODE_ARG, + num_args(0..=3), + group = "node_mode" )] - pub(crate) mode: Option, + pub(crate) mode: Option>, + + /// Specifies the current mode(s) of this nym-node as a single flag. + #[clap( + long, + value_enum, + env = NYMNODE_MODES_ARG, + value_delimiter = ',', + group = "node_mode" + )] + pub(crate) modes: Vec, /// If this node has been initialised before, specify whether to write any new changes to the config file. #[clap( @@ -99,11 +111,14 @@ pub(crate) struct Args { #[clap(flatten)] mixnet: MixnetArgs, + #[clap(flatten)] + metrics: MetricsArgs, + #[clap(flatten)] wireguard: WireguardArgs, #[clap(flatten)] - mixnode: MixnodeArgs, + verloc: VerlocArgs, #[clap(flatten)] entry_gateway: EntryGatewayArgs, @@ -119,6 +134,18 @@ impl Args { } impl Args { + pub(crate) fn custom_modes(&self) -> Option { + if let Some(explicit_modes) = &self.mode { + return Some(explicit_modes.as_slice().into()); + } + + if !self.modes.is_empty() { + return Some(self.modes.as_slice().into()); + } + + None + } + pub(crate) fn build_config(self) -> Result { let config_path = self.config.config_path(); let data_dir = Config::default_data_directory(&config_path)?; @@ -133,35 +160,41 @@ impl Args { })?; let config = ConfigBuilder::new(id, config_path.clone(), data_dir.clone()) - .with_mode(self.mode.unwrap_or_default()) + // the old default behaviour of running in mixnode mode if nothing is explicitly set + .with_modes( + self.custom_modes() + .unwrap_or(*NodeModes::default().with_mixnode()), + ) .with_host(self.host.build_config_section()) .with_http(self.http.build_config_section()) .with_mixnet(self.mixnet.build_config_section()) .with_wireguard(self.wireguard.build_config_section(&data_dir)) .with_storage_paths(NymNodePaths::new(&data_dir)) - .with_mixnode(self.mixnode.build_config_section()) - .with_entry_gateway(self.entry_gateway.build_config_section(&data_dir)) - .with_exit_gateway(self.exit_gateway.build_config_section(&data_dir)) + .with_verloc(self.verloc.build_config_section()) + .with_metrics(self.metrics.build_config_section()) + .with_gateway_tasks(self.entry_gateway.build_config_section(&data_dir)) + .with_service_providers(self.exit_gateway.build_config_section(&data_dir)) .build(); Ok(config) } pub(crate) fn override_config(self, mut config: Config) -> Config { - if let Some(mode) = self.mode { - config.mode = mode; + if let Some(modes) = self.custom_modes() { + config.modes = modes; } + config.host = self.host.override_config_section(config.host); config.http = self.http.override_config_section(config.http); config.mixnet = self.mixnet.override_config_section(config.mixnet); config.wireguard = self.wireguard.override_config_section(config.wireguard); - config.mixnode = self.mixnode.override_config_section(config.mixnode); - config.entry_gateway = self + config.metrics = self.metrics.override_config_section(config.metrics); + config.gateway_tasks = self .entry_gateway - .override_config_section(config.entry_gateway); - config.exit_gateway = self + .override_config_section(config.gateway_tasks); + config.service_providers = self .exit_gateway - .override_config_section(config.exit_gateway); + .override_config_section(config.service_providers); config } } diff --git a/nym-node/src/cli/commands/run/mod.rs b/nym-node/src/cli/commands/run/mod.rs index 8135e66f8c..bcaa1e5106 100644 --- a/nym-node/src/cli/commands/run/mod.rs +++ b/nym-node/src/cli/commands/run/mod.rs @@ -1,15 +1,14 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::bonding_information::BondingInformationV1; +use crate::config::upgrade_helpers::try_load_current_config; +use crate::error::NymNodeError; +use crate::node::bonding_information::BondingInformation; use crate::node::NymNode; use nym_config::helpers::SPECIAL_ADDRESSES; -use nym_node::config::upgrade_helpers::try_load_current_config; -use nym_node::error::NymNodeError; use std::fs; use std::net::IpAddr; -use tracing::log::warn; -use tracing::{debug, info, trace}; +use tracing::{debug, info, trace, warn}; mod args; @@ -80,6 +79,10 @@ pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> { config }; + if !config.modes.any_enabled() { + warn!("this node is going to run without mixnode or gateway support! consider providing `mode` value"); + } + if config.host.public_ips.is_empty() { return Err(NymNodeError::NoPublicIps); } @@ -95,11 +98,8 @@ pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> { "writing bonding information to '{}'", bonding_info_path.display() ); - let info = BondingInformationV1::from_data( - nym_node.mode(), - nym_node.ed25519_identity_key().to_base58_string(), - nym_node.x25519_sphinx_key().to_base58_string(), - ); + let info = + BondingInformation::from_data(nym_node.config(), *nym_node.ed25519_identity_key()); let data = output.format(&info); fs::write(&bonding_info_path, data).map_err(|source| { NymNodeError::BondingInfoWriteFailure { diff --git a/nym-node/src/cli/commands/sign.rs b/nym-node/src/cli/commands/sign.rs index d22b3c9e28..ab22077396 100644 --- a/nym-node/src/cli/commands/sign.rs +++ b/nym-node/src/cli/commands/sign.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::cli::helpers::ConfigArgs; +use crate::config::upgrade_helpers::try_load_current_config; +use crate::error::NymNodeError; use crate::node::helpers::load_ed25519_identity_keypair; use nym_bin_common::output_format::OutputFormat; use nym_crypto::asymmetric::identity; -use nym_node::config::upgrade_helpers::try_load_current_config; -use nym_node::error::NymNodeError; use nym_types::helpers::ConsoleSigningOutput; // I don't think it makes sense to expose 'text' and 'contract-msg' as env variables diff --git a/nym-node/src/cli/helpers.rs b/nym-node/src/cli/helpers.rs index 4ca33dbed3..b17a86be1b 100644 --- a/nym-node/src/cli/helpers.rs +++ b/nym-node/src/cli/helpers.rs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: GPL-3.0-only use super::DEFAULT_NYMNODE_ID; +use crate::config; +use crate::config::default_config_filepath; use crate::env::vars::*; use celes::Country; use clap::builder::ArgPredicate; use clap::Args; -use nym_node::config; -use nym_node::config::default_config_filepath; use std::net::{IpAddr, SocketAddr}; use std::path::{Path, PathBuf}; use url::Url; @@ -313,7 +313,7 @@ impl WireguardArgs { } #[derive(clap::Args, Debug)] -pub(crate) struct MixnodeArgs { +pub(crate) struct VerlocArgs { /// Socket address this node will use for binding its verloc API. /// default: `0.0.0.0:1790` #[clap( @@ -332,21 +332,43 @@ pub(crate) struct MixnodeArgs { pub(crate) verloc_announce_port: Option, } -impl MixnodeArgs { - // TODO: could we perhaps make a clap error here and call `safe_exit` instead? - pub(crate) fn build_config_section(self) -> config::MixnodeConfig { - self.override_config_section(config::MixnodeConfig::new_default()) +impl VerlocArgs { + pub(crate) fn build_config_section(self) -> config::Verloc { + self.override_config_section(config::Verloc::default()) + } + + pub(crate) fn override_config_section(self, mut section: config::Verloc) -> config::Verloc { + if let Some(bind_address) = self.verloc_bind_address { + section.bind_address = bind_address + } + if let Some(announce_port) = self.verloc_announce_port { + section.announce_port = Some(announce_port) + } + section + } +} + +#[derive(clap::Args, Debug)] +pub(crate) struct MetricsArgs { + /// Specify whether running statistics of this node should be logged to the console. + #[clap( + long, + env = NYMNODE_ENABLE_CONSOLE_LOGGING + )] + enable_console_logging: Option, +} + +impl MetricsArgs { + pub(crate) fn build_config_section(self) -> config::MetricsConfig { + self.override_config_section(config::MetricsConfig::default()) } pub(crate) fn override_config_section( self, - mut section: config::MixnodeConfig, - ) -> config::MixnodeConfig { - if let Some(bind_address) = self.verloc_bind_address { - section.verloc.bind_address = bind_address - } - if let Some(announce_port) = self.verloc_announce_port { - section.verloc.announce_port = Some(announce_port) + mut section: config::MetricsConfig, + ) -> config::MetricsConfig { + if let Some(enable_console_logging) = self.enable_console_logging { + section.debug.log_stats_to_console = enable_console_logging; } section } @@ -400,14 +422,14 @@ impl EntryGatewayArgs { pub(crate) fn build_config_section>( self, data_dir: P, - ) -> config::EntryGatewayConfig { - self.override_config_section(config::EntryGatewayConfig::new_default(data_dir)) + ) -> config::GatewayTasksConfig { + self.override_config_section(config::GatewayTasksConfig::new_default(data_dir)) } pub(crate) fn override_config_section( self, - mut section: config::EntryGatewayConfig, - ) -> config::EntryGatewayConfig { + mut section: config::GatewayTasksConfig, + ) -> config::GatewayTasksConfig { if let Some(bind_address) = self.entry_bind_address { section.bind_address = bind_address } @@ -448,14 +470,14 @@ impl ExitGatewayArgs { pub(crate) fn build_config_section>( self, data_dir: P, - ) -> config::ExitGatewayConfig { - self.override_config_section(config::ExitGatewayConfig::new_default(data_dir)) + ) -> config::ServiceProvidersConfig { + self.override_config_section(config::ServiceProvidersConfig::new_default(data_dir)) } pub(crate) fn override_config_section( self, - mut section: config::ExitGatewayConfig, - ) -> config::ExitGatewayConfig { + mut section: config::ServiceProvidersConfig, + ) -> config::ServiceProvidersConfig { if let Some(upstream_exit_policy) = self.upstream_exit_policy_url { section.upstream_exit_policy_url = upstream_exit_policy } diff --git a/nym-node/src/cli/mod.rs b/nym-node/src/cli/mod.rs index 762e2d9bce..d3374c7fdf 100644 --- a/nym-node/src/cli/mod.rs +++ b/nym-node/src/cli/mod.rs @@ -3,9 +3,9 @@ use crate::cli::commands::{bonding_information, build_info, migrate, node_details, run, sign}; use crate::env::vars::{NYMNODE_CONFIG_ENV_FILE_ARG, NYMNODE_NO_BANNER_ARG}; +use crate::error::NymNodeError; use clap::{Parser, Subcommand}; use nym_bin_common::bin_info; -use nym_node::error::NymNodeError; use std::sync::OnceLock; mod commands; diff --git a/nym-node/src/config/exit_gateway.rs b/nym-node/src/config/exit_gateway.rs deleted file mode 100644 index 20aa884c08..0000000000 --- a/nym-node/src/config/exit_gateway.rs +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::config::helpers::ephemeral_gateway_config; -use crate::config::persistence::ExitGatewayPaths; -use crate::config::Config; -use crate::error::ExitGatewayError; -use nym_client_core_config_types::DebugConfig as ClientDebugConfig; -use nym_config::defaults::mainnet; -use nym_gateway::node::{ - LocalAuthenticatorOpts, LocalIpPacketRouterOpts, LocalNetworkRequesterOpts, -}; -use serde::{Deserialize, Serialize}; -use std::path::Path; -use url::Url; - -use super::{ - helpers::{base_client_config, EphemeralConfig}, - LocalWireguardOpts, -}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExitGatewayConfig { - pub storage_paths: ExitGatewayPaths, - - /// specifies whether this exit node should run in 'open-proxy' mode - /// and thus would attempt to resolve **ANY** request it receives. - pub open_proxy: bool, - - /// Specifies the url for an upstream source of the exit policy used by this node. - pub upstream_exit_policy_url: Url, - - pub network_requester: NetworkRequester, - - pub ip_packet_router: IpPacketRouter, - - #[serde(default)] - pub debug: Debug, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Debug { - /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. - pub message_retrieval_limit: i64, -} - -impl Debug { - const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; -} - -impl Default for Debug { - fn default() -> Self { - Debug { - message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, - } - } -} - -impl ExitGatewayConfig { - pub fn new_default>(data_dir: P) -> Self { - #[allow(clippy::expect_used)] - // SAFETY: - // we expect our default values to be well-formed - ExitGatewayConfig { - storage_paths: ExitGatewayPaths::new(data_dir), - open_proxy: false, - upstream_exit_policy_url: mainnet::EXIT_POLICY_URL - .parse() - .expect("invalid default exit policy URL"), - network_requester: Default::default(), - ip_packet_router: Default::default(), - debug: Default::default(), - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -pub struct NetworkRequester { - #[serde(default)] - pub debug: NetworkRequesterDebug, -} - -#[allow(clippy::derivable_impls)] -impl Default for NetworkRequester { - fn default() -> Self { - NetworkRequester { - debug: Default::default(), - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -pub struct NetworkRequesterDebug { - /// Specifies whether network requester service is enabled in this process. - /// This is only here for debugging purposes as exit gateway should always run **both** - /// network requester and an ip packet router. - pub enabled: bool, - - /// Disable Poisson sending rate. - /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true - /// (or is it (?)) - pub disable_poisson_rate: bool, - - /// Shared detailed client configuration options - #[serde(flatten)] - pub client_debug: ClientDebugConfig, -} - -impl Default for NetworkRequesterDebug { - fn default() -> Self { - NetworkRequesterDebug { - enabled: true, - disable_poisson_rate: true, - client_debug: Default::default(), - } - } -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -pub struct IpPacketRouter { - #[serde(default)] - pub debug: IpPacketRouterDebug, -} - -#[allow(clippy::derivable_impls)] -impl Default for IpPacketRouter { - fn default() -> Self { - IpPacketRouter { - debug: Default::default(), - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default)] -pub struct IpPacketRouterDebug { - /// Specifies whether ip packet routing service is enabled in this process. - /// This is only here for debugging purposes as exit gateway should always run **both** - /// network requester and an ip packet router. - pub enabled: bool, - - /// Disable Poisson sending rate. - /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true - /// (or is it (?)) - pub disable_poisson_rate: bool, - - /// Shared detailed client configuration options - #[serde(flatten)] - pub client_debug: ClientDebugConfig, -} - -impl Default for IpPacketRouterDebug { - fn default() -> Self { - IpPacketRouterDebug { - enabled: true, - disable_poisson_rate: true, - client_debug: Default::default(), - } - } -} - -// that function is rather disgusting, but I hope it's not going to live for too long -pub fn ephemeral_exit_gateway_config( - config: Config, - mnemonic: &bip39::Mnemonic, -) -> Result { - let mut nr_opts = LocalNetworkRequesterOpts { - config: nym_network_requester::Config { - base: nym_client_core_config_types::Config { - client: base_client_config(&config), - debug: config.exit_gateway.network_requester.debug.client_debug, - }, - network_requester: nym_network_requester::config::NetworkRequester { - open_proxy: config.exit_gateway.open_proxy, - disable_poisson_rate: config - .exit_gateway - .network_requester - .debug - .disable_poisson_rate, - upstream_exit_policy_url: Some( - config.exit_gateway.upstream_exit_policy_url.clone(), - ), - }, - storage_paths: nym_network_requester::config::NetworkRequesterPaths { - common_paths: config - .exit_gateway - .storage_paths - .network_requester - .to_common_client_paths(), - }, - network_requester_debug: Default::default(), - logging: config.logging, - }, - custom_mixnet_path: None, - }; - - // SAFETY: this function can only fail if fastmode or nocover is set alongside medium_toggle which is not the case here - #[allow(clippy::unwrap_used)] - nr_opts - .config - .base - .try_apply_traffic_modes( - nr_opts.config.network_requester.disable_poisson_rate, - false, - false, - false, - ) - .unwrap(); - - let mut ipr_opts = LocalIpPacketRouterOpts { - config: nym_ip_packet_router::Config { - base: nym_client_core_config_types::Config { - client: base_client_config(&config), - debug: config.exit_gateway.ip_packet_router.debug.client_debug, - }, - ip_packet_router: nym_ip_packet_router::config::IpPacketRouter { - disable_poisson_rate: config - .exit_gateway - .ip_packet_router - .debug - .disable_poisson_rate, - upstream_exit_policy_url: Some( - config.exit_gateway.upstream_exit_policy_url.clone(), - ), - }, - storage_paths: nym_ip_packet_router::config::IpPacketRouterPaths { - common_paths: config - .exit_gateway - .storage_paths - .ip_packet_router - .to_common_client_paths(), - ip_packet_router_description: Default::default(), - }, - - logging: config.logging, - }, - custom_mixnet_path: None, - }; - - if ipr_opts.config.ip_packet_router.disable_poisson_rate { - ipr_opts.config.base.set_no_poisson_process() - } - - let mut auth_opts = LocalAuthenticatorOpts { - config: nym_authenticator::Config { - base: nym_client_core_config_types::Config { - client: base_client_config(&config), - debug: config.authenticator.debug.client_debug, - }, - authenticator: config.wireguard.clone().into(), - storage_paths: nym_authenticator::config::AuthenticatorPaths { - common_paths: config - .exit_gateway - .storage_paths - .authenticator - .to_common_client_paths(), - }, - logging: config.logging, - }, - custom_mixnet_path: None, - }; - - if config.authenticator.debug.disable_poisson_rate { - auth_opts.config.base.set_no_poisson_process(); - } - - let ipr_enabled = config.exit_gateway.ip_packet_router.debug.enabled; - let nr_enabled = config.exit_gateway.network_requester.debug.enabled; - - let wg_opts = LocalWireguardOpts { - config: super::Wireguard { - enabled: config.wireguard.enabled, - bind_address: config.wireguard.bind_address, - private_ipv4: config.wireguard.private_ipv4, - private_ipv6: config.wireguard.private_ipv6, - announced_port: config.wireguard.announced_port, - private_network_prefix_v4: config.wireguard.private_network_prefix_v4, - private_network_prefix_v6: config.wireguard.private_network_prefix_v6, - storage_paths: config.wireguard.storage_paths.clone(), - }, - custom_mixnet_path: None, - }; - - let mut gateway = ephemeral_gateway_config(config, mnemonic)?; - gateway.ip_packet_router.enabled = ipr_enabled; - gateway.network_requester.enabled = nr_enabled; - - Ok(EphemeralConfig { - nr_opts: Some(nr_opts), - ipr_opts: Some(ipr_opts), - auth_opts, - wg_opts, - gateway, - }) -} diff --git a/nym-node/src/config/entry_gateway.rs b/nym-node/src/config/gateway_tasks.rs similarity index 66% rename from nym-node/src/config/entry_gateway.rs rename to nym-node/src/config/gateway_tasks.rs index 8ab330e090..677d381155 100644 --- a/nym-node/src/config/entry_gateway.rs +++ b/nym-node/src/config/gateway_tasks.rs @@ -1,30 +1,23 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::helpers::ephemeral_gateway_config; -use crate::config::persistence::EntryGatewayPaths; -use crate::config::Config; -use crate::error::EntryGatewayError; +use crate::config::persistence::GatewayTasksPaths; use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, TICKETBOOK_VALIDITY_DAYS}; use nym_config::helpers::inaddr_any; use nym_config::serde_helpers::de_maybe_port; -use nym_gateway::node::LocalAuthenticatorOpts; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use std::path::Path; use std::time::Duration; -use super::helpers::{base_client_config, EphemeralConfig}; -use super::LocalWireguardOpts; - pub const DEFAULT_WS_PORT: u16 = DEFAULT_CLIENT_LISTENING_PORT; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct EntryGatewayConfig { - pub storage_paths: EntryGatewayPaths, +pub struct GatewayTasksConfig { + pub storage_paths: GatewayTasksPaths, - /// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet + /// Indicates whether this gateway is accepting only zk-nym credentials for accessing the mixnet /// or if it also accepts non-paying clients pub enforce_zk_nyms: bool, @@ -136,10 +129,10 @@ impl Default for ZkNymTicketHandlerDebug { } } -impl EntryGatewayConfig { +impl GatewayTasksConfig { pub fn new_default>(data_dir: P) -> Self { - EntryGatewayConfig { - storage_paths: EntryGatewayPaths::new(data_dir), + GatewayTasksConfig { + storage_paths: GatewayTasksPaths::new(data_dir), enforce_zk_nyms: false, bind_address: SocketAddr::new(inaddr_any(), DEFAULT_WS_PORT), announce_ws_port: None, @@ -148,55 +141,3 @@ impl EntryGatewayConfig { } } } - -// a temporary solution until all nodes are even more tightly integrated -pub fn ephemeral_entry_gateway_config( - config: Config, - mnemonic: &bip39::Mnemonic, -) -> Result { - let mut auth_opts = LocalAuthenticatorOpts { - config: nym_authenticator::Config { - base: nym_client_core_config_types::Config { - client: base_client_config(&config), - debug: config.authenticator.debug.client_debug, - }, - authenticator: config.wireguard.clone().into(), - storage_paths: nym_authenticator::config::AuthenticatorPaths { - common_paths: config - .exit_gateway - .storage_paths - .authenticator - .to_common_client_paths(), - }, - logging: config.logging, - }, - custom_mixnet_path: None, - }; - - if config.authenticator.debug.disable_poisson_rate { - auth_opts.config.base.set_no_poisson_process(); - } - - let wg_opts = LocalWireguardOpts { - config: super::Wireguard { - enabled: config.wireguard.enabled, - bind_address: config.wireguard.bind_address, - private_ipv4: config.wireguard.private_ipv4, - private_ipv6: config.wireguard.private_ipv6, - announced_port: config.wireguard.announced_port, - private_network_prefix_v4: config.wireguard.private_network_prefix_v4, - private_network_prefix_v6: config.wireguard.private_network_prefix_v6, - storage_paths: config.wireguard.storage_paths.clone(), - }, - custom_mixnet_path: None, - }; - - let gateway = ephemeral_gateway_config(config, mnemonic)?; - Ok(EphemeralConfig { - nr_opts: None, - ipr_opts: None, - auth_opts, - wg_opts, - gateway, - }) -} diff --git a/nym-node/src/config/helpers.rs b/nym-node/src/config/helpers.rs index 9b7c9d3af9..3ba91b5432 100644 --- a/nym-node/src/config/helpers.rs +++ b/nym-node/src/config/helpers.rs @@ -1,106 +1,54 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use super::LocalWireguardOpts; use crate::config::Config; use clap::crate_version; use nym_gateway::node::{ LocalAuthenticatorOpts, LocalIpPacketRouterOpts, LocalNetworkRequesterOpts, }; -use std::net::IpAddr; -use thiserror::Error; -use super::LocalWireguardOpts; - -#[derive(Debug, Error)] -#[error("currently it's not supported to have different ip addresses for clients and mixnet ({clients_bind_ip} and {mix_bind_ip} were used)")] -pub struct UnsupportedGatewayAddresses { - clients_bind_ip: IpAddr, - mix_bind_ip: IpAddr, -} - -// a temporary solution until all nodes are even more tightly integrated -pub fn ephemeral_gateway_config( - config: Config, - mnemonic: &bip39::Mnemonic, -) -> Result { - let host = nym_gateway::config::Host { - public_ips: config.host.public_ips, - hostname: config.host.hostname, - }; - - let http = nym_gateway::config::Http { - bind_address: config.http.bind_address, - landing_page_assets_path: config.http.landing_page_assets_path, - }; - - let clients_bind_ip = config.entry_gateway.bind_address.ip(); - let mix_bind_ip = config.mixnet.bind_address.ip(); - if clients_bind_ip != mix_bind_ip { - return Err(UnsupportedGatewayAddresses { - clients_bind_ip, - mix_bind_ip, - }); - } - - // SAFETY: we're using hardcoded valid url here (that won't be used anyway) - #[allow(clippy::unwrap_used)] - let gateway = nym_gateway::config::Gateway { - // that field is very much irrelevant, but I guess let's keep them for now - version: format!("{}-nym-node", crate_version!()), - id: config.id, - only_coconut_credentials: config.entry_gateway.enforce_zk_nyms, - listening_address: clients_bind_ip, - mix_port: config.mixnet.bind_address.port(), - clients_port: config.entry_gateway.bind_address.port(), - clients_wss_port: config.entry_gateway.announce_wss_port, - nym_api_urls: config.mixnet.nym_api_urls, - nyxd_urls: config.mixnet.nyxd_urls, - - // that's nasty but can't do anything about it for this temporary solution : ( - cosmos_mnemonic: mnemonic.clone(), - }; - - Ok(nym_gateway::config::Config::externally_loaded( - host, - http, - gateway, - nym_gateway::config::NetworkRequester { enabled: false }, - nym_gateway::config::IpPacketRouter { enabled: false }, +// a temporary solution until further refactoring is made +fn ephemeral_gateway_config(config: &Config) -> nym_gateway::config::Config { + nym_gateway::config::Config::new( + nym_gateway::config::Gateway { + enforce_zk_nyms: config.gateway_tasks.enforce_zk_nyms, + websocket_bind_address: config.gateway_tasks.bind_address, + nym_api_urls: config.mixnet.nym_api_urls.clone(), + nyxd_urls: config.mixnet.nyxd_urls.clone(), + }, + nym_gateway::config::NetworkRequester { + enabled: config.service_providers.ip_packet_router.debug.enabled, + }, + nym_gateway::config::IpPacketRouter { + enabled: config.service_providers.network_requester.debug.enabled, + }, nym_gateway::config::Debug { - packet_forwarding_initial_backoff: config - .mixnet - .debug - .packet_forwarding_initial_backoff, - packet_forwarding_maximum_backoff: config - .mixnet - .debug - .packet_forwarding_maximum_backoff, - initial_connection_timeout: config.mixnet.debug.initial_connection_timeout, - maximum_connection_buffer_size: config.mixnet.debug.maximum_connection_buffer_size, - message_retrieval_limit: config.entry_gateway.debug.message_retrieval_limit, - use_legacy_framed_packet_version: false, + client_bandwidth_max_flushing_rate: + nym_gateway::config::DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE, + client_bandwidth_max_delta_flushing_amount: + nym_gateway::config::DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT, zk_nym_tickets: nym_gateway::config::ZkNymTicketHandlerDebug { revocation_bandwidth_penalty: config - .entry_gateway + .gateway_tasks .debug .zk_nym_tickets .revocation_bandwidth_penalty, - pending_poller: config.entry_gateway.debug.zk_nym_tickets.pending_poller, - minimum_api_quorum: config.entry_gateway.debug.zk_nym_tickets.minimum_api_quorum, + pending_poller: config.gateway_tasks.debug.zk_nym_tickets.pending_poller, + minimum_api_quorum: config.gateway_tasks.debug.zk_nym_tickets.minimum_api_quorum, minimum_redemption_tickets: config - .entry_gateway + .gateway_tasks .debug .zk_nym_tickets .minimum_redemption_tickets, maximum_time_between_redemption: config - .entry_gateway + .gateway_tasks .debug .zk_nym_tickets .maximum_time_between_redemption, }, - ..Default::default() }, - )) + ) } pub fn base_client_config(config: &Config) -> nym_client_core_config_types::Client { @@ -114,10 +62,145 @@ pub fn base_client_config(config: &Config) -> nym_client_core_config_types::Clie } } -pub struct EphemeralConfig { +pub struct GatewayTasksConfig { pub gateway: nym_gateway::config::Config, pub nr_opts: Option, pub ipr_opts: Option, - pub auth_opts: LocalAuthenticatorOpts, + pub auth_opts: Option, + #[allow(dead_code)] pub wg_opts: LocalWireguardOpts, } + +// that function is rather disgusting, but I hope it's not going to live for too long +pub fn gateway_tasks_config(config: &Config) -> GatewayTasksConfig { + let mut nr_opts = LocalNetworkRequesterOpts { + config: nym_network_requester::Config { + base: nym_client_core_config_types::Config { + client: base_client_config(config), + debug: config + .service_providers + .network_requester + .debug + .client_debug, + }, + network_requester: nym_network_requester::config::NetworkRequester { + open_proxy: config.service_providers.open_proxy, + disable_poisson_rate: config + .service_providers + .network_requester + .debug + .disable_poisson_rate, + upstream_exit_policy_url: Some( + config.service_providers.upstream_exit_policy_url.clone(), + ), + }, + storage_paths: nym_network_requester::config::NetworkRequesterPaths { + common_paths: config + .service_providers + .storage_paths + .network_requester + .to_common_client_paths(), + }, + network_requester_debug: Default::default(), + logging: config.logging, + }, + custom_mixnet_path: None, + }; + + // SAFETY: this function can only fail if fastmode or nocover is set alongside medium_toggle which is not the case here + #[allow(clippy::unwrap_used)] + nr_opts + .config + .base + .try_apply_traffic_modes( + nr_opts.config.network_requester.disable_poisson_rate, + false, + false, + false, + ) + .unwrap(); + + let mut ipr_opts = LocalIpPacketRouterOpts { + config: nym_ip_packet_router::Config { + base: nym_client_core_config_types::Config { + client: base_client_config(config), + debug: config.service_providers.ip_packet_router.debug.client_debug, + }, + ip_packet_router: nym_ip_packet_router::config::IpPacketRouter { + disable_poisson_rate: config + .service_providers + .ip_packet_router + .debug + .disable_poisson_rate, + upstream_exit_policy_url: Some( + config.service_providers.upstream_exit_policy_url.clone(), + ), + }, + storage_paths: nym_ip_packet_router::config::IpPacketRouterPaths { + common_paths: config + .service_providers + .storage_paths + .ip_packet_router + .to_common_client_paths(), + ip_packet_router_description: Default::default(), + }, + + logging: config.logging, + }, + custom_mixnet_path: None, + }; + + if ipr_opts.config.ip_packet_router.disable_poisson_rate { + ipr_opts.config.base.set_no_poisson_process() + } + + let mut auth_opts = LocalAuthenticatorOpts { + config: nym_authenticator::Config { + base: nym_client_core_config_types::Config { + client: base_client_config(config), + debug: config.service_providers.authenticator.debug.client_debug, + }, + authenticator: config.wireguard.clone().into(), + storage_paths: nym_authenticator::config::AuthenticatorPaths { + common_paths: config + .service_providers + .storage_paths + .authenticator + .to_common_client_paths(), + }, + logging: config.logging, + }, + custom_mixnet_path: None, + }; + + if config + .service_providers + .authenticator + .debug + .disable_poisson_rate + { + auth_opts.config.base.set_no_poisson_process(); + } + + let wg_opts = LocalWireguardOpts { + config: super::Wireguard { + enabled: config.wireguard.enabled, + bind_address: config.wireguard.bind_address, + private_ipv4: config.wireguard.private_ipv4, + private_ipv6: config.wireguard.private_ipv6, + announced_port: config.wireguard.announced_port, + private_network_prefix_v4: config.wireguard.private_network_prefix_v4, + private_network_prefix_v6: config.wireguard.private_network_prefix_v6, + storage_paths: config.wireguard.storage_paths.clone(), + }, + custom_mixnet_path: None, + }; + + GatewayTasksConfig { + gateway: ephemeral_gateway_config(config), + nr_opts: Some(nr_opts), + ipr_opts: Some(ipr_opts), + auth_opts: Some(auth_opts), + wg_opts, + } +} diff --git a/nym-node/src/config/metrics.rs b/nym-node/src/config/metrics.rs new file mode 100644 index 0000000000..80f9dcea6d --- /dev/null +++ b/nym-node/src/config/metrics.rs @@ -0,0 +1,60 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MetricsConfig { + #[serde(default)] + pub debug: Debug, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Debug { + /// Specify whether running statistics of this node should be logged to the console. + pub log_stats_to_console: bool, + + /// Specify the rate of which the metrics aggregator should call the `on_update` methods of all its registered handlers. + #[serde(with = "humantime_serde")] + pub aggregator_update_rate: Duration, + + /// Specify the target rate of clearing old stale mixnet metrics. + #[serde(with = "humantime_serde")] + pub stale_mixnet_metrics_cleaner_rate: Duration, + + /// Specify the rate of updating clients sessions + #[serde(with = "humantime_serde")] + pub clients_sessions_update_rate: Duration, + + /// If console logging is enabled, specify the interval at which that happens + #[serde(with = "humantime_serde")] + pub console_logging_update_interval: Duration, + + /// Specify the update rate of running stats for the legacy `/metrics/mixing` endpoint + #[serde(with = "humantime_serde")] + pub legacy_mixing_metrics_update_rate: Duration, +} + +impl Debug { + const DEFAULT_CONSOLE_LOGGING_INTERVAL: Duration = Duration::from_millis(60_000); + const DEFAULT_LEGACY_MIXING_UPDATE_RATE: Duration = Duration::from_millis(30_000); + const DEFAULT_AGGREGATOR_UPDATE_RATE: Duration = Duration::from_secs(5); + const DEFAULT_STALE_MIXNET_ETRICS_UPDATE_RATE: Duration = Duration::from_secs(3600); + const DEFAULT_CLIENT_SESSIONS_UPDATE_RATE: Duration = Duration::from_secs(3600); +} + +impl Default for Debug { + fn default() -> Self { + Debug { + log_stats_to_console: true, + console_logging_update_interval: Self::DEFAULT_CONSOLE_LOGGING_INTERVAL, + legacy_mixing_metrics_update_rate: Self::DEFAULT_LEGACY_MIXING_UPDATE_RATE, + aggregator_update_rate: Self::DEFAULT_AGGREGATOR_UPDATE_RATE, + stale_mixnet_metrics_cleaner_rate: Self::DEFAULT_STALE_MIXNET_ETRICS_UPDATE_RATE, + clients_sessions_update_rate: Self::DEFAULT_CLIENT_SESSIONS_UPDATE_RATE, + } + } +} diff --git a/nym-node/src/config/mixnode.rs b/nym-node/src/config/mixnode.rs deleted file mode 100644 index a62ee4b075..0000000000 --- a/nym-node/src/config/mixnode.rs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::config::persistence::MixnodePaths; -use crate::config::Config; -use crate::error::MixnodeError; -use clap::crate_version; -use nym_config::defaults::DEFAULT_VERLOC_LISTENING_PORT; -use nym_config::helpers::inaddr_any; -use nym_config::serde_helpers::de_maybe_port; -use serde::{Deserialize, Serialize}; -use std::net::SocketAddr; -use std::time::Duration; - -pub const DEFAULT_VERLOC_PORT: u16 = DEFAULT_VERLOC_LISTENING_PORT; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MixnodeConfig { - pub storage_paths: MixnodePaths, - - pub verloc: Verloc, - - #[serde(default)] - pub debug: Debug, -} - -impl MixnodeConfig { - pub fn new_default() -> Self { - MixnodeConfig { - storage_paths: MixnodePaths {}, - verloc: Default::default(), - debug: Default::default(), - } - } -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields, default)] -pub struct Verloc { - /// Socket address this node will use for binding its verloc API. - /// default: `0.0.0.0:1790` - pub bind_address: SocketAddr, - - /// If applicable, custom port announced in the self-described API that other clients and nodes - /// will use. - /// Useful when the node is behind a proxy. - #[serde(deserialize_with = "de_maybe_port")] - #[serde(default)] - pub announce_port: Option, - - #[serde(default)] - pub debug: VerlocDebug, -} - -impl Default for Verloc { - fn default() -> Self { - Verloc { - bind_address: SocketAddr::new(inaddr_any(), DEFAULT_VERLOC_PORT), - announce_port: None, - debug: Default::default(), - } - } -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct VerlocDebug { - /// Specifies number of echo packets sent to each node during a measurement run. - pub packets_per_node: usize, - - /// Specifies maximum amount of time to wait for the connection to get established. - #[serde(with = "humantime_serde")] - pub connection_timeout: Duration, - - /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. - #[serde(with = "humantime_serde")] - pub packet_timeout: Duration, - - /// Specifies delay between subsequent test packets being sent (after receiving a reply). - #[serde(with = "humantime_serde")] - pub delay_between_packets: Duration, - - /// Specifies number of nodes being tested at once. - pub tested_nodes_batch_size: usize, - - /// Specifies delay between subsequent test runs. - #[serde(with = "humantime_serde")] - pub testing_interval: Duration, - - /// Specifies delay between attempting to run the measurement again if the previous run failed - /// due to being unable to get the list of nodes. - #[serde(with = "humantime_serde")] - pub retry_timeout: Duration, -} - -impl VerlocDebug { - const DEFAULT_PACKETS_PER_NODE: usize = 100; - const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); - const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); - const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); - const DEFAULT_BATCH_SIZE: usize = 50; - const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); - const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); -} - -impl Default for VerlocDebug { - fn default() -> Self { - VerlocDebug { - packets_per_node: VerlocDebug::DEFAULT_PACKETS_PER_NODE, - connection_timeout: VerlocDebug::DEFAULT_CONNECTION_TIMEOUT, - packet_timeout: VerlocDebug::DEFAULT_PACKET_TIMEOUT, - delay_between_packets: VerlocDebug::DEFAULT_DELAY_BETWEEN_PACKETS, - tested_nodes_batch_size: VerlocDebug::DEFAULT_BATCH_SIZE, - testing_interval: VerlocDebug::DEFAULT_TESTING_INTERVAL, - retry_timeout: VerlocDebug::DEFAULT_RETRY_TIMEOUT, - } - } -} - -impl From for nym_mixnode::config::Verloc { - fn from(value: VerlocDebug) -> Self { - nym_mixnode::config::Verloc { - packets_per_node: value.packets_per_node, - connection_timeout: value.connection_timeout, - packet_timeout: value.packet_timeout, - delay_between_packets: value.delay_between_packets, - tested_nodes_batch_size: value.tested_nodes_batch_size, - testing_interval: value.testing_interval, - retry_timeout: value.retry_timeout, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Debug { - /// Delay between each subsequent node statistics being logged to the console - #[serde(with = "humantime_serde")] - pub node_stats_logging_delay: Duration, - - /// Delay between each subsequent node statistics being updated - #[serde(with = "humantime_serde")] - pub node_stats_updating_delay: Duration, -} - -impl Debug { - const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); - const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000); -} - -impl Default for Debug { - fn default() -> Self { - Debug { - node_stats_logging_delay: Debug::DEFAULT_NODE_STATS_LOGGING_DELAY, - node_stats_updating_delay: Debug::DEFAULT_NODE_STATS_UPDATING_DELAY, - } - } -} - -// a temporary solution until all nodes are even more tightly integrated -pub fn ephemeral_mixnode_config( - config: Config, -) -> Result { - let host = nym_mixnode::config::Host { - public_ips: config.host.public_ips, - hostname: config.host.hostname, - }; - - let http = nym_mixnode::config::Http { - bind_address: config.http.bind_address, - landing_page_assets_path: config.http.landing_page_assets_path, - metrics_key: config.http.access_token, - }; - - let verloc_bind_ip = config.mixnode.verloc.bind_address.ip(); - let mix_bind_ip = config.mixnet.bind_address.ip(); - if verloc_bind_ip != mix_bind_ip { - return Err(MixnodeError::UnsupportedAddresses { - verloc_bind_ip, - mix_bind_ip, - }); - } - - let listening_address = mix_bind_ip; - let mix_port = config.mixnet.bind_address.port(); - let verloc_port = config.mixnode.verloc.bind_address.port(); - let nym_api_urls = config.mixnet.nym_api_urls; - - let mixnode = nym_mixnode::config::MixNode { - // that field is very much irrelevant, but I guess let's keep them for now - version: format!("{}-nym-node", crate_version!()), - id: config.id, - listening_address, - mix_port, - verloc_port, - nym_api_urls, - }; - - Ok(nym_mixnode::config::Config::externally_loaded( - host, - http, - mixnode, - config.mixnode.verloc.debug, - nym_mixnode::config::Debug { - node_stats_logging_delay: config.mixnode.debug.node_stats_logging_delay, - node_stats_updating_delay: config.mixnode.debug.node_stats_updating_delay, - packet_forwarding_initial_backoff: config - .mixnet - .debug - .packet_forwarding_initial_backoff, - packet_forwarding_maximum_backoff: config - .mixnet - .debug - .packet_forwarding_maximum_backoff, - initial_connection_timeout: config.mixnet.debug.initial_connection_timeout, - maximum_connection_buffer_size: config.mixnet.debug.maximum_connection_buffer_size, - use_legacy_framed_packet_version: false, - }, - )) -} diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 626c9cf098..6b996f24be 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -4,13 +4,13 @@ use crate::config::persistence::NymNodePaths; use crate::config::template::CONFIG_TEMPLATE; use crate::error::NymNodeError; -use authenticator::Authenticator; use celes::Country; use clap::ValueEnum; use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::{ - mainnet, var_names, DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT, WG_PORT, - WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, + mainnet, var_names, DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT, + DEFAULT_VERLOC_LISTENING_PORT, WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, + WG_TUN_DEVICE_IP_ADDRESS_V6, }; use nym_config::defaults::{WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6}; use nym_config::helpers::inaddr_any; @@ -30,18 +30,18 @@ use tracing::{debug, error}; use url::Url; pub mod authenticator; -pub mod entry_gateway; -pub mod exit_gateway; +pub mod gateway_tasks; pub mod helpers; -pub mod mixnode; +pub mod metrics; mod old_configs; pub mod persistence; +pub mod service_providers; mod template; pub mod upgrade_helpers; -pub use crate::config::entry_gateway::EntryGatewayConfig; -pub use crate::config::exit_gateway::ExitGatewayConfig; -pub use crate::config::mixnode::MixnodeConfig; +pub use crate::config::gateway_tasks::GatewayTasksConfig; +pub use crate::config::metrics::MetricsConfig; +pub use crate::config::service_providers::ServiceProvidersConfig; const DEFAULT_NYMNODES_DIR: &str = "nym-nodes"; @@ -89,6 +89,62 @@ impl Display for NodeMode { } } +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy)] +pub struct NodeModes { + /// Specifies whether this node can operate in a mixnode mode. + pub mixnode: bool, + + /// Specifies whether this node can operate in an entry mode. + pub entry: bool, + + /// Specifies whether this node can operate in an exit mode. + pub exit: bool, + // TODO: would it make sense to also put WG here for completion? +} + +impl From<&[NodeMode]> for NodeModes { + fn from(modes: &[NodeMode]) -> Self { + let mut out = NodeModes::default(); + for &mode in modes { + out.with_mode(mode); + } + out + } +} + +impl NodeModes { + pub fn any_enabled(&self) -> bool { + self.mixnode || self.entry || self.exit + } + + pub fn with_mode(&mut self, mode: NodeMode) -> &mut Self { + match mode { + NodeMode::Mixnode => self.with_mixnode(), + NodeMode::EntryGateway => self.with_entry(), + NodeMode::ExitGateway => self.with_exit(), + } + } + + pub fn expects_final_hop_traffic(&self) -> bool { + self.entry || self.exit + } + + pub fn with_mixnode(&mut self) -> &mut Self { + self.mixnode = true; + self + } + + pub fn with_entry(&mut self) -> &mut Self { + self.entry = true; + self + } + + pub fn with_exit(&mut self) -> &mut Self { + self.exit = true; + self + } +} + pub struct ConfigBuilder { pub id: String, @@ -96,7 +152,7 @@ pub struct ConfigBuilder { pub data_dir: PathBuf, - pub mode: NodeMode, + pub modes: NodeModes, pub mixnet: Option, @@ -104,17 +160,17 @@ pub struct ConfigBuilder { pub http: Option, + pub verloc: Option, + pub wireguard: Option, pub storage_paths: Option, - pub mixnode: Option, + pub gateway_tasks: Option, - pub entry_gateway: Option, + pub service_providers: Option, - pub exit_gateway: Option, - - pub authenticator: Option, + pub metrics: Option, pub logging: Option, } @@ -128,19 +184,19 @@ impl ConfigBuilder { host: None, http: None, mixnet: None, + verloc: None, wireguard: None, - mode: NodeMode::default(), + modes: NodeModes::default(), storage_paths: None, - mixnode: None, - entry_gateway: None, - exit_gateway: None, - authenticator: None, + gateway_tasks: None, + service_providers: None, + metrics: None, logging: None, } } - pub fn with_mode(mut self, mode: impl Into) -> Self { - self.mode = mode.into(); + pub fn with_modes(mut self, mode: impl Into) -> Self { + self.modes = mode.into(); self } @@ -154,6 +210,11 @@ impl ConfigBuilder { self } + pub fn with_verloc(mut self, section: impl Into>) -> Self { + self.verloc = section.into(); + self + } + pub fn with_mixnet(mut self, section: impl Into>) -> Self { self.mixnet = section.into(); self @@ -169,49 +230,48 @@ impl ConfigBuilder { self } - pub fn with_mixnode(mut self, section: impl Into>) -> Self { - self.mixnode = section.into(); + pub fn with_metrics(mut self, section: impl Into>) -> Self { + self.metrics = section.into(); self } - pub fn with_entry_gateway(mut self, section: impl Into>) -> Self { - self.entry_gateway = section.into(); + pub fn with_gateway_tasks(mut self, section: impl Into>) -> Self { + self.gateway_tasks = section.into(); self } - pub fn with_exit_gateway(mut self, section: impl Into>) -> Self { - self.exit_gateway = section.into(); - self - } - - pub fn with_logging(mut self, section: impl Into>) -> Self { - self.logging = section.into(); + pub fn with_service_providers( + mut self, + section: impl Into>, + ) -> Self { + self.service_providers = section.into(); self } pub fn build(self) -> Config { Config { id: self.id, - mode: self.mode, + modes: self.modes, host: self.host.unwrap_or_default(), http: self.http.unwrap_or_default(), mixnet: self.mixnet.unwrap_or_default(), + verloc: self.verloc.unwrap_or_default(), wireguard: self .wireguard .unwrap_or_else(|| Wireguard::new_default(&self.data_dir)), storage_paths: self .storage_paths .unwrap_or_else(|| NymNodePaths::new(&self.data_dir)), - mixnode: self.mixnode.unwrap_or_else(MixnodeConfig::new_default), - entry_gateway: self - .entry_gateway - .unwrap_or_else(|| EntryGatewayConfig::new_default(&self.data_dir)), - exit_gateway: self - .exit_gateway - .unwrap_or_else(|| ExitGatewayConfig::new_default(&self.data_dir)), + metrics: self.metrics.unwrap_or_default(), + gateway_tasks: self + .gateway_tasks + .unwrap_or_else(|| GatewayTasksConfig::new_default(&self.data_dir)), + service_providers: self + .service_providers + .unwrap_or_else(|| ServiceProvidersConfig::new_default(&self.data_dir)), logging: self.logging.unwrap_or_default(), save_path: Some(self.config_path), - authenticator: self.authenticator.unwrap_or_default(), + debug: Default::default(), } } } @@ -226,9 +286,8 @@ pub struct Config { /// Human-readable ID of this particular node. pub id: String, - /// Current mode of this nym-node. - /// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway) - pub mode: NodeMode, + /// Current modes of this nym-node. + pub modes: NodeModes, pub host: Host, @@ -240,18 +299,25 @@ pub struct Config { #[serde(default)] pub http: Http, + #[serde(default)] + pub verloc: Verloc, + pub wireguard: Wireguard, - pub mixnode: MixnodeConfig, + #[serde(alias = "entry_gateway")] + pub gateway_tasks: GatewayTasksConfig, - pub entry_gateway: EntryGatewayConfig, + #[serde(alias = "exit_gateway")] + pub service_providers: ServiceProvidersConfig, - pub exit_gateway: ExitGatewayConfig, - - pub authenticator: Authenticator, + #[serde(default)] + pub metrics: MetricsConfig, #[serde(default)] pub logging: LoggingSettings, + + #[serde(default)] + pub debug: Debug, } impl NymConfigTemplate for Config { @@ -437,6 +503,10 @@ pub struct Mixnet { #[serde(default)] #[serde(deny_unknown_fields)] pub struct MixnetDebug { + /// Specifies the duration of time this node is willing to delay a forward packet for. + #[serde(with = "humantime_serde")] + pub maximum_forward_packet_delay: Duration, + /// Initial value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. #[serde(with = "humantime_serde")] @@ -459,6 +529,10 @@ pub struct MixnetDebug { } impl MixnetDebug { + // given that genuine clients are using mean delay of 50ms, + // the probability of them delaying for over 10s is 10^-87 + // which for all intents and purposes will never happen + const DEFAULT_MAXIMUM_FORWARD_PACKET_DELAY: Duration = Duration::from_secs(10); const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); @@ -468,6 +542,7 @@ impl MixnetDebug { impl Default for MixnetDebug { fn default() -> Self { MixnetDebug { + maximum_forward_packet_delay: Self::DEFAULT_MAXIMUM_FORWARD_PACKET_DELAY, packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT, @@ -507,6 +582,93 @@ impl Default for Mixnet { } } +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Verloc { + /// Socket address this node will use for binding its verloc API. + /// default: `0.0.0.0:1790` + pub bind_address: SocketAddr, + + /// If applicable, custom port announced in the self-described API that other clients and nodes + /// will use. + /// Useful when the node is behind a proxy. + #[serde(deserialize_with = "de_maybe_port")] + #[serde(default)] + pub announce_port: Option, + + #[serde(default)] + pub debug: VerlocDebug, +} + +impl Verloc { + pub const DEFAULT_VERLOC_PORT: u16 = DEFAULT_VERLOC_LISTENING_PORT; +} + +impl Default for Verloc { + fn default() -> Self { + Verloc { + bind_address: SocketAddr::new(inaddr_any(), Self::DEFAULT_VERLOC_PORT), + announce_port: None, + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocDebug { + /// Specifies number of echo packets sent to each node during a measurement run. + pub packets_per_node: usize, + + /// Specifies maximum amount of time to wait for the connection to get established. + #[serde(with = "humantime_serde")] + pub connection_timeout: Duration, + + /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. + #[serde(with = "humantime_serde")] + pub packet_timeout: Duration, + + /// Specifies delay between subsequent test packets being sent (after receiving a reply). + #[serde(with = "humantime_serde")] + pub delay_between_packets: Duration, + + /// Specifies number of nodes being tested at once. + pub tested_nodes_batch_size: usize, + + /// Specifies delay between subsequent test runs. + #[serde(with = "humantime_serde")] + pub testing_interval: Duration, + + /// Specifies delay between attempting to run the measurement again if the previous run failed + /// due to being unable to get the list of nodes. + #[serde(with = "humantime_serde")] + pub retry_timeout: Duration, +} + +impl VerlocDebug { + const DEFAULT_PACKETS_PER_NODE: usize = 100; + const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); + const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); + const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); + const DEFAULT_BATCH_SIZE: usize = 50; + const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); + const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); +} + +impl Default for VerlocDebug { + fn default() -> Self { + VerlocDebug { + packets_per_node: Self::DEFAULT_PACKETS_PER_NODE, + connection_timeout: Self::DEFAULT_CONNECTION_TIMEOUT, + packet_timeout: Self::DEFAULT_PACKET_TIMEOUT, + delay_between_packets: Self::DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: Self::DEFAULT_BATCH_SIZE, + testing_interval: Self::DEFAULT_TESTING_INTERVAL, + retry_timeout: Self::DEFAULT_RETRY_TIMEOUT, + } + } +} + #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Wireguard { @@ -584,7 +746,29 @@ impl From for nym_authenticator::config::Authenticator { #[derive(Debug, Clone)] pub struct LocalWireguardOpts { + #[allow(dead_code)] pub config: Wireguard, + #[allow(dead_code)] pub custom_mixnet_path: Option, } + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Debug { + /// Specifies the time to live of the internal topology provider cache. + #[serde(with = "humantime_serde")] + pub topology_cache_ttl: Duration, +} + +impl Debug { + pub const DEFAULT_TOPOLOGY_CACHE_TTL: Duration = Duration::from_secs(5 * 60); +} + +impl Default for Debug { + fn default() -> Self { + Debug { + topology_cache_ttl: Self::DEFAULT_TOPOLOGY_CACHE_TTL, + } + } +} diff --git a/nym-node/src/config/old_configs/mod.rs b/nym-node/src/config/old_configs/mod.rs index 58040fde6b..f7b12e860d 100644 --- a/nym-node/src/config/old_configs/mod.rs +++ b/nym-node/src/config/old_configs/mod.rs @@ -6,9 +6,11 @@ mod old_config_v2; mod old_config_v3; mod old_config_v4; mod old_config_v5; +mod old_config_v6; pub use old_config_v1::try_upgrade_config_v1; pub use old_config_v2::try_upgrade_config_v2; pub use old_config_v3::try_upgrade_config_v3; pub use old_config_v4::try_upgrade_config_v4; pub use old_config_v5::try_upgrade_config_v5; +pub use old_config_v6::try_upgrade_config_v6; diff --git a/nym-node/src/config/old_configs/old_config_v1.rs b/nym-node/src/config/old_configs/old_config_v1.rs index c67f9358ae..b268ea86ab 100644 --- a/nym-node/src/config/old_configs/old_config_v1.rs +++ b/nym-node/src/config/old_configs/old_config_v1.rs @@ -22,25 +22,6 @@ pub struct WireguardPathsV1 { pub public_diffie_hellman_key_file: PathBuf, } -impl WireguardPathsV1 { - pub fn new>(data_dir: P) -> Self { - let data_dir = data_dir.as_ref(); - WireguardPathsV1 { - private_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME), - public_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME), - } - } - - pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath { - nym_pemstore::KeyPairPath::new( - &self.private_diffie_hellman_key_file, - &self.public_diffie_hellman_key_file, - ) - } -} - #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct WireguardV1 { @@ -656,77 +637,7 @@ pub struct ConfigV1 { pub logging: LoggingSettingsV1, } -impl NymConfigTemplate for ConfigV1 { - fn template(&self) -> &'static str { - CONFIG_TEMPLATE - } -} - impl ConfigV1 { - pub fn save(&self) -> Result<(), NymNodeError> { - let save_location = self.save_location(); - debug!( - "attempting to save config file to '{}'", - save_location.display() - ); - save_formatted_config_to_file(self, &save_location).map_err(|source| { - NymNodeError::ConfigSaveFailure { - id: self.id.clone(), - path: save_location, - source, - } - }) - } - - pub fn save_location(&self) -> PathBuf { - self.save_path - .clone() - .unwrap_or(self.default_save_location()) - } - - pub fn default_save_location(&self) -> PathBuf { - default_config_filepath(&self.id) - } - - pub fn default_data_directory>(config_path: P) -> Result { - let config_path = config_path.as_ref(); - - // we got a proper path to the .toml file - let Some(config_dir) = config_path.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - let Some(config_dir_name) = config_dir.file_name() else { - error!( - "could not obtain parent directory name of '{}'. Have you used relative paths?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - if config_dir_name != DEFAULT_CONFIG_DIR { - error!( - "the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported", - config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN") - ); - return Err(NymNodeError::DataDirDerivationFailure); - } - - let Some(node_dir) = config_dir.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_dir.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - Ok(node_dir.join(DEFAULT_DATA_DIR)) - } - // simple wrapper that reads config file and assigns path location fn read_from_path>(path: P) -> Result { let path = path.as_ref(); @@ -739,10 +650,6 @@ impl ConfigV1 { debug!("loaded config file from {}", path.display()); Ok(loaded) } - - pub fn read_from_toml_file>(path: P) -> Result { - Self::read_from_path(path) - } } fn initialise(config: &WireguardV2) -> std::io::Result<()> { diff --git a/nym-node/src/config/old_configs/old_config_v2.rs b/nym-node/src/config/old_configs/old_config_v2.rs index 2082ed4f70..712555b3b5 100644 --- a/nym-node/src/config/old_configs/old_config_v2.rs +++ b/nym-node/src/config/old_configs/old_config_v2.rs @@ -35,7 +35,6 @@ impl WireguardPathsV2 { .join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME), } } - pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath { nym_pemstore::KeyPairPath::new( &self.private_diffie_hellman_key_file, @@ -659,38 +658,7 @@ pub struct ConfigV2 { pub logging: LoggingSettingsV2, } -impl NymConfigTemplate for ConfigV2 { - fn template(&self) -> &'static str { - CONFIG_TEMPLATE - } -} - impl ConfigV2 { - pub fn save(&self) -> Result<(), NymNodeError> { - let save_location = self.save_location(); - debug!( - "attempting to save config file to '{}'", - save_location.display() - ); - save_formatted_config_to_file(self, &save_location).map_err(|source| { - NymNodeError::ConfigSaveFailure { - id: self.id.clone(), - path: save_location, - source, - } - }) - } - - pub fn save_location(&self) -> PathBuf { - self.save_path - .clone() - .unwrap_or(self.default_save_location()) - } - - pub fn default_save_location(&self) -> PathBuf { - default_config_filepath(&self.id) - } - pub fn default_data_directory>(config_path: P) -> Result { let config_path = config_path.as_ref(); @@ -742,10 +710,6 @@ impl ConfigV2 { debug!("loaded config file from {}", path.display()); Ok(loaded) } - - pub fn read_from_toml_file>(path: P) -> Result { - Self::read_from_path(path) - } } pub async fn initialise( diff --git a/nym-node/src/config/old_configs/old_config_v3.rs b/nym-node/src/config/old_configs/old_config_v3.rs index 1301230167..6796f1aa8c 100644 --- a/nym-node/src/config/old_configs/old_config_v3.rs +++ b/nym-node/src/config/old_configs/old_config_v3.rs @@ -3,19 +3,11 @@ #![allow(dead_code)] -use crate::{config::*, error::KeyIOFailure}; +use crate::config::*; use nym_client_core_config_types::DebugConfig as ClientDebugConfig; use nym_config::serde_helpers::de_maybe_port; -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_network_requester::{ - set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, - GatewayDetails, -}; -use nym_pemstore::{store_key, store_keypair}; -use nym_sphinx_acknowledgements::AckKey; use old_configs::old_config_v4::*; use persistence::*; -use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use tracing::instrument; @@ -26,25 +18,6 @@ pub struct WireguardPathsV3 { pub public_diffie_hellman_key_file: PathBuf, } -impl WireguardPathsV3 { - pub fn new>(data_dir: P) -> Self { - let data_dir = data_dir.as_ref(); - WireguardPathsV3 { - private_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME), - public_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME), - } - } - - pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath { - nym_pemstore::KeyPairPath::new( - &self.private_diffie_hellman_key_file, - &self.public_diffie_hellman_key_file, - ) - } -} - #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct WireguardV3 { @@ -811,77 +784,7 @@ pub struct ConfigV3 { pub logging: LoggingSettingsV3, } -impl NymConfigTemplate for ConfigV3 { - fn template(&self) -> &'static str { - CONFIG_TEMPLATE - } -} - impl ConfigV3 { - pub fn save(&self) -> Result<(), NymNodeError> { - let save_location = self.save_location(); - debug!( - "attempting to save config file to '{}'", - save_location.display() - ); - save_formatted_config_to_file(self, &save_location).map_err(|source| { - NymNodeError::ConfigSaveFailure { - id: self.id.clone(), - path: save_location, - source, - } - }) - } - - pub fn save_location(&self) -> PathBuf { - self.save_path - .clone() - .unwrap_or(self.default_save_location()) - } - - pub fn default_save_location(&self) -> PathBuf { - default_config_filepath(&self.id) - } - - pub fn default_data_directory>(config_path: P) -> Result { - let config_path = config_path.as_ref(); - - // we got a proper path to the .toml file - let Some(config_dir) = config_path.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - let Some(config_dir_name) = config_dir.file_name() else { - error!( - "could not obtain parent directory name of '{}'. Have you used relative paths?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - if config_dir_name != DEFAULT_CONFIG_DIR { - error!( - "the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported", - config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN") - ); - return Err(NymNodeError::DataDirDerivationFailure); - } - - let Some(node_dir) = config_dir.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_dir.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - Ok(node_dir.join(DEFAULT_DATA_DIR)) - } - // simple wrapper that reads config file and assigns path location fn read_from_path>(path: P) -> Result { let path = path.as_ref(); @@ -894,49 +797,6 @@ impl ConfigV3 { debug!("loaded config file from {}", path.display()); Ok(loaded) } - - pub fn read_from_toml_file>(path: P) -> Result { - Self::read_from_path(path) - } -} - -pub async fn initialise( - paths: &AuthenticatorPaths, - public_key: nym_crypto::asymmetric::identity::PublicKey, -) -> Result<(), NymNodeError> { - let mut rng = OsRng; - let ed25519_keys = ed25519::KeyPair::new(&mut rng); - let x25519_keys = x25519::KeyPair::new(&mut rng); - let aes128ctr_key = AckKey::new(&mut rng); - let gateway_details = GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into(); - - store_keypair(&ed25519_keys, &paths.ed25519_identity_storage_paths()).map_err(|e| { - KeyIOFailure::KeyPairStoreFailure { - keys: "ed25519-identity".to_string(), - paths: paths.ed25519_identity_storage_paths(), - err: e, - } - })?; - store_keypair(&x25519_keys, &paths.x25519_diffie_hellman_storage_paths()).map_err(|e| { - KeyIOFailure::KeyPairStoreFailure { - keys: "x25519-dh".to_string(), - paths: paths.x25519_diffie_hellman_storage_paths(), - err: e, - } - })?; - store_key(&aes128ctr_key, &paths.ack_key_file).map_err(|e| KeyIOFailure::KeyStoreFailure { - key: "ack".to_string(), - path: paths.ack_key_file.clone(), - err: e, - })?; - - // insert all required information into the gateways store - // (I hate that we have to do it, but that's currently the simplest thing to do) - let storage = setup_fs_gateways_storage(&paths.gateway_registrations).await?; - store_gateway_details(&storage, &gateway_details).await?; - set_active_gateway(&storage, &gateway_details.gateway_id().to_base58_string()).await?; - - Ok(()) } #[instrument(skip_all)] @@ -951,7 +811,7 @@ pub async fn try_upgrade_config_v3>( ConfigV3::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; - let exit_gateway_paths = ExitGatewayPaths::new( + let exit_gateway_paths = ServiceProvidersPaths::new( old_cfg .exit_gateway .storage_paths diff --git a/nym-node/src/config/old_configs/old_config_v4.rs b/nym-node/src/config/old_configs/old_config_v4.rs index 03a32b2b39..fb6d3aaa30 100644 --- a/nym-node/src/config/old_configs/old_config_v4.rs +++ b/nym-node/src/config/old_configs/old_config_v4.rs @@ -3,22 +3,14 @@ #![allow(dead_code)] -use crate::{config::*, error::KeyIOFailure}; +use crate::config::*; use nym_client_core_config_types::{ disk_persistence::{ClientKeysPaths, CommonClientPaths}, DebugConfig as ClientDebugConfig, }; use nym_config::{defaults::TICKETBOOK_VALIDITY_DAYS, serde_helpers::de_maybe_port}; -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_network_requester::{ - set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, - GatewayDetails, -}; -use nym_pemstore::{store_key, store_keypair}; -use nym_sphinx_acknowledgements::AckKey; use old_configs::old_config_v5::*; use persistence::*; -use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use tracing::instrument; @@ -29,25 +21,6 @@ pub struct WireguardPathsV4 { pub public_diffie_hellman_key_file: PathBuf, } -impl WireguardPathsV4 { - pub fn new>(data_dir: P) -> Self { - let data_dir = data_dir.as_ref(); - WireguardPathsV4 { - private_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME), - public_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME), - } - } - - pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath { - nym_pemstore::KeyPairPath::new( - &self.private_diffie_hellman_key_file, - &self.public_diffie_hellman_key_file, - ) - } -} - #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct WireguardV4 { @@ -933,77 +906,7 @@ pub struct ConfigV4 { pub logging: LoggingSettingsV4, } -impl NymConfigTemplate for ConfigV4 { - fn template(&self) -> &'static str { - CONFIG_TEMPLATE - } -} - impl ConfigV4 { - pub fn save(&self) -> Result<(), NymNodeError> { - let save_location = self.save_location(); - debug!( - "attempting to save config file to '{}'", - save_location.display() - ); - save_formatted_config_to_file(self, &save_location).map_err(|source| { - NymNodeError::ConfigSaveFailure { - id: self.id.clone(), - path: save_location, - source, - } - }) - } - - pub fn save_location(&self) -> PathBuf { - self.save_path - .clone() - .unwrap_or(self.default_save_location()) - } - - pub fn default_save_location(&self) -> PathBuf { - default_config_filepath(&self.id) - } - - pub fn default_data_directory>(config_path: P) -> Result { - let config_path = config_path.as_ref(); - - // we got a proper path to the .toml file - let Some(config_dir) = config_path.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - let Some(config_dir_name) = config_dir.file_name() else { - error!( - "could not obtain parent directory name of '{}'. Have you used relative paths?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - if config_dir_name != DEFAULT_CONFIG_DIR { - error!( - "the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported", - config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN") - ); - return Err(NymNodeError::DataDirDerivationFailure); - } - - let Some(node_dir) = config_dir.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_dir.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - Ok(node_dir.join(DEFAULT_DATA_DIR)) - } - // simple wrapper that reads config file and assigns path location fn read_from_path>(path: P) -> Result { let path = path.as_ref(); @@ -1016,49 +919,6 @@ impl ConfigV4 { debug!("loaded config file from {}", path.display()); Ok(loaded) } - - pub fn read_from_toml_file>(path: P) -> Result { - Self::read_from_path(path) - } -} - -pub async fn initialise( - paths: &AuthenticatorPaths, - public_key: nym_crypto::asymmetric::identity::PublicKey, -) -> Result<(), NymNodeError> { - let mut rng = OsRng; - let ed25519_keys = ed25519::KeyPair::new(&mut rng); - let x25519_keys = x25519::KeyPair::new(&mut rng); - let aes128ctr_key = AckKey::new(&mut rng); - let gateway_details = GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into(); - - store_keypair(&ed25519_keys, &paths.ed25519_identity_storage_paths()).map_err(|e| { - KeyIOFailure::KeyPairStoreFailure { - keys: "ed25519-identity".to_string(), - paths: paths.ed25519_identity_storage_paths(), - err: e, - } - })?; - store_keypair(&x25519_keys, &paths.x25519_diffie_hellman_storage_paths()).map_err(|e| { - KeyIOFailure::KeyPairStoreFailure { - keys: "x25519-dh".to_string(), - paths: paths.x25519_diffie_hellman_storage_paths(), - err: e, - } - })?; - store_key(&aes128ctr_key, &paths.ack_key_file).map_err(|e| KeyIOFailure::KeyStoreFailure { - key: "ack".to_string(), - path: paths.ack_key_file.clone(), - err: e, - })?; - - // insert all required information into the gateways store - // (I hate that we have to do it, but that's currently the simplest thing to do) - let storage = setup_fs_gateways_storage(&paths.gateway_registrations).await?; - store_gateway_details(&storage, &gateway_details).await?; - set_active_gateway(&storage, &gateway_details.gateway_id().to_base58_string()).await?; - - Ok(()) } #[instrument(skip_all)] @@ -1073,7 +933,7 @@ pub async fn try_upgrade_config_v4>( ConfigV4::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; - let exit_gateway_paths = ExitGatewayPaths::new( + let exit_gateway_paths = ServiceProvidersPaths::new( old_cfg .exit_gateway .storage_paths @@ -1082,7 +942,7 @@ pub async fn try_upgrade_config_v4>( .ok_or(NymNodeError::DataDirDerivationFailure)?, ); - let entry_gateway_paths = EntryGatewayPaths::new( + let entry_gateway_paths = GatewayTasksPaths::new( old_cfg .entry_gateway .storage_paths diff --git a/nym-node/src/config/old_configs/old_config_v5.rs b/nym-node/src/config/old_configs/old_config_v5.rs index 4a1cb57c64..0984802358 100644 --- a/nym-node/src/config/old_configs/old_config_v5.rs +++ b/nym-node/src/config/old_configs/old_config_v5.rs @@ -3,27 +3,14 @@ #![allow(dead_code)] -use crate::{config::*, error::KeyIOFailure}; -use entry_gateway::{Debug as EntryGatewayConfigDebug, ZkNymTicketHandlerDebug}; -use exit_gateway::{ - Debug as ExitGatewayConfigDebug, IpPacketRouter, IpPacketRouterDebug, NetworkRequester, - NetworkRequesterDebug, -}; -use mixnode::{Verloc, VerlocDebug}; +use crate::config::*; use nym_client_core_config_types::{ disk_persistence::{ClientKeysPaths, CommonClientPaths}, DebugConfig as ClientDebugConfig, }; use nym_config::{defaults::TICKETBOOK_VALIDITY_DAYS, serde_helpers::de_maybe_port}; -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_network_requester::{ - set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, - GatewayDetails, -}; -use nym_pemstore::{store_key, store_keypair}; -use nym_sphinx_acknowledgements::AckKey; +use old_configs::old_config_v6::*; use persistence::*; -use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use tracing::instrument; @@ -34,25 +21,6 @@ pub struct WireguardPathsV5 { pub public_diffie_hellman_key_file: PathBuf, } -impl WireguardPathsV5 { - pub fn new>(data_dir: P) -> Self { - let data_dir = data_dir.as_ref(); - WireguardPathsV5 { - private_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME), - public_diffie_hellman_key_file: data_dir - .join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME), - } - } - - pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath { - nym_pemstore::KeyPairPath::new( - &self.private_diffie_hellman_key_file, - &self.public_diffie_hellman_key_file, - ) - } -} - #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct WireguardV5 { @@ -94,12 +62,12 @@ pub enum NodeModeV5 { ExitGateway, } -impl From for NodeMode { +impl From for NodeModeV6 { fn from(config: NodeModeV5) -> Self { match config { - NodeModeV5::Mixnode => NodeMode::Mixnode, - NodeModeV5::EntryGateway => NodeMode::EntryGateway, - NodeModeV5::ExitGateway => NodeMode::ExitGateway, + NodeModeV5::Mixnode => NodeModeV6::Mixnode, + NodeModeV5::EntryGateway => NodeModeV6::EntryGateway, + NodeModeV5::ExitGateway => NodeModeV6::ExitGateway, } } } @@ -942,77 +910,7 @@ pub struct ConfigV5 { pub logging: LoggingSettingsV5, } -impl NymConfigTemplate for ConfigV5 { - fn template(&self) -> &'static str { - CONFIG_TEMPLATE - } -} - impl ConfigV5 { - pub fn save(&self) -> Result<(), NymNodeError> { - let save_location = self.save_location(); - debug!( - "attempting to save config file to '{}'", - save_location.display() - ); - save_formatted_config_to_file(self, &save_location).map_err(|source| { - NymNodeError::ConfigSaveFailure { - id: self.id.clone(), - path: save_location, - source, - } - }) - } - - pub fn save_location(&self) -> PathBuf { - self.save_path - .clone() - .unwrap_or(self.default_save_location()) - } - - pub fn default_save_location(&self) -> PathBuf { - default_config_filepath(&self.id) - } - - pub fn default_data_directory>(config_path: P) -> Result { - let config_path = config_path.as_ref(); - - // we got a proper path to the .toml file - let Some(config_dir) = config_path.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - let Some(config_dir_name) = config_dir.file_name() else { - error!( - "could not obtain parent directory name of '{}'. Have you used relative paths?", - config_path.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - if config_dir_name != DEFAULT_CONFIG_DIR { - error!( - "the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported", - config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN") - ); - return Err(NymNodeError::DataDirDerivationFailure); - } - - let Some(node_dir) = config_dir.parent() else { - error!( - "'{}' does not have a parent directory. Have you pointed to the fs root?", - config_dir.display() - ); - return Err(NymNodeError::DataDirDerivationFailure); - }; - - Ok(node_dir.join(DEFAULT_DATA_DIR)) - } - // simple wrapper that reads config file and assigns path location fn read_from_path>(path: P) -> Result { let path = path.as_ref(); @@ -1025,56 +923,13 @@ impl ConfigV5 { debug!("loaded config file from {}", path.display()); Ok(loaded) } - - pub fn read_from_toml_file>(path: P) -> Result { - Self::read_from_path(path) - } -} - -pub async fn initialise( - paths: &AuthenticatorPaths, - public_key: nym_crypto::asymmetric::identity::PublicKey, -) -> Result<(), NymNodeError> { - let mut rng = OsRng; - let ed25519_keys = ed25519::KeyPair::new(&mut rng); - let x25519_keys = x25519::KeyPair::new(&mut rng); - let aes128ctr_key = AckKey::new(&mut rng); - let gateway_details = GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into(); - - store_keypair(&ed25519_keys, &paths.ed25519_identity_storage_paths()).map_err(|e| { - KeyIOFailure::KeyPairStoreFailure { - keys: "ed25519-identity".to_string(), - paths: paths.ed25519_identity_storage_paths(), - err: e, - } - })?; - store_keypair(&x25519_keys, &paths.x25519_diffie_hellman_storage_paths()).map_err(|e| { - KeyIOFailure::KeyPairStoreFailure { - keys: "x25519-dh".to_string(), - paths: paths.x25519_diffie_hellman_storage_paths(), - err: e, - } - })?; - store_key(&aes128ctr_key, &paths.ack_key_file).map_err(|e| KeyIOFailure::KeyStoreFailure { - key: "ack".to_string(), - path: paths.ack_key_file.clone(), - err: e, - })?; - - // insert all required information into the gateways store - // (I hate that we have to do it, but that's currently the simplest thing to do) - let storage = setup_fs_gateways_storage(&paths.gateway_registrations).await?; - store_gateway_details(&storage, &gateway_details).await?; - set_active_gateway(&storage, &gateway_details.gateway_id().to_base58_string()).await?; - - Ok(()) } #[instrument(skip_all)] pub async fn try_upgrade_config_v5>( path: P, prev_config: Option, -) -> Result { +) -> Result { debug!("attempting to load v5 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config @@ -1087,21 +942,21 @@ pub async fn try_upgrade_config_v5>( IpAddr::V6(ipv6_addr) => (WG_TUN_DEVICE_IP_ADDRESS_V4, ipv6_addr), }; - let cfg = Config { + let cfg = ConfigV6 { save_path: old_cfg.save_path, id: old_cfg.id, mode: old_cfg.mode.into(), - host: Host { + host: HostV6 { public_ips: old_cfg.host.public_ips, hostname: old_cfg.host.hostname, location: old_cfg.host.location, }, - mixnet: Mixnet { + mixnet: MixnetV6 { 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, - debug: MixnetDebug { + debug: MixnetDebugV6 { packet_forwarding_initial_backoff: old_cfg .mixnet .debug @@ -1115,8 +970,8 @@ pub async fn try_upgrade_config_v5>( unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, }, }, - storage_paths: NymNodePaths { - keys: KeysPaths { + storage_paths: NymNodePathsV6 { + keys: KeysPathsV6 { private_ed25519_identity_key_file: old_cfg .storage_paths .keys @@ -1144,7 +999,7 @@ pub async fn try_upgrade_config_v5>( }, description: old_cfg.storage_paths.description, }, - http: Http { + http: HttpV6 { 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, @@ -1152,7 +1007,7 @@ pub async fn try_upgrade_config_v5>( expose_system_hardware: old_cfg.http.expose_system_hardware, expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, }, - wireguard: Wireguard { + wireguard: WireguardV6 { enabled: old_cfg.wireguard.enabled, bind_address: old_cfg.wireguard.bind_address, private_ipv4, @@ -1160,7 +1015,7 @@ pub async fn try_upgrade_config_v5>( announced_port: old_cfg.wireguard.announced_port, private_network_prefix_v4: old_cfg.wireguard.private_network_prefix, private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6, - storage_paths: WireguardPaths { + storage_paths: WireguardPathsV6 { private_diffie_hellman_key_file: old_cfg .wireguard .storage_paths @@ -1171,12 +1026,12 @@ pub async fn try_upgrade_config_v5>( .public_diffie_hellman_key_file, }, }, - mixnode: MixnodeConfig { - storage_paths: MixnodePaths {}, - verloc: Verloc { + mixnode: MixnodeConfigV6 { + storage_paths: MixnodePathsV6 {}, + verloc: VerlocV6 { bind_address: old_cfg.mixnode.verloc.bind_address, announce_port: old_cfg.mixnode.verloc.announce_port, - debug: VerlocDebug { + debug: VerlocDebugV6 { packets_per_node: old_cfg.mixnode.verloc.debug.packets_per_node, connection_timeout: old_cfg.mixnode.verloc.debug.connection_timeout, packet_timeout: old_cfg.mixnode.verloc.debug.packet_timeout, @@ -1186,17 +1041,17 @@ pub async fn try_upgrade_config_v5>( retry_timeout: old_cfg.mixnode.verloc.debug.retry_timeout, }, }, - debug: mixnode::Debug { + debug: DebugV6 { node_stats_logging_delay: old_cfg.mixnode.debug.node_stats_logging_delay, node_stats_updating_delay: old_cfg.mixnode.debug.node_stats_updating_delay, }, }, - entry_gateway: EntryGatewayConfig { - storage_paths: EntryGatewayPaths { + entry_gateway: EntryGatewayConfigV6 { + storage_paths: EntryGatewayPathsV6 { clients_storage: old_cfg.entry_gateway.storage_paths.clients_storage, stats_storage: old_cfg.entry_gateway.storage_paths.stats_storage, cosmos_mnemonic: old_cfg.entry_gateway.storage_paths.cosmos_mnemonic, - authenticator: AuthenticatorPaths { + authenticator: AuthenticatorPathsV6 { private_ed25519_identity_key_file: old_cfg .entry_gateway .storage_paths @@ -1238,9 +1093,9 @@ pub async fn try_upgrade_config_v5>( bind_address: old_cfg.entry_gateway.bind_address, announce_ws_port: old_cfg.entry_gateway.announce_ws_port, announce_wss_port: old_cfg.entry_gateway.announce_wss_port, - debug: EntryGatewayConfigDebug { + debug: EntryGatewayConfigDebugV6 { message_retrieval_limit: old_cfg.entry_gateway.debug.message_retrieval_limit, - zk_nym_tickets: ZkNymTicketHandlerDebug { + zk_nym_tickets: ZkNymTicketHandlerDebugV6 { revocation_bandwidth_penalty: old_cfg .entry_gateway .debug @@ -1265,11 +1120,11 @@ pub async fn try_upgrade_config_v5>( }, }, }, - exit_gateway: ExitGatewayConfig { - storage_paths: ExitGatewayPaths { + exit_gateway: ExitGatewayConfigV6 { + storage_paths: ExitGatewayPathsV6 { clients_storage: old_cfg.exit_gateway.storage_paths.clients_storage, stats_storage: old_cfg.exit_gateway.storage_paths.stats_storage, - network_requester: NetworkRequesterPaths { + network_requester: NetworkRequesterPathsV6 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -1306,7 +1161,7 @@ pub async fn try_upgrade_config_v5>( .network_requester .gateway_registrations, }, - ip_packet_router: IpPacketRouterPaths { + ip_packet_router: IpPacketRouterPathsV6 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -1343,7 +1198,7 @@ pub async fn try_upgrade_config_v5>( .ip_packet_router .gateway_registrations, }, - authenticator: AuthenticatorPaths { + authenticator: AuthenticatorPathsV6 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -1383,8 +1238,8 @@ pub async fn try_upgrade_config_v5>( }, open_proxy: old_cfg.exit_gateway.open_proxy, upstream_exit_policy_url: old_cfg.exit_gateway.upstream_exit_policy_url, - network_requester: NetworkRequester { - debug: NetworkRequesterDebug { + network_requester: NetworkRequesterV6 { + debug: NetworkRequesterDebugV6 { enabled: old_cfg.exit_gateway.network_requester.debug.enabled, disable_poisson_rate: old_cfg .exit_gateway @@ -1394,8 +1249,8 @@ pub async fn try_upgrade_config_v5>( client_debug: old_cfg.exit_gateway.network_requester.debug.client_debug, }, }, - ip_packet_router: IpPacketRouter { - debug: IpPacketRouterDebug { + ip_packet_router: IpPacketRouterV6 { + debug: IpPacketRouterDebugV6 { enabled: old_cfg.exit_gateway.ip_packet_router.debug.enabled, disable_poisson_rate: old_cfg .exit_gateway @@ -1405,12 +1260,12 @@ pub async fn try_upgrade_config_v5>( client_debug: old_cfg.exit_gateway.ip_packet_router.debug.client_debug, }, }, - debug: ExitGatewayConfigDebug { + debug: ExitGatewayDebugV6 { message_retrieval_limit: old_cfg.exit_gateway.debug.message_retrieval_limit, }, }, authenticator: Default::default(), - logging: LoggingSettings {}, + logging: LoggingSettingsV6 {}, }; Ok(cfg) diff --git a/nym-node/src/config/old_configs/old_config_v6.rs b/nym-node/src/config/old_configs/old_config_v6.rs new file mode 100644 index 0000000000..b920bf035f --- /dev/null +++ b/nym-node/src/config/old_configs/old_config_v6.rs @@ -0,0 +1,1256 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#![allow(dead_code)] + +use crate::config::authenticator::{Authenticator, AuthenticatorDebug}; +use crate::config::gateway_tasks::ZkNymTicketHandlerDebug; +use crate::config::service_providers::{ + IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug, +}; +use crate::config::*; +use crate::error::NymNodeError; +use celes::Country; +use clap::ValueEnum; +use nym_client_core_config_types::{ + disk_persistence::{ClientKeysPaths, CommonClientPaths}, + DebugConfig as ClientDebugConfig, +}; +use nym_config::defaults::{mainnet, var_names}; +use nym_config::helpers::inaddr_any; +use nym_config::{ + defaults::TICKETBOOK_VALIDITY_DAYS, + serde_helpers::{de_maybe_port, de_maybe_stringified}, +}; +use nym_config::{parse_urls, read_config_from_toml_file}; +use persistence::*; +use serde::{Deserialize, Serialize}; +use std::env; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use tracing::{debug, instrument}; +use url::Url; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardPathsV6 { + pub private_diffie_hellman_key_file: PathBuf, + pub public_diffie_hellman_key_file: PathBuf, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardV6 { + /// 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: `0.0.0.0: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, + + /// 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_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: WireguardPathsV6, +} + +// a temporary solution until all "types" are run at the same time +#[derive(Debug, Default, Serialize, Deserialize, ValueEnum, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum NodeModeV6 { + #[default] + Mixnode, + + EntryGateway, + + ExitGateway, +} + +impl From for NodeModes { + fn from(config: NodeModeV6) -> Self { + match config { + NodeModeV6::Mixnode => *NodeModes::default().with_mixnode(), + NodeModeV6::EntryGateway => *NodeModes::default().with_entry(), + // in old version exit implied entry + NodeModeV6::ExitGateway => *NodeModes::default().with_entry().with_exit(), + } + } +} + +// TODO: this is very much a WIP. we need proper ssl certificate support here +#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct HostV6 { + /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. + /// If no values are provided, when this node gets included in the network, + /// its ip addresses will be populated by whatever value is resolved by associated nym-api. + pub public_ips: Vec, + + /// Optional hostname of this node, for example nymtech.net. + // TODO: this is temporary. to be replaced by pulling the data directly from the certs. + #[serde(deserialize_with = "de_maybe_stringified")] + pub hostname: Option, + + /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location + #[serde(deserialize_with = "de_maybe_stringified")] + pub location: Option, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetDebugV6 { + /// Initial value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_initial_backoff: Duration, + + /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_maximum_backoff: Duration, + + /// Timeout for establishing initial connection when trying to forward a sphinx packet. + #[serde(with = "humantime_serde")] + pub initial_connection_timeout: Duration, + + /// Maximum number of packets that can be stored waiting to get sent to a particular connection. + pub maximum_connection_buffer_size: usize, + + /// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented) + pub unsafe_disable_noise: bool, +} + +impl MixnetDebugV6 { + const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); + const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); + const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); + const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; +} + +impl Default for MixnetDebugV6 { + fn default() -> Self { + MixnetDebugV6 { + packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT, + maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + // to be changed by @SW once the implementation is there + unsafe_disable_noise: true, + } + } +} + +impl Default for MixnetV6 { + fn default() -> Self { + // SAFETY: + // our hardcoded values should always be valid + #[allow(clippy::expect_used)] + // is if there's anything set in the environment, otherwise fallback to mainnet + let nym_api_urls = if let Ok(env_value) = env::var(var_names::NYM_API) { + parse_urls(&env_value) + } else { + vec![mainnet::NYM_API.parse().expect("Invalid default API URL")] + }; + + #[allow(clippy::expect_used)] + let nyxd_urls = if let Ok(env_value) = env::var(var_names::NYXD) { + parse_urls(&env_value) + } else { + vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")] + }; + + MixnetV6 { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_MIXNET_PORT), + announce_port: None, + nym_api_urls, + nyxd_urls, + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetV6 { + /// Address this node will bind to for listening for mixnet packets + /// default: `0.0.0.0:1789` + pub bind_address: SocketAddr, + + /// If applicable, custom port announced in the self-described API that other clients and nodes + /// will use. + /// Useful when the node is behind a proxy. + #[serde(deserialize_with = "de_maybe_port")] + pub announce_port: Option, + + /// Addresses to nym APIs from which the node gets the view of the network. + pub nym_api_urls: Vec, + + /// Addresses to nyxd which the node uses to interact with the nyx chain. + pub nyxd_urls: Vec, + + #[serde(default)] + pub debug: MixnetDebugV6, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct KeysPathsV6 { + /// Path to file containing ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing x25519 sphinx private key. + pub private_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 sphinx public key. + pub public_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 noise private key. + pub private_x25519_noise_key_file: PathBuf, + + /// Path to file containing x25519 noise public key. + pub public_x25519_noise_key_file: PathBuf, +} + +impl KeysPathsV6 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + + KeysPathsV6 { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_sphinx_key_file: data_dir + .join(DEFAULT_X25519_PRIVATE_SPHINX_KEY_FILENAME), + public_x25519_sphinx_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_SPHINX_KEY_FILENAME), + private_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME), + public_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_sphinx_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_sphinx_key_file, + &self.public_x25519_sphinx_key_file, + ) + } + + pub fn x25519_noise_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_noise_key_file, + &self.public_x25519_noise_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NymNodePathsV6 { + pub keys: KeysPathsV6, + + /// Path to a file containing basic node description: human-readable name, website, details, etc. + pub description: PathBuf, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct HttpV6 { + /// Socket address this node will use for binding its http API. + /// default: `0.0.0.0:8080` + pub bind_address: SocketAddr, + + /// Path to assets directory of custom landing page of this node. + #[serde(deserialize_with = "de_maybe_stringified")] + pub landing_page_assets_path: Option, + + /// An optional bearer token for accessing certain http endpoints. + /// Currently only used for obtaining mixnode's stats. + #[serde(default)] + pub access_token: Option, + + /// Specify whether basic system information should be exposed. + /// default: true + pub expose_system_info: bool, + + /// Specify whether basic system hardware information should be exposed. + /// This option is superseded by `expose_system_info` + /// default: true + pub expose_system_hardware: bool, + + /// Specify whether detailed system crypto hardware information should be exposed. + /// This option is superseded by `expose_system_hardware` + /// default: true + pub expose_crypto_hardware: bool, +} + +impl Default for HttpV6 { + fn default() -> Self { + HttpV6 { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_HTTP_PORT), + landing_page_assets_path: None, + access_token: None, + expose_system_info: true, + expose_system_hardware: true, + expose_crypto_hardware: true, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodePathsV6 {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DebugV6 { + /// Delay between each subsequent node statistics being logged to the console + #[serde(with = "humantime_serde")] + pub node_stats_logging_delay: Duration, + + /// Delay between each subsequent node statistics being updated + #[serde(with = "humantime_serde")] + pub node_stats_updating_delay: Duration, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocDebugV6 { + /// Specifies number of echo packets sent to each node during a measurement run. + pub packets_per_node: usize, + + /// Specifies maximum amount of time to wait for the connection to get established. + #[serde(with = "humantime_serde")] + pub connection_timeout: Duration, + + /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. + #[serde(with = "humantime_serde")] + pub packet_timeout: Duration, + + /// Specifies delay between subsequent test packets being sent (after receiving a reply). + #[serde(with = "humantime_serde")] + pub delay_between_packets: Duration, + + /// Specifies number of nodes being tested at once. + pub tested_nodes_batch_size: usize, + + /// Specifies delay between subsequent test runs. + #[serde(with = "humantime_serde")] + pub testing_interval: Duration, + + /// Specifies delay between attempting to run the measurement again if the previous run failed + /// due to being unable to get the list of nodes. + #[serde(with = "humantime_serde")] + pub retry_timeout: Duration, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocV6 { + /// Socket address this node will use for binding its verloc API. + /// default: `0.0.0.0:1790` + pub bind_address: SocketAddr, + + #[serde(deserialize_with = "de_maybe_port")] + pub announce_port: Option, + + #[serde(default)] + pub debug: VerlocDebugV6, +} + +impl VerlocDebugV6 { + const DEFAULT_PACKETS_PER_NODE: usize = 100; + const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); + const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); + const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); + const DEFAULT_BATCH_SIZE: usize = 50; + const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); + const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); +} + +impl Default for VerlocDebugV6 { + fn default() -> Self { + VerlocDebugV6 { + packets_per_node: Self::DEFAULT_PACKETS_PER_NODE, + connection_timeout: Self::DEFAULT_CONNECTION_TIMEOUT, + packet_timeout: Self::DEFAULT_PACKET_TIMEOUT, + delay_between_packets: Self::DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: Self::DEFAULT_BATCH_SIZE, + testing_interval: Self::DEFAULT_TESTING_INTERVAL, + retry_timeout: Self::DEFAULT_RETRY_TIMEOUT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodeConfigV6 { + pub storage_paths: MixnodePathsV6, + + pub verloc: VerlocV6, + + #[serde(default)] + pub debug: DebugV6, +} + +impl DebugV6 { + const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); + const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000); +} + +impl Default for DebugV6 { + fn default() -> Self { + DebugV6 { + node_stats_logging_delay: Self::DEFAULT_NODE_STATS_LOGGING_DELAY, + node_stats_updating_delay: Self::DEFAULT_NODE_STATS_UPDATING_DELAY, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayPathsV6 { + /// Path to sqlite database containing all persistent data: messages for offline clients, + /// derived shared keys and available client bandwidths. + pub clients_storage: PathBuf, + + pub stats_storage: PathBuf, + + /// Path to file containing cosmos account mnemonic used for zk-nym redemption. + pub cosmos_mnemonic: PathBuf, + + pub authenticator: AuthenticatorPathsV6, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct ZkNymTicketHandlerDebugV6 { + /// Specifies the multiplier for revoking a malformed/double-spent ticket + /// (if it has to go all the way to the nym-api for verification) + /// e.g. if one ticket grants 100Mb and `revocation_bandwidth_penalty` is set to 1.5, + /// the client will lose 150Mb + pub revocation_bandwidth_penalty: f32, + + /// Specifies the interval for attempting to resolve any failed, pending operations, + /// such as ticket verification or redemption. + #[serde(with = "humantime_serde")] + pub pending_poller: Duration, + + pub minimum_api_quorum: f32, + + /// Specifies the minimum number of tickets this gateway will attempt to redeem. + pub minimum_redemption_tickets: usize, + + /// Specifies the maximum time between two subsequent tickets redemptions. + /// That's required as nym-apis will purge all ticket information for tickets older than maximum validity. + #[serde(with = "humantime_serde")] + pub maximum_time_between_redemption: Duration, +} + +impl ZkNymTicketHandlerDebugV6 { + pub const DEFAULT_REVOCATION_BANDWIDTH_PENALTY: f32 = 10.0; + pub const DEFAULT_PENDING_POLLER: Duration = Duration::from_secs(300); + pub const DEFAULT_MINIMUM_API_QUORUM: f32 = 0.8; + pub const DEFAULT_MINIMUM_REDEMPTION_TICKETS: usize = 100; + + // use min(4/5 of max validity, validity - 1), but making sure it's no greater than 1 day + // ASSUMPTION: our validity period is AT LEAST 2 days + // + // this could have been a constant, but it's more readable as a function + pub const fn default_maximum_time_between_redemption() -> Duration { + let desired_secs = TICKETBOOK_VALIDITY_DAYS * (86400 * 4) / 5; + let desired_secs_alt = (TICKETBOOK_VALIDITY_DAYS - 1) * 86400; + + // can't use `min` in const context + let target_secs = if desired_secs < desired_secs_alt { + desired_secs + } else { + desired_secs_alt + }; + + assert!( + target_secs > 86400, + "the maximum time between redemption can't be lower than 1 day!" + ); + Duration::from_secs(target_secs as u64) + } +} + +impl Default for ZkNymTicketHandlerDebugV6 { + fn default() -> Self { + ZkNymTicketHandlerDebugV6 { + revocation_bandwidth_penalty: Self::DEFAULT_REVOCATION_BANDWIDTH_PENALTY, + pending_poller: Self::DEFAULT_PENDING_POLLER, + minimum_api_quorum: Self::DEFAULT_MINIMUM_API_QUORUM, + minimum_redemption_tickets: Self::DEFAULT_MINIMUM_REDEMPTION_TICKETS, + maximum_time_between_redemption: Self::default_maximum_time_between_redemption(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfigDebugV6 { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, + pub zk_nym_tickets: ZkNymTicketHandlerDebugV6, +} + +impl EntryGatewayConfigDebugV6 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for EntryGatewayConfigDebugV6 { + fn default() -> Self { + EntryGatewayConfigDebugV6 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + zk_nym_tickets: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfigV6 { + pub storage_paths: EntryGatewayPathsV6, + + /// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet + /// or if it also accepts non-paying clients + pub enforce_zk_nyms: bool, + + /// Socket address this node will use for binding its client websocket API. + /// default: `0.0.0.0:9000` + pub bind_address: SocketAddr, + + /// Custom announced port for listening for websocket client traffic. + /// If unspecified, the value from the `bind_address` will be used instead + /// default: None + #[serde(deserialize_with = "de_maybe_port")] + pub announce_ws_port: Option, + + /// If applicable, announced port for listening for secure websocket client traffic. + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub announce_wss_port: Option, + + #[serde(default)] + pub debug: EntryGatewayConfigDebugV6, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NetworkRequesterPathsV6 { + /// Path to file containing network requester ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct IpPacketRouterPathsV6 { + /// Path to file containing ip packet router ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct AuthenticatorPathsV6 { + /// Path to file containing authenticator ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing authenticator ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing authenticator x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing authenticator x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +impl AuthenticatorPathsV6 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + AuthenticatorPathsV6 { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_AUTH_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_AUTH_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_AUTH_PRIVATE_DH_KEY_FILENAME), + public_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_AUTH_PUBLIC_DH_KEY_FILENAME), + ack_key_file: data_dir.join(DEFAULT_AUTH_ACK_KEY_FILENAME), + reply_surb_database: data_dir.join(DEFAULT_AUTH_REPLY_SURB_DB_FILENAME), + gateway_registrations: data_dir.join(DEFAULT_AUTH_GATEWAYS_DB_FILENAME), + } + } + + pub fn to_common_client_paths(&self) -> CommonClientPaths { + CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: self.private_ed25519_identity_key_file.clone(), + public_identity_key_file: self.public_ed25519_identity_key_file.clone(), + private_encryption_key_file: self.private_x25519_diffie_hellman_key_file.clone(), + public_encryption_key_file: self.public_x25519_diffie_hellman_key_file.clone(), + ack_key_file: self.ack_key_file.clone(), + }, + gateway_registrations: self.gateway_registrations.clone(), + + // not needed for embedded providers + credentials_database: Default::default(), + reply_surb_database: self.reply_surb_database.clone(), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_diffie_hellman_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_diffie_hellman_key_file, + &self.public_x25519_diffie_hellman_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayPathsV6 { + pub clients_storage: PathBuf, + + pub stats_storage: PathBuf, + + pub network_requester: NetworkRequesterPathsV6, + + pub ip_packet_router: IpPacketRouterPathsV6, + + pub authenticator: AuthenticatorPathsV6, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct AuthenticatorV6 { + #[serde(default)] + pub debug: AuthenticatorDebugV6, +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct AuthenticatorDebugV6 { + /// Specifies whether authenticator service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run + /// the authenticator. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for AuthenticatorDebugV6 { + fn default() -> Self { + AuthenticatorDebugV6 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[allow(clippy::derivable_impls)] +impl Default for AuthenticatorV6 { + fn default() -> Self { + AuthenticatorV6 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IpPacketRouterDebugV6 { + /// Specifies whether ip packet routing service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for IpPacketRouterDebugV6 { + fn default() -> Self { + IpPacketRouterDebugV6 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct IpPacketRouterV6 { + #[serde(default)] + pub debug: IpPacketRouterDebugV6, +} + +#[allow(clippy::derivable_impls)] +impl Default for IpPacketRouterV6 { + fn default() -> Self { + IpPacketRouterV6 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterDebugV6 { + /// Specifies whether network requester service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for NetworkRequesterDebugV6 { + fn default() -> Self { + NetworkRequesterDebugV6 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterV6 { + #[serde(default)] + pub debug: NetworkRequesterDebugV6, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequesterV6 { + fn default() -> Self { + NetworkRequesterV6 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayDebugV6 { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, +} + +impl ExitGatewayDebugV6 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for ExitGatewayDebugV6 { + fn default() -> Self { + ExitGatewayDebugV6 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayConfigV6 { + pub storage_paths: ExitGatewayPathsV6, + + /// specifies whether this exit node should run in 'open-proxy' mode + /// and thus would attempt to resolve **ANY** request it receives. + pub open_proxy: bool, + + /// Specifies the url for an upstream source of the exit policy used by this node. + pub upstream_exit_policy_url: Url, + + pub network_requester: NetworkRequesterV6, + + pub ip_packet_router: IpPacketRouterV6, + + #[serde(default)] + pub debug: ExitGatewayDebugV6, +} + +#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingSettingsV6 { + // well, we need to implement something here at some point... +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV6 { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + + /// Human-readable ID of this particular node. + pub id: String, + + /// Current mode of this nym-node. + /// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway) + pub mode: NodeModeV6, + + pub host: HostV6, + + pub mixnet: MixnetV6, + + /// Storage paths to persistent nym-node data, such as its long term keys. + pub storage_paths: NymNodePathsV6, + + #[serde(default)] + pub http: HttpV6, + + pub wireguard: WireguardV6, + + pub mixnode: MixnodeConfigV6, + + pub entry_gateway: EntryGatewayConfigV6, + + pub exit_gateway: ExitGatewayConfigV6, + + pub authenticator: AuthenticatorV6, + + #[serde(default)] + pub logging: LoggingSettingsV6, +} + +impl ConfigV6 { + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> Result { + let path = path.as_ref(); + let mut loaded: ConfigV6 = + 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_v6>( + path: P, + prev_config: Option, +) -> Result { + debug!("attempting to load v6 config..."); + + let old_cfg = if let Some(prev_config) = prev_config { + prev_config + } else { + ConfigV6::read_from_path(&path)? + }; + + let cfg = Config { + save_path: old_cfg.save_path, + id: old_cfg.id, + modes: old_cfg.mode.into(), + 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, + debug: MixnetDebug { + maximum_forward_packet_delay: MixnetDebug::DEFAULT_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, + }, + }, + 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, + private_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .private_x25519_sphinx_key_file, + public_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .public_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, + }, + 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, + }, + verloc: Verloc { + bind_address: old_cfg.mixnode.verloc.bind_address, + announce_port: old_cfg.mixnode.verloc.announce_port, + debug: VerlocDebug { + packets_per_node: old_cfg.mixnode.verloc.debug.packets_per_node, + connection_timeout: old_cfg.mixnode.verloc.debug.connection_timeout, + packet_timeout: old_cfg.mixnode.verloc.debug.packet_timeout, + delay_between_packets: old_cfg.mixnode.verloc.debug.delay_between_packets, + tested_nodes_batch_size: old_cfg.mixnode.verloc.debug.tested_nodes_batch_size, + testing_interval: old_cfg.mixnode.verloc.debug.testing_interval, + retry_timeout: old_cfg.mixnode.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_port: old_cfg.wireguard.announced_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.entry_gateway.storage_paths.clients_storage, + stats_storage: old_cfg.entry_gateway.storage_paths.stats_storage, + cosmos_mnemonic: old_cfg.entry_gateway.storage_paths.cosmos_mnemonic, + }, + enforce_zk_nyms: old_cfg.entry_gateway.enforce_zk_nyms, + bind_address: old_cfg.entry_gateway.bind_address, + announce_ws_port: old_cfg.entry_gateway.announce_ws_port, + announce_wss_port: old_cfg.entry_gateway.announce_wss_port, + debug: gateway_tasks::Debug { + message_retrieval_limit: old_cfg.entry_gateway.debug.message_retrieval_limit, + zk_nym_tickets: ZkNymTicketHandlerDebug { + revocation_bandwidth_penalty: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .revocation_bandwidth_penalty, + pending_poller: old_cfg.entry_gateway.debug.zk_nym_tickets.pending_poller, + minimum_api_quorum: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .minimum_api_quorum, + minimum_redemption_tickets: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .minimum_redemption_tickets, + maximum_time_between_redemption: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .maximum_time_between_redemption, + }, + }, + }, + service_providers: ServiceProvidersConfig { + storage_paths: ServiceProvidersPaths { + clients_storage: old_cfg.exit_gateway.storage_paths.clients_storage, + stats_storage: old_cfg.exit_gateway.storage_paths.stats_storage, + network_requester: NetworkRequesterPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .network_requester + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .network_requester + .gateway_registrations, + }, + ip_packet_router: IpPacketRouterPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .gateway_registrations, + }, + authenticator: AuthenticatorPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .authenticator + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .authenticator + .gateway_registrations, + }, + }, + open_proxy: old_cfg.exit_gateway.open_proxy, + upstream_exit_policy_url: old_cfg.exit_gateway.upstream_exit_policy_url, + network_requester: NetworkRequester { + debug: NetworkRequesterDebug { + enabled: old_cfg.exit_gateway.network_requester.debug.enabled, + disable_poisson_rate: old_cfg + .exit_gateway + .network_requester + .debug + .disable_poisson_rate, + client_debug: old_cfg.exit_gateway.network_requester.debug.client_debug, + }, + }, + ip_packet_router: IpPacketRouter { + debug: IpPacketRouterDebug { + enabled: old_cfg.exit_gateway.ip_packet_router.debug.enabled, + disable_poisson_rate: old_cfg + .exit_gateway + .ip_packet_router + .debug + .disable_poisson_rate, + client_debug: old_cfg.exit_gateway.ip_packet_router.debug.client_debug, + }, + }, + authenticator: Authenticator { + debug: AuthenticatorDebug { + enabled: old_cfg.authenticator.debug.enabled, + disable_poisson_rate: old_cfg.authenticator.debug.disable_poisson_rate, + client_debug: old_cfg.authenticator.debug.client_debug, + }, + }, + debug: service_providers::Debug { + message_retrieval_limit: old_cfg.exit_gateway.debug.message_retrieval_limit, + }, + }, + metrics: Default::default(), + logging: LoggingSettings {}, + debug: Default::default(), + }; + Ok(cfg) +} diff --git a/nym-node/src/config/persistence.rs b/nym-node/src/config/persistence.rs index e62ced0c55..b3db6c827b 100644 --- a/nym-node/src/config/persistence.rs +++ b/nym-node/src/config/persistence.rs @@ -19,8 +19,6 @@ pub const DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME: &str = "x25519_noise"; pub const DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME: &str = "x25519_noise.pub"; pub const DEFAULT_NYMNODE_DESCRIPTION_FILENAME: &str = "description.toml"; -pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml"; - // Mixnode: // Entry Gateway: @@ -72,7 +70,7 @@ impl NymNodePaths { NymNodePaths { keys: KeysPaths::new(data_dir), - description: data_dir.join(DEFAULT_DESCRIPTION_FILENAME), + description: data_dir.join(DEFAULT_NYMNODE_DESCRIPTION_FILENAME), } } } @@ -140,11 +138,7 @@ impl KeysPaths { #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] -pub struct MixnodePaths {} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct EntryGatewayPaths { +pub struct GatewayTasksPaths { /// Path to sqlite database containing all persistent data: messages for offline clients, /// derived shared keys, available client bandwidths and wireguard peers. pub clients_storage: PathBuf, @@ -154,17 +148,14 @@ pub struct EntryGatewayPaths { /// Path to file containing cosmos account mnemonic used for zk-nym redemption. pub cosmos_mnemonic: PathBuf, - - pub authenticator: AuthenticatorPaths, } -impl EntryGatewayPaths { +impl GatewayTasksPaths { pub fn new>(data_dir: P) -> Self { - EntryGatewayPaths { + GatewayTasksPaths { clients_storage: data_dir.as_ref().join(DEFAULT_CLIENTS_STORAGE_FILENAME), stats_storage: data_dir.as_ref().join(DEFAULT_STATS_STORAGE_FILENAME), cosmos_mnemonic: data_dir.as_ref().join(DEFAULT_MNEMONIC_FILENAME), - authenticator: AuthenticatorPaths::new(data_dir), } } @@ -208,7 +199,7 @@ impl EntryGatewayPaths { #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] -pub struct ExitGatewayPaths { +pub struct ServiceProvidersPaths { /// Path to sqlite database containing all persistent data: messages for offline clients, /// derived shared keys, available client bandwidths and wireguard peers. pub clients_storage: PathBuf, @@ -463,10 +454,10 @@ impl AuthenticatorPaths { } } -impl ExitGatewayPaths { +impl ServiceProvidersPaths { pub fn new>(data_dir: P) -> Self { let data_dir = data_dir.as_ref(); - ExitGatewayPaths { + ServiceProvidersPaths { clients_storage: data_dir.join(DEFAULT_CLIENTS_STORAGE_FILENAME), stats_storage: data_dir.join(DEFAULT_STATS_STORAGE_FILENAME), network_requester: NetworkRequesterPaths::new(data_dir), diff --git a/nym-node/src/config/service_providers.rs b/nym-node/src/config/service_providers.rs new file mode 100644 index 0000000000..0c66990926 --- /dev/null +++ b/nym-node/src/config/service_providers.rs @@ -0,0 +1,155 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::authenticator::Authenticator; +use crate::config::persistence::ServiceProvidersPaths; +use nym_client_core_config_types::DebugConfig as ClientDebugConfig; +use nym_config::defaults::mainnet; +use serde::{Deserialize, Serialize}; +use std::path::Path; +use url::Url; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ServiceProvidersConfig { + pub storage_paths: ServiceProvidersPaths, + + /// specifies whether this exit node should run in 'open-proxy' mode + /// and thus would attempt to resolve **ANY** request it receives. + pub open_proxy: bool, + + /// Specifies the url for an upstream source of the exit policy used by this node. + pub upstream_exit_policy_url: Url, + + pub network_requester: NetworkRequester, + + pub ip_packet_router: IpPacketRouter, + + pub authenticator: Authenticator, + + #[serde(default)] + pub debug: Debug, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Debug { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, +} + +impl Debug { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for Debug { + fn default() -> Self { + Debug { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } +} + +impl ServiceProvidersConfig { + pub fn new_default>(data_dir: P) -> Self { + #[allow(clippy::expect_used)] + // SAFETY: + // we expect our default values to be well-formed + ServiceProvidersConfig { + storage_paths: ServiceProvidersPaths::new(data_dir), + open_proxy: false, + upstream_exit_policy_url: mainnet::EXIT_POLICY_URL + .parse() + .expect("invalid default exit policy URL"), + network_requester: Default::default(), + ip_packet_router: Default::default(), + authenticator: Default::default(), + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequester { + #[serde(default)] + pub debug: NetworkRequesterDebug, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequester { + fn default() -> Self { + NetworkRequester { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterDebug { + /// Specifies whether network requester service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for NetworkRequesterDebug { + fn default() -> Self { + NetworkRequesterDebug { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct IpPacketRouter { + #[serde(default)] + pub debug: IpPacketRouterDebug, +} + +#[allow(clippy::derivable_impls)] +impl Default for IpPacketRouter { + fn default() -> Self { + IpPacketRouter { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IpPacketRouterDebug { + /// Specifies whether ip packet routing service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for IpPacketRouterDebug { + fn default() -> Self { + IpPacketRouterDebug { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} diff --git a/nym-node/src/config/template.rs b/nym-node/src/config/template.rs index 58edfae032..459b79bca1 100644 --- a/nym-node/src/config/template.rs +++ b/nym-node/src/config/template.rs @@ -18,9 +18,17 @@ pub(crate) const CONFIG_TEMPLATE: &str = r#" # Human-readable ID of this particular node. id = '{{ id }}' -# Current mode of this nym-node. -# Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway) -mode = '{{ mode }}' +# Current modes of this nym-node. + +[modes] +# Specifies whether this node can operate in a mixnode mode. +mixnode = {{ modes.mixnode }} + +# Specifies whether this node can operate in an entry mode. +entry = {{ modes.entry }} + +# Specifies whether this node can operate in an exit mode. +exit = {{ modes.exit }} [host] # Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. @@ -149,195 +157,158 @@ private_diffie_hellman_key_file = '{{ wireguard.storage_paths.private_diffie_hel public_diffie_hellman_key_file = '{{ wireguard.storage_paths.public_diffie_hellman_key_file }}' -##### mixnode mode nym-node config options ##### +##### verloc config options ##### -[mixnode] - -[mixnode.verloc] +[verloc] # Socket address this node will use for binding its verloc API. # default: `0.0.0.0:1790` -bind_address = '{{ mixnode.verloc.bind_address }}' +bind_address = '{{ verloc.bind_address }}' # If applicable, custom port announced in the self-described API that other clients and nodes # will use. # Useful when the node is behind a proxy. # (default: 0 - disabled) -announce_port ={{#if mixnode.verloc.announce_port }} {{ mixnode.verloc.announce_port }} {{else}} 0 {{/if}} +announce_port ={{#if verloc.announce_port }} {{ verloc.announce_port }} {{else}} 0 {{/if}} -[mixnode.storage_paths] -# currently empty -##### entry-gateway mode nym-node config options ##### +##### gateway tasks config options ##### -[entry_gateway] +[gateway_tasks] # Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet # or if it also accepts non-paying clients -enforce_zk_nyms = {{ entry_gateway.enforce_zk_nyms }} +enforce_zk_nyms = {{ gateway_tasks.enforce_zk_nyms }} # Socket address this node will use for binding its client websocket API. # default: `0.0.0.0:9000` -bind_address = '{{ entry_gateway.bind_address }}' +bind_address = '{{ gateway_tasks.bind_address }}' # Custom announced port for listening for websocket client traffic. # If unspecified, the value from the `bind_address` will be used instead # (default: 0 - unspecified) -announce_ws_port = {{#if entry_gateway.announce_ws_port }} {{ entry_gateway.announce_ws_port }} {{else}} 0 {{/if}} +announce_ws_port = {{#if gateway_tasks.announce_ws_port }} {{ gateway_tasks.announce_ws_port }} {{else}} 0 {{/if}} # If applicable, announced port for listening for secure websocket client traffic. # (default: 0 - disabled) -announce_wss_port = {{#if entry_gateway.announce_wss_port }} {{ entry_gateway.announce_wss_port }} {{else}} 0 {{/if}} +announce_wss_port = {{#if gateway_tasks.announce_wss_port }} {{ gateway_tasks.announce_wss_port }} {{else}} 0 {{/if}} -[entry_gateway.storage_paths] +[gateway_tasks.storage_paths] # Path to sqlite database containing all persistent data: messages for offline clients, # derived shared keys, available client bandwidths and wireguard peers. -clients_storage = '{{ entry_gateway.storage_paths.clients_storage }}' +clients_storage = '{{ gateway_tasks.storage_paths.clients_storage }}' # Path to sqlite database containing all persistent stats data. -stats_storage = '{{ entry_gateway.storage_paths.stats_storage }}' +stats_storage = '{{ gateway_tasks.storage_paths.stats_storage }}' # Path to file containing cosmos account mnemonic used for zk-nym redemption. -cosmos_mnemonic = '{{ entry_gateway.storage_paths.cosmos_mnemonic }}' +cosmos_mnemonic = '{{ gateway_tasks.storage_paths.cosmos_mnemonic }}' -[entry_gateway.storage_paths.authenticator] -# Path to file containing authenticator ed25519 identity private key. -private_ed25519_identity_key_file = '{{ entry_gateway.storage_paths.authenticator.private_ed25519_identity_key_file }}' +##### service providers nym-node config options ##### -# Path to file containing authenticator ed25519 identity public key. -public_ed25519_identity_key_file = '{{ entry_gateway.storage_paths.authenticator.public_ed25519_identity_key_file }}' - -# Path to file containing authenticator x25519 diffie hellman private key. -private_x25519_diffie_hellman_key_file = '{{ entry_gateway.storage_paths.authenticator.private_x25519_diffie_hellman_key_file }}' - -# Path to file containing authenticator x25519 diffie hellman public key. -public_x25519_diffie_hellman_key_file = '{{ entry_gateway.storage_paths.authenticator.public_x25519_diffie_hellman_key_file }}' - -# Path to file containing key used for encrypting and decrypting the content of an -# acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ entry_gateway.storage_paths.authenticator.ack_key_file }}' - -# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database = '{{ entry_gateway.storage_paths.authenticator.reply_surb_database }}' - -# Normally this is a path to the file containing information about gateways used by this client, -# i.e. details such as their public keys, owner addresses or the network information. -# but in this case it just has the basic information of "we're using custom gateway". -# Due to how clients are started up, this file has to exist. -gateway_registrations = '{{ entry_gateway.storage_paths.authenticator.gateway_registrations }}' - -##### exit-gateway mode nym-node config options ##### - -[exit_gateway] +[service_providers] # specifies whether this exit node should run in 'open-proxy' mode # and thus would attempt to resolve **ANY** request it receives. -open_proxy = {{ exit_gateway.open_proxy }} +open_proxy = {{ service_providers.open_proxy }} # Specifies the custom url for an upstream source of the exit policy used by this node. -upstream_exit_policy_url = '{{ exit_gateway.upstream_exit_policy_url }}' +upstream_exit_policy_url = '{{ service_providers.upstream_exit_policy_url }}' -[exit_gateway.network_requester] +[service_providers.network_requester] # currently empty (there are some debug options one might want to configure) -[exit_gateway.ip_packet_router] +[service_providers.ip_packet_router] # currently empty (there are some debug options one might want to configure) -[exit_gateway.storage_paths] +[service_providers.authenticator] +# currently empty (there are some debug options one might want to configure) + +[service_providers.storage_paths] # Path to sqlite database containing all persistent data: messages for offline clients, # derived shared keys, available client bandwidths and wireguard peers. -clients_storage = '{{ exit_gateway.storage_paths.clients_storage }}' +clients_storage = '{{ service_providers.storage_paths.clients_storage }}' # Path to sqlite database containing all persistent stats data. -stats_storage = '{{ exit_gateway.storage_paths.stats_storage }}' +stats_storage = '{{ service_providers.storage_paths.stats_storage }}' - -[exit_gateway.storage_paths.network_requester] +[service_providers.storage_paths.network_requester] # Path to file containing network requester ed25519 identity private key. -private_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.network_requester.private_ed25519_identity_key_file }}' +private_ed25519_identity_key_file = '{{ service_providers.storage_paths.network_requester.private_ed25519_identity_key_file }}' # Path to file containing network requester ed25519 identity public key. -public_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.network_requester.public_ed25519_identity_key_file }}' +public_ed25519_identity_key_file = '{{ service_providers.storage_paths.network_requester.public_ed25519_identity_key_file }}' # Path to file containing network requester x25519 diffie hellman private key. -private_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.network_requester.private_x25519_diffie_hellman_key_file }}' +private_x25519_diffie_hellman_key_file = '{{ service_providers.storage_paths.network_requester.private_x25519_diffie_hellman_key_file }}' # Path to file containing network requester x25519 diffie hellman public key. -public_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.network_requester.public_x25519_diffie_hellman_key_file }}' +public_x25519_diffie_hellman_key_file = '{{ service_providers.storage_paths.network_requester.public_x25519_diffie_hellman_key_file }}' # Path to file containing key used for encrypting and decrypting the content of an # acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ exit_gateway.storage_paths.network_requester.ack_key_file }}' +ack_key_file = '{{ service_providers.storage_paths.network_requester.ack_key_file }}' # Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database = '{{ exit_gateway.storage_paths.network_requester.reply_surb_database }}' +reply_surb_database = '{{ service_providers.storage_paths.network_requester.reply_surb_database }}' # Normally this is a path to the file containing information about gateways used by this client, # i.e. details such as their public keys, owner addresses or the network information. # but in this case it just has the basic information of "we're using custom gateway". # Due to how clients are started up, this file has to exist. -gateway_registrations = '{{ exit_gateway.storage_paths.network_requester.gateway_registrations }}' +gateway_registrations = '{{ service_providers.storage_paths.network_requester.gateway_registrations }}' -[exit_gateway.storage_paths.ip_packet_router] +[service_providers.storage_paths.ip_packet_router] # Path to file containing ip packet router ed25519 identity private key. -private_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.private_ed25519_identity_key_file }}' +private_ed25519_identity_key_file = '{{ service_providers.storage_paths.ip_packet_router.private_ed25519_identity_key_file }}' # Path to file containing ip packet router ed25519 identity public key. -public_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.public_ed25519_identity_key_file }}' +public_ed25519_identity_key_file = '{{ service_providers.storage_paths.ip_packet_router.public_ed25519_identity_key_file }}' # Path to file containing ip packet router x25519 diffie hellman private key. -private_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.private_x25519_diffie_hellman_key_file }}' +private_x25519_diffie_hellman_key_file = '{{ service_providers.storage_paths.ip_packet_router.private_x25519_diffie_hellman_key_file }}' # Path to file containing ip packet router x25519 diffie hellman public key. -public_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.public_x25519_diffie_hellman_key_file }}' +public_x25519_diffie_hellman_key_file = '{{ service_providers.storage_paths.ip_packet_router.public_x25519_diffie_hellman_key_file }}' # Path to file containing key used for encrypting and decrypting the content of an # acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.ack_key_file }}' +ack_key_file = '{{ service_providers.storage_paths.ip_packet_router.ack_key_file }}' # Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database = '{{ exit_gateway.storage_paths.ip_packet_router.reply_surb_database }}' +reply_surb_database = '{{ service_providers.storage_paths.ip_packet_router.reply_surb_database }}' # Normally this is a path to the file containing information about gateways used by this client, # i.e. details such as their public keys, owner addresses or the network information. # but in this case it just has the basic information of "we're using custom gateway". # Due to how clients are started up, this file has to exist. -gateway_registrations = '{{ exit_gateway.storage_paths.ip_packet_router.gateway_registrations }}' +gateway_registrations = '{{ service_providers.storage_paths.ip_packet_router.gateway_registrations }}' -[exit_gateway.storage_paths.authenticator] +[service_providers.storage_paths.authenticator] # Path to file containing authenticator ed25519 identity private key. -private_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.authenticator.private_ed25519_identity_key_file }}' +private_ed25519_identity_key_file = '{{ service_providers.storage_paths.authenticator.private_ed25519_identity_key_file }}' # Path to file containing authenticator ed25519 identity public key. -public_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.authenticator.public_ed25519_identity_key_file }}' +public_ed25519_identity_key_file = '{{ service_providers.storage_paths.authenticator.public_ed25519_identity_key_file }}' # Path to file containing authenticator x25519 diffie hellman private key. -private_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.authenticator.private_x25519_diffie_hellman_key_file }}' +private_x25519_diffie_hellman_key_file = '{{ service_providers.storage_paths.authenticator.private_x25519_diffie_hellman_key_file }}' # Path to file containing authenticator x25519 diffie hellman public key. -public_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.authenticator.public_x25519_diffie_hellman_key_file }}' +public_x25519_diffie_hellman_key_file = '{{ service_providers.storage_paths.authenticator.public_x25519_diffie_hellman_key_file }}' # Path to file containing key used for encrypting and decrypting the content of an # acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ exit_gateway.storage_paths.authenticator.ack_key_file }}' +ack_key_file = '{{ service_providers.storage_paths.authenticator.ack_key_file }}' # Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database = '{{ exit_gateway.storage_paths.authenticator.reply_surb_database }}' +reply_surb_database = '{{ service_providers.storage_paths.authenticator.reply_surb_database }}' # Normally this is a path to the file containing information about gateways used by this client, # i.e. details such as their public keys, owner addresses or the network information. # but in this case it just has the basic information of "we're using custom gateway". # Due to how clients are started up, this file has to exist. -gateway_registrations = '{{ exit_gateway.storage_paths.authenticator.gateway_registrations }}' +gateway_registrations = '{{ service_providers.storage_paths.authenticator.gateway_registrations }}' -[authenticator] -# currently empty (there are some debug options one might want to configure) - -##### logging configuration options ##### - -[logging] - -# TODO - "#; diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs index c7596b69a6..26cc748f37 100644 --- a/nym-node/src/config/upgrade_helpers.rs +++ b/nym-node/src/config/upgrade_helpers.rs @@ -13,7 +13,8 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { let cfg = try_upgrade_config_v2(path, cfg).await.ok(); let cfg = try_upgrade_config_v3(path, cfg).await.ok(); let cfg = try_upgrade_config_v4(path, cfg).await.ok(); - match try_upgrade_config_v5(path, cfg).await { + let cfg = try_upgrade_config_v5(path, cfg).await.ok(); + match try_upgrade_config_v6(path, cfg).await { Ok(cfg) => cfg.save(), Err(e) => { tracing::error!("Failed to finish upgrade - {e}"); diff --git a/nym-node/src/env.rs b/nym-node/src/env.rs index b4662fa69e..e471cf91bd 100644 --- a/nym-node/src/env.rs +++ b/nym-node/src/env.rs @@ -17,6 +17,7 @@ pub mod vars { pub const NYMNODE_BONDING_INFORMATION_OUTPUT_ARG: &str = "NYMNODE_BONDING_INFORMATION_OUTPUT"; pub const NYMNODE_MODE_ARG: &str = "NYMNODE_MODE"; + pub const NYMNODE_MODES_ARG: &str = "NYMNODE_MODES"; pub const NYMNODE_ACCEPT_OPERATOR_TERMS: &str = "NYMNODE_ACCEPT_OPERATOR_TERMS"; @@ -46,10 +47,13 @@ pub mod vars { pub const NYMNODE_WG_ANNOUNCED_PORT_ARG: &str = "NYMNODE_WG_ANNOUNCED_PORT"; pub const NYMNODE_WG_PRIVATE_NETWORK_PREFIX_ARG: &str = "NYMNODE_WG_PRIVATE_NETWORK_PREFIX"; - // mixnode: + // verloc: pub const NYMNODE_VERLOC_BIND_ADDRESS_ARG: &str = "NYMNODE_VERLOC_BIND_ADDRESS"; pub const NYMNODE_VERLOC_ANNOUNCE_PORT_ARG: &str = "NYMNODE_VERLOC_ANNOUNCE_PORT"; + // metrics + pub const NYMNODE_ENABLE_CONSOLE_LOGGING: &str = "NYMNODE_ENABLE_CONSOLE_LOGGING"; + // entry gateway: pub const NYMNODE_ENTRY_BIND_ADDRESS_ARG: &str = "NYMNODE_ENTRY_BIND_ADDRESS"; pub const NYMNODE_ENTRY_ANNOUNCE_WS_PORT_ARG: &str = "NYMNODE_ENTRY_ANNOUNCE_WS_PORT"; diff --git a/nym-node/src/error.rs b/nym-node/src/error.rs index afbed84a0e..4794b12961 100644 --- a/nym-node/src/error.rs +++ b/nym-node/src/error.rs @@ -1,16 +1,16 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::helpers::UnsupportedGatewayAddresses; +use crate::node::http::error::NymNodeHttpError; use crate::wireguard::error::WireguardError; use nym_ip_packet_router::error::ClientCoreError; -use nym_node_http_api::NymNodeHttpError; use std::io; use std::net::IpAddr; use std::path::PathBuf; use thiserror::Error; #[derive(Debug, Error)] +#[allow(clippy::enum_variant_names)] pub enum KeyIOFailure { #[error("failed to load {keys} keys from {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)] KeyPairLoadFailure { @@ -56,9 +56,6 @@ pub enum NymNodeError { #[error("could not derive path to data directory of this nym node")] DataDirDerivationFailure, - #[error("could not derive path to config directory of this nym node")] - ConfigDirDerivationFailure, - #[error(transparent)] HttpFailure(#[from] NymNodeHttpError), @@ -116,6 +113,9 @@ pub enum NymNodeError { source: WireguardError, }, + #[error("wireguard data is no longer available - has it been reused?")] + WireguardDataUnavailable, + #[deprecated] #[error(transparent)] KeyRecoveryError { @@ -129,9 +129,6 @@ pub enum NymNodeError { #[error("could not initialise nym-node as '--{name}' has not been specified which is required for a first time setup. (config section: {section})")] MissingInitArg { section: String, name: String }, - #[error("failed to migrate {node_type}: {message}")] - MigrationFailure { node_type: String, message: String }, - #[error("there was an issue with wireguard IP network: {source}")] IpNetworkError { #[from] @@ -139,13 +136,16 @@ pub enum NymNodeError { }, #[error(transparent)] - MixnodeFailure(#[from] MixnodeError), + GatewayFailure(#[from] nym_gateway::GatewayError), + + #[error(transparent)] + GatewayTasksStartupFailure(Box), #[error(transparent)] EntryGatewayFailure(#[from] EntryGatewayError), #[error(transparent)] - ExitGatewayFailure(#[from] ExitGatewayError), + ServiceProvidersFailure(#[from] ServiceProvidersError), // TODO: more granular errors #[error(transparent)] @@ -155,15 +155,6 @@ pub enum NymNodeError { FailedUpgrade, } -#[derive(Debug, Error)] -pub enum MixnodeError { - #[error("currently it's not supported to have different ip addresses for verloc and mixnet ({verloc_bind_ip} and {mix_bind_ip} were used)")] - UnsupportedAddresses { - verloc_bind_ip: IpAddr, - mix_bind_ip: IpAddr, - }, -} - #[derive(Debug, Error)] pub enum EntryGatewayError { #[error(transparent)] @@ -193,25 +184,16 @@ pub enum EntryGatewayError { source: bip39::Error, }, - #[error(transparent)] - UnsupportedAddresses(#[from] UnsupportedGatewayAddresses), - #[error("entry gateway failure: {0}")] External(#[from] nym_gateway::GatewayError), } #[derive(Debug, Error)] -pub enum ExitGatewayError { +pub enum ServiceProvidersError { #[error(transparent)] KeyFailure(#[from] KeyIOFailure), - #[error(transparent)] - UnsupportedAddresses(#[from] UnsupportedGatewayAddresses), - // TODO: more granular errors #[error(transparent)] ExternalClientCore(#[from] ClientCoreError), - - #[error("exit gateway failure: {0}")] - External(#[from] nym_gateway::GatewayError), } diff --git a/nym-node/src/lib.rs b/nym-node/src/lib.rs deleted file mode 100644 index 320d22559d..0000000000 --- a/nym-node/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2023-2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -#![warn(clippy::expect_used)] -#![warn(clippy::unwrap_used)] - -pub mod config; -pub mod error; -pub mod wireguard; - -pub use nym_node_http_api as http; diff --git a/nym-node/src/main.rs b/nym-node/src/main.rs index 8d65de2aa6..9d66d778d2 100644 --- a/nym-node/src/main.rs +++ b/nym-node/src/main.rs @@ -10,9 +10,12 @@ use nym_bin_common::logging::maybe_print_banner; use nym_config::defaults::setup_env; mod cli; +pub(crate) mod config; mod env; +pub(crate) mod error; mod logging; pub(crate) mod node; +pub(crate) mod wireguard; #[tokio::main] async fn main() -> anyhow::Result<()> { diff --git a/nym-node/src/node/bonding_information.rs b/nym-node/src/node/bonding_information.rs index 5132d37762..94d151dd03 100644 --- a/nym-node/src/node/bonding_information.rs +++ b/nym-node/src/node/bonding_information.rs @@ -1,170 +1,59 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::helpers::{ - bonding_version, load_ed25519_identity_public_key, load_x25519_sphinx_public_key, -}; -use nym_node::config::{Config, NodeMode}; -use nym_node::error::NymNodeError; +use crate::config::Config; +use crate::error::NymNodeError; +use crate::node::helpers::load_ed25519_identity_public_key; +use nym_crypto::asymmetric::ed25519; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "node_type")] -pub enum BondingInformationV1 { - Mixnode(MixnodeBondingInformation), - Gateway(GatewayBondingInformation), +#[derive(Debug, Deserialize, Serialize)] +pub struct BondingInformation { + host: String, + identity_key: ed25519::PublicKey, } -impl Display for BondingInformationV1 { +impl Display for BondingInformation { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - BondingInformationV1::Mixnode(m) => m.fmt(f), - BondingInformationV1::Gateway(g) => g.fmt(f), - } - } -} - -// TODO: work in progress, I'm not 100% sure yet what will be needed -// #[derive(Serialize, Deserialize, Debug)] -// pub struct BondingInformationV2 { -// pub(crate) ed25519_identity_key: ed25519::PublicKey, -// pub(crate) x25519_sphinx_key: x25519::PublicKey, -// } -// -// impl Display for BondingInformationV2 { -// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { -// writeln!( -// f, -// "ed25519 identity key: {}", -// self.ed25519_identity_key.to_base58_string() -// )?; -// write!( -// f, -// "x25519 sphinx key: {}", -// self.x25519_sphinx_key.to_base58_string() -// ) -// } -// } - -#[derive(Serialize, Deserialize, Debug)] -pub struct MixnodeBondingInformation { - pub(crate) version: String, - pub(crate) host: String, - pub(crate) identity_key: String, - pub(crate) sphinx_key: String, -} - -impl MixnodeBondingInformation { - pub fn from_data( - ed25519_identity_key: String, - x25519_sphinx_key: String, - ) -> MixnodeBondingInformation { - MixnodeBondingInformation { - version: bonding_version(), - host: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(), - identity_key: ed25519_identity_key, - sphinx_key: x25519_sphinx_key, - } - } -} - -impl Display for MixnodeBondingInformation { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Node type: Mixnode")?; writeln!(f, "Identity Key: {}", self.identity_key)?; - writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; writeln!(f, "Host: {}", self.host)?; - writeln!(f, "Version: {}", self.version)?; + writeln!(f, "Custom HTTP Port: you might want to set it if your node won't be accessible on any of the ports: 80/443/8080")?; + Ok(()) } } -#[derive(Serialize, Deserialize, Debug)] -pub struct GatewayBondingInformation { - pub(crate) version: String, - pub(crate) host: String, - pub(crate) location: String, - pub(crate) identity_key: String, - pub(crate) sphinx_key: String, -} +impl BondingInformation { + pub fn from_data(config: &Config, ed25519_identity_key: ed25519::PublicKey) -> Self { + let host = match config.host.hostname { + Some(ref host) => host.clone(), + None => match config.host.public_ips.first() { + Some(first_ip) => { + if !first_ip.is_loopback() + && !first_ip.is_multicast() + && !first_ip.is_unspecified() + { + first_ip.to_string() + } else { + "NO KNOWN VALID HOSTNAMES - YOU NEED TO FILL IT MANUALLY".to_string() + } + } + None => "NO KNOWN VALID HOSTNAMES - YOU NEED TO FILL IT MANUALLY".to_string(), + }, + }; -impl GatewayBondingInformation { - pub fn from_data( - ed25519_identity_key: String, - x25519_sphinx_key: String, - ) -> GatewayBondingInformation { - GatewayBondingInformation { - version: bonding_version(), - host: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(), - location: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(), + BondingInformation { + host, identity_key: ed25519_identity_key, - sphinx_key: x25519_sphinx_key, - } - } -} - -impl Display for GatewayBondingInformation { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Node type: Gateway")?; - writeln!(f, "Identity Key: {}", self.identity_key)?; - writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; - writeln!(f, "Location: {}", self.location)?; - writeln!(f, "Host: {}", self.host)?; - writeln!(f, "Version: {}", self.version)?; - Ok(()) - } -} - -impl BondingInformationV1 { - pub fn from_data( - mode: NodeMode, - ed25519_identity_key: String, - x25519_sphinx_key: String, - ) -> BondingInformationV1 { - match mode { - NodeMode::Mixnode => BondingInformationV1::Mixnode( - MixnodeBondingInformation::from_data(ed25519_identity_key, x25519_sphinx_key), - ), - NodeMode::EntryGateway | NodeMode::ExitGateway => BondingInformationV1::Gateway( - GatewayBondingInformation::from_data(ed25519_identity_key, x25519_sphinx_key), - ), } } - fn ed25519_identity_key(&self) -> String { - match self { - BondingInformationV1::Mixnode(m) => m.identity_key.clone(), - BondingInformationV1::Gateway(g) => g.identity_key.clone(), - } - } - - fn x25519_sphinx_key(&self) -> String { - match self { - BondingInformationV1::Mixnode(m) => m.sphinx_key.clone(), - BondingInformationV1::Gateway(g) => g.sphinx_key.clone(), - } - } - - pub fn try_load(config: &Config) -> Result { + pub fn try_load(config: &Config) -> Result { let ed25519_identity_key = load_ed25519_identity_public_key( &config.storage_paths.keys.public_ed25519_identity_key_file, )?; - let x25519_sphinx_key = load_x25519_sphinx_public_key( - &config.storage_paths.keys.public_x25519_sphinx_key_file, - )?; - let mode = config.mode; - Ok(Self::from_data( - mode, - ed25519_identity_key.to_base58_string(), - x25519_sphinx_key.to_base58_string(), - )) - } - - pub fn with_mode(self, mode: NodeMode) -> Self { - let identity_key = self.ed25519_identity_key(); - let sphinx_key = self.x25519_sphinx_key(); - Self::from_data(mode, identity_key, sphinx_key) + Ok(Self::from_data(config, ed25519_identity_key)) } } diff --git a/nym-node/src/node/description.rs b/nym-node/src/node/description.rs index 0f5b713727..e28efab75d 100644 --- a/nym-node/src/node/description.rs +++ b/nym-node/src/node/description.rs @@ -1,8 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_node::error::NymNodeError; -use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription; +use crate::error::NymNodeError; +use nym_node_requests::api::v1::node::models::NodeDescription; use std::fs; use std::fs::create_dir_all; use std::path::Path; diff --git a/nym-node/src/node/helpers.rs b/nym-node/src/node/helpers.rs index 3055578757..aa9e3525e4 100644 --- a/nym-node/src/node/helpers.rs +++ b/nym-node/src/node/helpers.rs @@ -1,33 +1,19 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::config::NodeModes; +use crate::error::{KeyIOFailure, NymNodeError}; use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_node::config::NodeMode; -use nym_node::error::{KeyIOFailure, NymNodeError}; -use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription; +use nym_node_requests::api::v1::node::models::NodeDescription; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use nym_pemstore::KeyPairPath; -use semver::{BuildMetadata, Version}; use serde::Serialize; use std::fmt::{Display, Formatter}; use std::path::Path; -#[allow(clippy::unwrap_used)] -pub fn bonding_version() -> String { - // SAFETY: - // the value has been put there by cargo - let raw = env!("CARGO_PKG_VERSION"); - let mut semver: Version = raw.parse().unwrap(); - - // if it's not empty, then we messed up our own versioning - assert!(semver.build.is_empty()); - semver.build = BuildMetadata::new("nymnode").unwrap(); - semver.to_string() -} - #[derive(Debug, Serialize)] pub(crate) struct DisplayDetails { - pub(crate) current_mode: NodeMode, + pub(crate) current_modes: NodeModes, pub(crate) description: NodeDescription, @@ -43,7 +29,7 @@ pub(crate) struct DisplayDetails { impl Display for DisplayDetails { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - writeln!(f, "current mode: {}", self.current_mode)?; + writeln!(f, "current mode: {:#?}", self.current_modes)?; writeln!(f, "moniker: '{}'", self.description.moniker)?; writeln!(f, "website: '{}'", self.description.website)?; writeln!( @@ -152,12 +138,6 @@ pub(crate) fn load_x25519_wireguard_keypair( Ok(load_keypair(paths, "x25519-wireguard")?) } -pub(crate) fn load_x25519_sphinx_public_key>( - path: P, -) -> Result { - Ok(load_key(path, "x25519-sphinx-public-key")?) -} - pub(crate) fn store_ed25519_identity_keypair( keys: &ed25519::KeyPair, paths: KeyPairPath, diff --git a/nym-node/nym-node-http-api/src/error.rs b/nym-node/src/node/http/error.rs similarity index 100% rename from nym-node/nym-node-http-api/src/error.rs rename to nym-node/src/node/http/error.rs diff --git a/nym-node/src/node/http/helpers/mod.rs b/nym-node/src/node/http/helpers/mod.rs new file mode 100644 index 0000000000..a819bdd062 --- /dev/null +++ b/nym-node/src/node/http/helpers/mod.rs @@ -0,0 +1,38 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::Config; +use crate::error::NymNodeError; +use crate::node::http::api::api_requests; +use crate::node::http::error::NymNodeHttpError; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_node_requests::api::SignedHostInformation; + +pub mod system_info; + +pub(crate) fn sign_host_details( + config: &Config, + x22519_sphinx: &x25519::PublicKey, + x25519_noise: &x25519::PublicKey, + ed22519_identity: &ed25519::KeyPair, +) -> Result { + let x25519_noise = if config.mixnet.debug.unsafe_disable_noise { + None + } else { + Some(*x25519_noise) + }; + + let host_info = api_requests::v1::node::models::HostInformation { + ip_address: config.host.public_ips.clone(), + hostname: config.host.hostname.clone(), + keys: api_requests::v1::node::models::HostKeys { + ed25519_identity: *ed22519_identity.public_key(), + x25519_sphinx: *x22519_sphinx, + x25519_noise, + }, + }; + + let signed_info = SignedHostInformation::new(host_info, ed22519_identity.private_key()) + .map_err(NymNodeHttpError::from)?; + Ok(signed_info) +} diff --git a/nym-node/src/node/http/system_info.rs b/nym-node/src/node/http/helpers/system_info.rs similarity index 94% rename from nym-node/src/node/http/system_info.rs rename to nym-node/src/node/http/helpers/system_info.rs index 128ce37229..980c305ba9 100644 --- a/nym-node/src/node/http/system_info.rs +++ b/nym-node/src/node/http/helpers/system_info.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use cupid::TopologyType; -use nym_node_http_api::api::api_requests::v1::node::models::{ - Cpu, CryptoHardware, Hardware, HostSystem, -}; +use nym_node_requests::api::v1::node::models::{Cpu, CryptoHardware, Hardware, HostSystem}; use sysinfo::System; fn crypto_hardware() -> Option { diff --git a/nym-node/nym-node-http-api/src/middleware/mod.rs b/nym-node/src/node/http/middleware/mod.rs similarity index 85% rename from nym-node/nym-node-http-api/src/middleware/mod.rs rename to nym-node/src/node/http/middleware/mod.rs index 54a67e0147..af282c5deb 100644 --- a/nym-node/nym-node-http-api/src/middleware/mod.rs +++ b/nym-node/src/node/http/middleware/mod.rs @@ -1,4 +1,2 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only - -pub mod logging; diff --git a/nym-node/src/node/http/mod.rs b/nym-node/src/node/http/mod.rs index defdd9628e..db80ccd870 100644 --- a/nym-node/src/node/http/mod.rs +++ b/nym-node/src/node/http/mod.rs @@ -1,40 +1,66 @@ -// Copyright 2024 - Nym Technologies SA +// Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_node::config::Config; -use nym_node::error::NymNodeError; -use nym_node_http_api::api::api_requests; -use nym_node_http_api::api::api_requests::SignedHostInformation; -use nym_node_http_api::NymNodeHttpError; +use axum::extract::connect_info::IntoMakeServiceWithConnectInfo; +use axum::extract::ConnectInfo; +use axum::middleware::AddExtension; +use axum::serve::Serve; +use axum::Router; +use nym_task::TaskClient; +use std::net::SocketAddr; +use tracing::{debug, error}; -pub(crate) mod system_info; +pub use router::{api, HttpServerConfig, NymNodeRouter}; -pub(crate) fn sign_host_details( - config: &Config, - x22519_sphinx: &x25519::PublicKey, - x25519_noise: &x25519::PublicKey, - ed22519_identity: &ed25519::KeyPair, -) -> Result { - let x25519_noise = if config.mixnet.debug.unsafe_disable_noise { - None - } else { - Some(*x25519_noise) - }; +pub mod error; +pub mod helpers; +pub mod middleware; +pub mod router; +pub mod state; - let host_info = api_requests::v1::node::models::HostInformation { - ip_address: config.host.public_ips.clone(), - hostname: config.host.hostname.clone(), - keys: api_requests::v1::node::models::HostKeys { - ed25519_identity: *ed22519_identity.public_key(), - x25519_sphinx: *x22519_sphinx, - x25519_noise, - }, - }; +type InnerService = IntoMakeServiceWithConnectInfo; +type ConnectInfoExt = AddExtension>; +pub type ServeService = Serve; - let signed_info = SignedHostInformation::new(host_info, ed22519_identity.private_key()) - .map_err(NymNodeHttpError::from)?; - Ok(signed_info) +pub struct NymNodeHttpServer { + task_client: Option, + inner: ServeService, } -// pub(crate) fn run_http_api(config: &Config, task_client: TaskClient) +impl NymNodeHttpServer { + pub(crate) fn new(inner: ServeService) -> Self { + NymNodeHttpServer { + task_client: None, + inner, + } + } + + #[must_use] + pub fn with_task_client(mut self, task_client: TaskClient) -> Self { + self.task_client = Some(task_client); + self + } + + async fn run_server_forever(server: ServeService) { + if let Err(err) = server.await { + error!("the HTTP server has terminated with the error: {err}"); + } else { + error!("the HTTP server has terminated with producing any errors"); + } + } + + pub async fn run(self) { + if let Some(mut task_client) = self.task_client { + tokio::select! { + _ = task_client.recv_with_delay() => { + debug!("NymNodeHTTPServer: Received shutdown"); + } + _ = Self::run_server_forever(self.inner) => { } + } + } else { + Self::run_server_forever(self.inner).await + } + + debug!("NymNodeHTTPServer: Exiting"); + } +} diff --git a/nym-node/nym-node-http-api/src/router/api/mod.rs b/nym-node/src/node/http/router/api/mod.rs similarity index 79% rename from nym-node/nym-node-http-api/src/router/api/mod.rs rename to nym-node/src/node/http/router/api/mod.rs index 25498d9a55..ea3c5e4c05 100644 --- a/nym-node/nym-node-http-api/src/router/api/mod.rs +++ b/nym-node/src/node/http/router/api/mod.rs @@ -1,14 +1,12 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::state::AppState; +use crate::node::http::state::AppState; use axum::Router; use nym_node_requests::routes; pub mod v1; -pub(crate) use nym_http_api_common::{FormattedResponse, Output, OutputParams}; - pub use nym_node_requests::api as api_requests; #[derive(Debug, Clone)] diff --git a/nym-node/nym-node-http-api/src/router/api/v1/authenticator/mod.rs b/nym-node/src/node/http/router/api/v1/authenticator/mod.rs similarity index 100% rename from nym-node/nym-node-http-api/src/router/api/v1/authenticator/mod.rs rename to nym-node/src/node/http/router/api/v1/authenticator/mod.rs diff --git a/nym-node/nym-node-http-api/src/router/api/v1/authenticator/root.rs b/nym-node/src/node/http/router/api/v1/authenticator/root.rs similarity index 94% rename from nym-node/nym-node-http-api/src/router/api/v1/authenticator/root.rs rename to nym-node/src/node/http/router/api/v1/authenticator/root.rs index 9fb2104206..054d224779 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/authenticator/root.rs +++ b/nym-node/src/node/http/router/api/v1/authenticator/root.rs @@ -1,9 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::authenticator::models::Authenticator; /// Returns root authenticator information diff --git a/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/mod.rs b/nym-node/src/node/http/router/api/v1/gateway/client_interfaces/mod.rs similarity index 98% rename from nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/mod.rs rename to nym-node/src/node/http/router/api/v1/gateway/client_interfaces/mod.rs index 809cc1fc9e..96215f7192 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/mod.rs +++ b/nym-node/src/node/http/router/api/v1/gateway/client_interfaces/mod.rs @@ -1,11 +1,11 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; use axum::routing::get; use axum::Router; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::gateway::models::{ClientInterfaces, WebSockets, Wireguard}; use nym_node_requests::routes::api::v1::gateway::client_interfaces; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/gateway/mod.rs b/nym-node/src/node/http/router/api/v1/gateway/mod.rs similarity index 100% rename from nym-node/nym-node-http-api/src/router/api/v1/gateway/mod.rs rename to nym-node/src/node/http/router/api/v1/gateway/mod.rs diff --git a/nym-node/nym-node-http-api/src/router/api/v1/gateway/root.rs b/nym-node/src/node/http/router/api/v1/gateway/root.rs similarity index 94% rename from nym-node/nym-node-http-api/src/router/api/v1/gateway/root.rs rename to nym-node/src/node/http/router/api/v1/gateway/root.rs index cb6fc6cbfd..4738acb30e 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/gateway/root.rs +++ b/nym-node/src/node/http/router/api/v1/gateway/root.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::gateway::models::Gateway; /// Returns root gateway information diff --git a/nym-node/nym-node-http-api/src/router/api/v1/health.rs b/nym-node/src/node/http/router/api/v1/health.rs similarity index 90% rename from nym-node/nym-node-http-api/src/router/api/v1/health.rs rename to nym-node/src/node/http/router/api/v1/health.rs index 4beb5f6af5..0972ea6339 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/health.rs +++ b/nym-node/src/node/http/router/api/v1/health.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::{FormattedResponse, OutputParams}; -use crate::state::AppState; +use crate::node::http::state::AppState; use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::health::models::NodeHealth; /// Returns health status of this node. diff --git a/nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/mod.rs b/nym-node/src/node/http/router/api/v1/ip_packet_router/mod.rs similarity index 100% rename from nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/mod.rs rename to nym-node/src/node/http/router/api/v1/ip_packet_router/mod.rs diff --git a/nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/root.rs b/nym-node/src/node/http/router/api/v1/ip_packet_router/root.rs similarity index 94% rename from nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/root.rs rename to nym-node/src/node/http/router/api/v1/ip_packet_router/root.rs index 41fc8e536c..18c503bd5c 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/root.rs +++ b/nym-node/src/node/http/router/api/v1/ip_packet_router/root.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; /// Returns root network requester information diff --git a/nym-node/src/node/http/router/api/v1/metrics/legacy_mixing.rs b/nym-node/src/node/http/router/api/v1/metrics/legacy_mixing.rs new file mode 100644 index 0000000000..23cfd0607d --- /dev/null +++ b/nym-node/src/node/http/router/api/v1/metrics/legacy_mixing.rs @@ -0,0 +1,47 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::http::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_metrics::NymNodeMetrics; +use nym_node_requests::api::v1::metrics::models::LegacyMixingStats; + +/// If applicable, returns mixing statistics information of this node. +/// This information is **PURELY** self-reported and in no way validated. +#[utoipa::path( + get, + path = "/mixing", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = LegacyMixingStats), + ("application/yaml" = LegacyMixingStats) + )) + ), + params(OutputParams), +)] +#[deprecated] +pub(crate) async fn legacy_mixing_stats( + Query(output): Query, + State(metrics_state): State, +) -> LegacyMixingStatsResponse { + let output = output.output.unwrap_or_default(); + output.to_response(build_legacy_response(&metrics_state.metrics)) +} + +fn build_legacy_response(metrics: &NymNodeMetrics) -> LegacyMixingStats { + LegacyMixingStats { + update_time: metrics.mixnet.legacy.last_update(), + previous_update_time: metrics.mixnet.legacy.previous_update(), + received_since_startup: metrics.mixnet.ingress.forward_hop_packets_received() as u64, + sent_since_startup: metrics.mixnet.egress.forward_hop_packets_sent() as u64, + dropped_since_startup: metrics.mixnet.egress.forward_hop_packets_dropped() as u64, + received_since_last_update: metrics.mixnet.legacy.received_since_last_update() as u64, + sent_since_last_update: metrics.mixnet.legacy.sent_since_last_update() as u64, + dropped_since_last_update: metrics.mixnet.legacy.dropped_since_last_update() as u64, + } +} + +pub type LegacyMixingStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs b/nym-node/src/node/http/router/api/v1/metrics/mod.rs similarity index 53% rename from nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs rename to nym-node/src/node/http/router/api/v1/metrics/mod.rs index cabf4aedf5..201dbc7a26 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs +++ b/nym-node/src/node/http/router/api/v1/metrics/mod.rs @@ -1,17 +1,18 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::v1::metrics::mixing::mixing_stats; -use crate::api::v1::metrics::prometheus::prometheus_metrics; -use crate::api::v1::metrics::sessions::sessions_stats; -use crate::api::v1::metrics::verloc::verloc_stats; -use crate::state::metrics::MetricsAppState; +use crate::node::http::api::v1::metrics::packets_stats::packets_stats; +use crate::node::http::api::v1::metrics::prometheus::prometheus_metrics; +use crate::node::http::api::v1::metrics::sessions::sessions_stats; +use crate::node::http::api::v1::metrics::verloc::verloc_stats; +use crate::node::http::state::metrics::MetricsAppState; use axum::extract::FromRef; use axum::routing::get; use axum::Router; use nym_node_requests::routes::api::v1::metrics; -pub mod mixing; +pub mod legacy_mixing; +pub mod packets_stats; pub mod prometheus; pub mod sessions; pub mod verloc; @@ -21,13 +22,18 @@ pub struct Config { // } +#[allow(deprecated)] pub(super) fn routes(_config: Config) -> Router where S: Send + Sync + 'static + Clone, MetricsAppState: FromRef, { Router::new() - .route(metrics::MIXING, get(mixing_stats)) + .route( + metrics::LEGACY_MIXING, + get(legacy_mixing::legacy_mixing_stats), + ) + .route(metrics::PACKETS_STATS, get(packets_stats)) .route(metrics::SESSIONS, get(sessions_stats)) .route(metrics::VERLOC, get(verloc_stats)) .route(metrics::PROMETHEUS, get(prometheus_metrics)) diff --git a/nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs b/nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs new file mode 100644 index 0000000000..d1a8e27f55 --- /dev/null +++ b/nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs @@ -0,0 +1,53 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::http::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_metrics::NymNodeMetrics; +use nym_node_requests::api::v1::metrics::models::packets::{ + EgressMixingStats, IngressMixingStats, PacketsStats, +}; + +/// If applicable, returns packets statistics information of this node. +/// This information is **PURELY** self-reported and in no way validated. +#[utoipa::path( + get, + path = "/packets-stats", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = PacketsStats), + ("application/yaml" = PacketsStats) + )) + ), + params(OutputParams), +)] +pub(crate) async fn packets_stats( + Query(output): Query, + State(metrics_state): State, +) -> PacketsStatsResponse { + let output = output.output.unwrap_or_default(); + output.to_response(build_response(&metrics_state.metrics)) +} + +fn build_response(metrics: &NymNodeMetrics) -> PacketsStats { + PacketsStats { + ingress_mixing: IngressMixingStats { + forward_hop_packets_received: metrics.mixnet.ingress.forward_hop_packets_received(), + final_hop_packets_received: metrics.mixnet.ingress.final_hop_packets_received(), + malformed_packets_received: metrics.mixnet.ingress.malformed_packets_received(), + excessive_delay_packets: metrics.mixnet.ingress.excessive_delay_packets(), + forward_hop_packets_dropped: metrics.mixnet.ingress.forward_hop_packets_dropped(), + final_hop_packets_dropped: metrics.mixnet.ingress.final_hop_packets_dropped(), + }, + egress_mixing: EgressMixingStats { + forward_hop_packets_sent: metrics.mixnet.egress.forward_hop_packets_sent(), + forward_hop_packets_dropped: metrics.mixnet.egress.forward_hop_packets_dropped(), + ack_packets_sent: metrics.mixnet.egress.ack_packets_sent(), + }, + } +} + +pub type PacketsStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs b/nym-node/src/node/http/router/api/v1/metrics/prometheus.rs similarity index 96% rename from nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs rename to nym-node/src/node/http/router/api/v1/metrics/prometheus.rs index a404cb9871..197cc5a1c8 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs +++ b/nym-node/src/node/http/router/api/v1/metrics/prometheus.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::state::metrics::MetricsAppState; +use crate::node::http::state::metrics::MetricsAppState; use axum::extract::State; use axum::http::StatusCode; use axum_extra::TypedHeader; diff --git a/nym-node/src/node/http/router/api/v1/metrics/sessions.rs b/nym-node/src/node/http/router/api/v1/metrics/sessions.rs new file mode 100644 index 0000000000..0b76e83205 --- /dev/null +++ b/nym-node/src/node/http/router/api/v1/metrics/sessions.rs @@ -0,0 +1,54 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::http::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_metrics::NymNodeMetrics; +use nym_node_requests::api::v1::metrics::models::{Session, SessionStats}; +use time::macros::time; + +/// If applicable, returns sessions statistics information of this node. +/// This information is **PURELY** self-reported and in no way validated. +#[utoipa::path( + get, + path = "/sessions", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = SessionStats), + ("application/yaml" = SessionStats) + )) + ), + params(OutputParams), +)] +pub(crate) async fn sessions_stats( + Query(output): Query, + State(metrics_state): State, +) -> SessionStatsResponse { + let output = output.output.unwrap_or_default(); + output.to_response(build_response(&metrics_state.metrics).await) +} + +async fn build_response(metrics: &NymNodeMetrics) -> SessionStats { + let guard = metrics.entry.client_sessions().await; + let sessions = guard + .finished_sessions + .iter() + .map(|finished| Session { + duration_ms: finished.duration.as_millis() as u64, + typ: finished.typ.to_string(), + }) + .collect(); + SessionStats { + update_time: guard.update_time.with_time(time!(0:00)).assume_utc(), + unique_active_users: guard.unique_users.len() as u32, + unique_active_users_hashes: guard.unique_users.clone(), + sessions, + sessions_started: guard.sessions_started, + sessions_finished: guard.finished_sessions.len() as u32, + } +} + +pub type SessionStatsResponse = FormattedResponse; diff --git a/nym-node/src/node/http/router/api/v1/metrics/verloc.rs b/nym-node/src/node/http/router/api/v1/metrics/verloc.rs new file mode 100644 index 0000000000..1a15986373 --- /dev/null +++ b/nym-node/src/node/http/router/api/v1/metrics/verloc.rs @@ -0,0 +1,73 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::http::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_requests::api::v1::metrics::models::{ + VerlocMeasurement, VerlocNodeResult, VerlocResult, VerlocResultData, VerlocStats, +}; +use nym_verloc::measurements::SharedVerlocStats; + +/// If applicable, returns verloc statistics information of this node. +#[utoipa::path( + get, + path = "/verloc", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = VerlocStats), + ("application/yaml" = VerlocStats) + )) + ), + params(OutputParams), +)] +pub(crate) async fn verloc_stats( + Query(output): Query, + State(metrics_state): State, +) -> VerlocStatsResponse { + let output = output.output.unwrap_or_default(); + output.to_response(build_response(&metrics_state.verloc).await) +} + +async fn build_response(verloc_stats: &SharedVerlocStats) -> VerlocStats { + fn verloc_result_to_response(data: &nym_verloc::models::VerlocResultData) -> VerlocResultData { + VerlocResultData { + nodes_tested: data.nodes_tested, + run_started: data.run_started, + run_finished: data.run_finished, + results: data + .results + .iter() + .map(|r| VerlocNodeResult { + node_identity: r.node_identity, + latest_measurement: r.latest_measurement.map(|l| VerlocMeasurement { + minimum: l.minimum, + mean: l.mean, + maximum: l.maximum, + standard_deviation: l.standard_deviation, + }), + }) + .collect(), + } + } + + let guard = verloc_stats.read().await; + + let previous = if !guard.previous_run_data.run_finished() { + VerlocResult::Unavailable + } else { + VerlocResult::Data(verloc_result_to_response(&guard.previous_run_data)) + }; + + let current = if !guard.current_run_data.run_finished() { + VerlocResult::MeasurementInProgress + } else { + VerlocResult::Data(verloc_result_to_response(&guard.previous_run_data)) + }; + + VerlocStats { previous, current } +} + +pub type VerlocStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/mixnode/mod.rs b/nym-node/src/node/http/router/api/v1/mixnode/mod.rs similarity index 100% rename from nym-node/nym-node-http-api/src/router/api/v1/mixnode/mod.rs rename to nym-node/src/node/http/router/api/v1/mixnode/mod.rs diff --git a/nym-node/nym-node-http-api/src/router/api/v1/mixnode/root.rs b/nym-node/src/node/http/router/api/v1/mixnode/root.rs similarity index 94% rename from nym-node/nym-node-http-api/src/router/api/v1/mixnode/root.rs rename to nym-node/src/node/http/router/api/v1/mixnode/root.rs index d7f56ba3e0..4f1133ebc3 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/mixnode/root.rs +++ b/nym-node/src/node/http/router/api/v1/mixnode/root.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::mixnode::models::Mixnode; /// Returns root mixnode information diff --git a/nym-node/nym-node-http-api/src/router/api/v1/mod.rs b/nym-node/src/node/http/router/api/v1/mod.rs similarity index 97% rename from nym-node/nym-node-http-api/src/router/api/v1/mod.rs rename to nym-node/src/node/http/router/api/v1/mod.rs index f25b7cc801..41ccef50cd 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/mod.rs +++ b/nym-node/src/node/http/router/api/v1/mod.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::state::AppState; +use crate::node::http::state::AppState; use axum::routing::get; use axum::Router; use nym_node_requests::routes::api::v1; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/network_requester/exit_policy.rs b/nym-node/src/node/http/router/api/v1/network_requester/exit_policy.rs similarity index 93% rename from nym-node/nym-node-http-api/src/router/api/v1/network_requester/exit_policy.rs rename to nym-node/src/node/http/router/api/v1/network_requester/exit_policy.rs index 70bc69ec62..a58cd8862c 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/network_requester/exit_policy.rs +++ b/nym-node/src/node/http/router/api/v1/network_requester/exit_policy.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::{FormattedResponse, OutputParams}; use axum::extract::Query; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; /// Returns information about the exit policy used by this node. diff --git a/nym-node/nym-node-http-api/src/router/api/v1/network_requester/mod.rs b/nym-node/src/node/http/router/api/v1/network_requester/mod.rs similarity index 93% rename from nym-node/nym-node-http-api/src/router/api/v1/network_requester/mod.rs rename to nym-node/src/node/http/router/api/v1/network_requester/mod.rs index 917b5a668b..5524b51508 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/network_requester/mod.rs +++ b/nym-node/src/node/http/router/api/v1/network_requester/mod.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::v1::network_requester::exit_policy::node_exit_policy; +use crate::node::http::api::v1::network_requester::exit_policy::node_exit_policy; use axum::routing::get; use axum::Router; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/network_requester/root.rs b/nym-node/src/node/http/router/api/v1/network_requester/root.rs similarity index 94% rename from nym-node/nym-node-http-api/src/router/api/v1/network_requester/root.rs rename to nym-node/src/node/http/router/api/v1/network_requester/root.rs index ac92133e53..426614b07f 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/network_requester/root.rs +++ b/nym-node/src/node/http/router/api/v1/network_requester/root.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; /// Returns root network requester information diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/auxiliary.rs b/nym-node/src/node/http/router/api/v1/node/auxiliary.rs similarity index 88% rename from nym-node/nym-node-http-api/src/router/api/v1/node/auxiliary.rs rename to nym-node/src/node/http/router/api/v1/node/auxiliary.rs index bc4d4dae94..9db2b5f6dd 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/auxiliary.rs +++ b/nym-node/src/node/http/router/api/v1/node/auxiliary.rs @@ -1,9 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::{FormattedResponse, OutputParams}; -use crate::router::types::RequestError; +use crate::node::http::router::types::RequestError; use axum::extract::Query; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::node::models::AuxiliaryDetails; /// Returns auxiliary details of this node. diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/build_information.rs b/nym-node/src/node/http/router/api/v1/node/build_information.rs similarity index 93% rename from nym-node/nym-node-http-api/src/router/api/v1/node/build_information.rs rename to nym-node/src/node/http/router/api/v1/node/build_information.rs index b877f43d17..6073482c3e 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/build_information.rs +++ b/nym-node/src/node/http/router/api/v1/node/build_information.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned; /// Returns build metadata of the binary running the API diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/description.rs b/nym-node/src/node/http/router/api/v1/node/description.rs similarity index 88% rename from nym-node/nym-node-http-api/src/router/api/v1/node/description.rs rename to nym-node/src/node/http/router/api/v1/node/description.rs index f49d311505..ff569c78fb 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/description.rs +++ b/nym-node/src/node/http/router/api/v1/node/description.rs @@ -1,9 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::{FormattedResponse, OutputParams}; -use crate::router::types::RequestError; +use crate::node::http::router::types::RequestError; use axum::extract::Query; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::node::models::NodeDescription; /// Returns human-readable description of this node. diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/hardware.rs b/nym-node/src/node/http/router/api/v1/node/hardware.rs similarity index 91% rename from nym-node/nym-node-http-api/src/router/api/v1/node/hardware.rs rename to nym-node/src/node/http/router/api/v1/node/hardware.rs index 2d17b9e3a2..969e93458f 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/hardware.rs +++ b/nym-node/src/node/http/router/api/v1/node/hardware.rs @@ -1,10 +1,10 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::{FormattedResponse, OutputParams}; -use crate::router::types::RequestError; +use crate::node::http::router::types::RequestError; use axum::extract::Query; use axum::http::StatusCode; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::node::models::HostSystem; /// Returns build system information of the host running the binary. diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/host_information.rs b/nym-node/src/node/http/router/api/v1/node/host_information.rs similarity index 93% rename from nym-node/nym-node-http-api/src/router/api/v1/node/host_information.rs rename to nym-node/src/node/http/router/api/v1/node/host_information.rs index b4d7f6d363..d6cea0acde 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/host_information.rs +++ b/nym-node/src/node/http/router/api/v1/node/host_information.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::{FormattedResponse, OutputParams}; use axum::extract::Query; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::node::models::SignedHostInformation; /// Returns host information of this node. diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs b/nym-node/src/node/http/router/api/v1/node/mod.rs similarity index 84% rename from nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs rename to nym-node/src/node/http/router/api/v1/node/mod.rs index 6589b57aeb..f62b17ab3b 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs +++ b/nym-node/src/node/http/router/api/v1/node/mod.rs @@ -1,12 +1,12 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::api::v1::node::auxiliary::auxiliary; -use crate::api::v1::node::build_information::build_information; -use crate::api::v1::node::description::description; -use crate::api::v1::node::hardware::host_system; -use crate::api::v1::node::host_information::host_information; -use crate::api::v1::node::roles::roles; +use crate::node::http::api::v1::node::auxiliary::auxiliary; +use crate::node::http::api::v1::node::build_information::build_information; +use crate::node::http::api::v1::node::description::description; +use crate::node::http::api::v1::node::hardware::host_system; +use crate::node::http::api::v1::node::host_information::host_information; +use crate::node::http::api::v1::node::roles::roles; use axum::routing::get; use axum::Router; use nym_node_requests::api::v1::node::models; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/roles.rs b/nym-node/src/node/http/router/api/v1/node/roles.rs similarity index 92% rename from nym-node/nym-node-http-api/src/router/api/v1/node/roles.rs rename to nym-node/src/node/http/router/api/v1/node/roles.rs index 145b13798d..40b98d868a 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/roles.rs +++ b/nym-node/src/node/http/router/api/v1/node/roles.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; +use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_node_requests::api::v1::node::models::NodeRoles; /// Returns roles supported by this node diff --git a/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs b/nym-node/src/node/http/router/api/v1/openapi.rs similarity index 91% rename from nym-node/nym-node-http-api/src/router/api/v1/openapi.rs rename to nym-node/src/node/http/router/api/v1/openapi.rs index a0ef945f38..a46812464f 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs +++ b/nym-node/src/node/http/router/api/v1/openapi.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::router::api; -use crate::router::types::{ErrorResponse, RequestError}; +use crate::node::http::router::api; +use crate::node::http::router::types::{ErrorResponse, RequestError}; use axum::Router; use nym_node_requests::api as api_requests; use nym_node_requests::routes::api::{v1, v1_absolute}; @@ -20,7 +20,8 @@ use utoipa_swagger_ui::SwaggerUi; api::v1::node::hardware::host_system, api::v1::node::description::description, api::v1::node::auxiliary::auxiliary, - api::v1::metrics::mixing::mixing_stats, + api::v1::metrics::legacy_mixing::legacy_mixing_stats, + api::v1::metrics::packets_stats::packets_stats, api::v1::metrics::verloc::verloc_stats, api::v1::metrics::prometheus::prometheus_metrics, api::v1::health::root_health, @@ -35,8 +36,8 @@ use utoipa_swagger_ui::SwaggerUi; components( schemas( ErrorResponse, - api::Output, - api::OutputParams, + nym_http_api_common::Output, + nym_http_api_common::OutputParams, api_requests::v1::health::models::NodeHealth, api_requests::v1::health::models::NodeStatus, api_requests::v1::node::models::BinaryBuildInformationOwned, @@ -50,7 +51,7 @@ use utoipa_swagger_ui::SwaggerUi; api_requests::v1::node::models::CryptoHardware, api_requests::v1::node::models::NodeDescription, api_requests::v1::node::models::AuxiliaryDetails, - api_requests::v1::metrics::models::MixingStats, + api_requests::v1::metrics::models::LegacyMixingStats, api_requests::v1::metrics::models::VerlocStats, api_requests::v1::metrics::models::VerlocResult, api_requests::v1::metrics::models::VerlocResultData, diff --git a/nym-node/nym-node-http-api/src/router/landing_page.rs b/nym-node/src/node/http/router/landing_page.rs similarity index 100% rename from nym-node/nym-node-http-api/src/router/landing_page.rs rename to nym-node/src/node/http/router/landing_page.rs diff --git a/nym-node/nym-node-http-api/src/router/mod.rs b/nym-node/src/node/http/router/mod.rs similarity index 67% rename from nym-node/nym-node-http-api/src/router/mod.rs rename to nym-node/src/node/http/router/mod.rs index 5721274174..1a7615bbf7 100644 --- a/nym-node/nym-node-http-api/src/router/mod.rs +++ b/nym-node/src/node/http/router/mod.rs @@ -1,48 +1,44 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::error::NymNodeHttpError; -use crate::middleware::logging; -use crate::state::AppState; -use crate::NymNodeHTTPServer; +use crate::node::http::error::NymNodeHttpError; +use crate::node::http::state::AppState; +use crate::node::http::NymNodeHttpServer; use axum::response::Redirect; use axum::routing::get; use axum::Router; +use nym_bin_common::bin_info_owned; +use nym_http_api_common::logging; use nym_node_requests::api::v1::authenticator::models::Authenticator; -use nym_node_requests::api::v1::gateway::models::{Gateway, Wireguard}; +use nym_node_requests::api::v1::gateway::models::Gateway; use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; -use nym_node_requests::api::v1::node::models; use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, HostSystem, NodeDescription}; use nym_node_requests::api::SignedHostInformation; use nym_node_requests::routes; use std::net::SocketAddr; use std::path::Path; -use tracing::warn; pub mod api; pub mod landing_page; pub mod types; #[derive(Debug, Clone)] -pub struct Config { +pub struct HttpServerConfig { pub landing: landing_page::Config, pub api: api::Config, } -impl Config { - pub fn new( - build_information: models::BinaryBuildInformationOwned, - host_information: SignedHostInformation, - ) -> Self { - Config { +impl HttpServerConfig { + pub fn new(host_information: SignedHostInformation) -> Self { + HttpServerConfig { landing: Default::default(), api: api::Config { v1_config: api::v1::Config { node: api::v1::node::Config { - build_information, + build_information: bin_info_owned!(), host_information, system_info: None, roles: Default::default(), @@ -60,18 +56,6 @@ impl Config { } } - pub fn with_wireguard_interface(mut self, wireguard: Wireguard) -> Self { - match &mut self.api.v1_config.gateway.details { - Some(gw) => gw.client_interfaces.wireguard = Some(wireguard), - None => { - warn!( - "can't add wireguard interface information as the gateway role is not enabled." - ); - } - } - self - } - #[must_use] pub fn with_landing_page_assets>(mut self, assets_path: Option

) -> Self { self.landing.assets_path = assets_path.map(|p| p.as_ref().to_path_buf()); @@ -96,36 +80,18 @@ impl Config { self } - #[must_use] - pub fn with_gateway(mut self, gateway: Gateway) -> Self { - self.api.v1_config.node.roles.gateway_enabled = true; - self.with_gateway_details(gateway) - } - #[must_use] pub fn with_gateway_details(mut self, gateway: Gateway) -> Self { self.api.v1_config.gateway.details = Some(gateway); self } - #[must_use] - pub fn with_mixnode(mut self, mixnode: Mixnode) -> Self { - self.api.v1_config.node.roles.mixnode_enabled = true; - self.with_mixnode_details(mixnode) - } - #[must_use] pub fn with_mixnode_details(mut self, mixnode: Mixnode) -> Self { self.api.v1_config.mixnode.details = Some(mixnode); self } - #[must_use] - pub fn with_network_requester(mut self, network_requester: NetworkRequester) -> Self { - self.api.v1_config.node.roles.network_requester_enabled = true; - self.with_network_requester_details(network_requester) - } - #[must_use] pub fn with_network_requester_details(mut self, network_requester: NetworkRequester) -> Self { self.api.v1_config.network_requester.details = Some(network_requester); @@ -138,12 +104,6 @@ impl Config { self } - #[must_use] - pub fn with_ip_packet_router(mut self, ip_packet_router: IpPacketRouter) -> Self { - self.api.v1_config.node.roles.ip_packet_router_enabled = true; - self.with_ip_packet_router_details(ip_packet_router) - } - #[must_use] pub fn with_ip_packet_router_details(mut self, ip_packet_router: IpPacketRouter) -> Self { self.api.v1_config.ip_packet_router.details = Some(ip_packet_router); @@ -162,10 +122,7 @@ pub struct NymNodeRouter { } impl NymNodeRouter { - // TODO: move the wg state to a builder - pub fn new(config: Config, app_state: Option) -> NymNodeRouter { - let state = app_state.unwrap_or(AppState::new()); - + pub fn new(config: HttpServerConfig, state: AppState) -> NymNodeRouter { NymNodeRouter { inner: Router::new() // redirection for old legacy mixnode routes @@ -179,7 +136,9 @@ impl NymNodeRouter { ) .route( "/stats", - get(|| async { Redirect::to(&routes::api::v1::metrics::mixing_absolute()) }), + get(|| async { + Redirect::to(&routes::api::v1::metrics::legacy_mixing_absolute()) + }), ) .route( "/verloc", @@ -198,23 +157,10 @@ impl NymNodeRouter { } } - // this is only a temporary method until everything is properly moved into the nym-node itself - #[must_use] - pub fn with_route(mut self, path: &str, router: Router) -> Self { - self.inner = self.inner.nest(path, router); - self - } - - #[must_use] - pub fn with_merged(mut self, router: Router) -> Self { - self.inner = self.inner.merge(router); - self - } - pub async fn build_server( self, bind_address: &SocketAddr, - ) -> Result { + ) -> Result { let listener = tokio::net::TcpListener::bind(bind_address) .await .map_err(|source| NymNodeHttpError::HttpBindFailure { @@ -228,6 +174,6 @@ impl NymNodeRouter { .into_make_service_with_connect_info::(), ); - Ok(NymNodeHTTPServer::new(axum_server)) + Ok(NymNodeHttpServer::new(axum_server)) } } diff --git a/nym-node/nym-node-http-api/src/router/types.rs b/nym-node/src/node/http/router/types.rs similarity index 100% rename from nym-node/nym-node-http-api/src/router/types.rs rename to nym-node/src/node/http/router/types.rs diff --git a/nym-node/src/node/http/state/metrics.rs b/nym-node/src/node/http/state/metrics.rs new file mode 100644 index 0000000000..c9156c021e --- /dev/null +++ b/nym-node/src/node/http/state/metrics.rs @@ -0,0 +1,23 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::http::state::AppState; +use axum::extract::FromRef; +use nym_node_metrics::NymNodeMetrics; + +pub use nym_verloc::measurements::metrics::SharedVerlocStats; + +#[derive(Clone)] +pub struct MetricsAppState { + pub(crate) prometheus_access_token: Option, + + pub(crate) metrics: NymNodeMetrics, + + pub(crate) verloc: SharedVerlocStats, +} + +impl FromRef for MetricsAppState { + fn from_ref(app_state: &AppState) -> Self { + app_state.metrics.clone() + } +} diff --git a/nym-node/src/node/http/state/mod.rs b/nym-node/src/node/http/state/mod.rs new file mode 100644 index 0000000000..b9b58c0cce --- /dev/null +++ b/nym-node/src/node/http/state/mod.rs @@ -0,0 +1,40 @@ +// Copyright 2023-2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::http::state::metrics::MetricsAppState; +use nym_node_metrics::NymNodeMetrics; +use nym_verloc::measurements::SharedVerlocStats; +use tokio::time::Instant; + +pub mod metrics; + +#[derive(Clone)] +pub struct AppState { + pub(crate) startup_time: Instant, + + pub(crate) metrics: MetricsAppState, +} + +impl AppState { + #[allow(clippy::new_without_default)] + pub fn new(metrics: NymNodeMetrics, verloc: SharedVerlocStats) -> Self { + AppState { + // is it 100% accurate? + // no. + // does it have to be? + // also no. + startup_time: Instant::now(), + metrics: MetricsAppState { + prometheus_access_token: None, + metrics, + verloc, + }, + } + } + + #[must_use] + pub fn with_metrics_key(mut self, bearer_token: impl Into>) -> Self { + self.metrics.prometheus_access_token = bearer_token.into(); + self + } +} diff --git a/nym-node/src/node/metrics/aggregator.rs b/nym-node/src/node/metrics/aggregator.rs new file mode 100644 index 0000000000..c166eb0d3c --- /dev/null +++ b/nym-node/src/node/metrics/aggregator.rs @@ -0,0 +1,147 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::metrics::handler::{HandlerWrapper, MetricsHandler, RegistrableHandler}; +use futures::StreamExt; +use nym_node_metrics::events::{ + events_channels, MetricEventsReceiver, MetricEventsSender, MetricsEvent, +}; +use std::any; +use std::any::TypeId; +use std::collections::HashMap; +use std::ops::DerefMut; +use std::time::Duration; +use tokio::task::JoinHandle; +use tokio::time::{interval_at, Instant}; +use tracing::{debug, error, trace, warn}; + +pub(crate) struct MetricsAggregator { + // possible issue: this has to be low enough so that frequent handlers would be called sufficiently + // (if handler doesn't need an update, its internal methods won't be called so it's not going to be wasteful) + handlers_update_interval: Duration, + + registered_handlers: HashMap>, + // registered_handlers: HashMap>, + event_sender: MetricEventsSender, + event_receiver: MetricEventsReceiver, + shutdown: nym_task::TaskClient, +} + +impl MetricsAggregator { + pub fn new(handlers_update_interval: Duration, shutdown: nym_task::TaskClient) -> Self { + let (event_sender, event_receiver) = events_channels(); + + MetricsAggregator { + handlers_update_interval, + registered_handlers: Default::default(), + event_sender, + event_receiver, + shutdown, + } + } + + pub fn sender(&self) -> MetricEventsSender { + self.event_sender.clone() + } + + pub fn register_handler(&mut self, handler: H, update_interval: Duration) + where + H: MetricsHandler, + { + let events_name = any::type_name::(); + let handler_name = any::type_name::(); + + debug!("registering handler '{handler_name}' for events of type '{events_name}'"); + + let type_id = TypeId::of::(); + if self.registered_handlers.contains_key(&type_id) { + panic!("duplicate handler for '{events_name}' (id: {type_id:?})",) + }; + + self.registered_handlers.insert( + type_id, + Box::new(HandlerWrapper::new(update_interval, handler)), + ); + } + + async fn periodic_handlers_update(&mut self) { + for handler in self.registered_handlers.values_mut() { + handler.on_update().await; + } + } + + async fn handle_metrics_event(&mut self, event: T) { + let Some(handler) = self.registered_handlers.get_mut(&TypeId::of::()) else { + let name = any::type_name::(); + + warn!("no registered handler for events of type {name}"); + return; + }; + + #[allow(clippy::expect_used)] + let handler: &mut HandlerWrapper = handler + .deref_mut() + .as_any_mut() + .downcast_mut() + .expect("handler downcasting failure"); + + handler.handle_event(event).await; + } + + async fn handle_event(&mut self, event: MetricsEvent) { + match event { + MetricsEvent::GatewayClientSession(client_session) => { + self.handle_metrics_event(client_session).await + } + } + } + + async fn on_start(&mut self) { + for handler in self.registered_handlers.values_mut() { + handler.on_start().await; + } + } + + pub async fn run(&mut self) { + self.on_start().await; + + let start = Instant::now() + self.handlers_update_interval; + let mut update_interval = interval_at(start, self.handlers_update_interval); + + let mut processed = 0; + trace!("starting MetricsAggregator"); + loop { + tokio::select! { + biased; + _ = self.shutdown.recv() => { + debug!("MetricsAggregator: Received shutdown"); + break; + } + _ = update_interval.tick() => { + self.periodic_handlers_update().await; + } + new_event = self.event_receiver.next() => { + // this one is impossible to ever panic - the struct itself contains a sender + // and hence it can't happen that ALL senders are dropped + #[allow(clippy::unwrap_used)] + self.handle_event(new_event.unwrap()).await; + if processed % 1000 == 0 { + let queue_len = self.event_sender.len(); + match queue_len { + n if n > 200 => error!("there are currently {n} pending events waiting to get processed!"), + n if n > 50 => warn!("there are currently {n} pending events waiting to get processed"), + n => trace!("there are currently {n} pending events waiting to get processed"), + } + } + processed += 1; + } + + } + } + trace!("MetricsAggregator: Exiting"); + } + + pub fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { self.run().await }) + } +} diff --git a/nym-node/src/node/metrics/console_logger.rs b/nym-node/src/node/metrics/console_logger.rs new file mode 100644 index 0000000000..ea3d11e94c --- /dev/null +++ b/nym-node/src/node/metrics/console_logger.rs @@ -0,0 +1,120 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use human_repr::HumanCount; +use human_repr::HumanThroughput; +use nym_node_metrics::NymNodeMetrics; +use nym_task::TaskClient; +use std::time::Duration; +use time::OffsetDateTime; +use tokio::task::JoinHandle; +use tokio::time::{interval_at, Instant}; +use tracing::{info, trace}; + +struct AtLastUpdate { + time: OffsetDateTime, + + // INGRESS + forward_hop_packets_received: usize, + + // INGRESS + final_hop_packets_received: usize, + + // EGRESS + forward_hop_packets_sent: usize, + + // EGRESS + ack_packets_sent: usize, +} + +impl AtLastUpdate { + fn new() -> Self { + Self { + time: OffsetDateTime::now_utc(), + forward_hop_packets_received: 0, + final_hop_packets_received: 0, + forward_hop_packets_sent: 0, + ack_packets_sent: 0, + } + } +} + +// replicate behaviour from old mixnode to log number of mixed packets +pub(crate) struct ConsoleLogger { + logging_delay: Duration, + at_last_update: AtLastUpdate, + metrics: NymNodeMetrics, + shutdown: TaskClient, +} + +impl ConsoleLogger { + pub(crate) fn new( + logging_delay: Duration, + metrics: NymNodeMetrics, + shutdown: TaskClient, + ) -> Self { + ConsoleLogger { + logging_delay, + at_last_update: AtLastUpdate::new(), + metrics, + shutdown, + } + } + + async fn log_running_stats(&mut self) { + let now = OffsetDateTime::now_utc(); + let delta_secs = (now - self.at_last_update.time).as_seconds_f64(); + + let forward_received = self.metrics.mixnet.ingress.forward_hop_packets_received(); + let final_received = self.metrics.mixnet.ingress.final_hop_packets_received(); + let forward_sent = self.metrics.mixnet.egress.forward_hop_packets_sent(); + let acks = self.metrics.mixnet.egress.ack_packets_sent(); + + let forward_received_rate = + (forward_received - self.at_last_update.forward_hop_packets_received) as f64 + / delta_secs; + let final_rate = + (final_received - self.at_last_update.final_hop_packets_received) as f64 / delta_secs; + let forward_sent_rate = + (forward_sent - self.at_last_update.forward_hop_packets_sent) as f64 / delta_secs; + let acks_rate = (acks - self.at_last_update.ack_packets_sent) as f64 / delta_secs; + + info!("↑↓ Packets sent [total] / sent [acks] / received [mix] / received [gw]: {} ({}) / {} ({}) / {} ({}) / {} ({})", + forward_sent.human_count_bare(), + forward_sent_rate.human_throughput_bare(), + acks.human_count_bare(), + acks_rate.human_throughput_bare(), + forward_received.human_count_bare(), + forward_received_rate.human_throughput_bare(), + final_received.human_count_bare(), + final_rate.human_throughput_bare(), + ); + + self.at_last_update.time = now; + self.at_last_update.forward_hop_packets_received = forward_received; + self.at_last_update.final_hop_packets_received = final_received; + self.at_last_update.forward_hop_packets_sent = forward_sent; + self.at_last_update.ack_packets_sent = acks; + + // TODO: add websocket-client traffic + } + + async fn run(&mut self) { + trace!("Starting ConsoleLogger"); + let mut interval = interval_at(Instant::now() + self.logging_delay, self.logging_delay); + while !self.shutdown.is_shutdown() { + tokio::select! { + biased; + _ = self.shutdown.recv() => { + trace!("ConsoleLogger: Received shutdown"); + } + _ = interval.tick() => self.log_running_stats().await, + }; + } + trace!("ConsoleLogger: Exiting"); + } + + pub(crate) fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { self.run().await }) + } +} diff --git a/nym-node/src/node/metrics/events_listener.rs b/nym-node/src/node/metrics/events_listener.rs new file mode 100644 index 0000000000..755fb6cc8b --- /dev/null +++ b/nym-node/src/node/metrics/events_listener.rs @@ -0,0 +1,2 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 diff --git a/gateway/src/node/statistics/sessions.rs b/nym-node/src/node/metrics/handler/client_sessions.rs similarity index 55% rename from gateway/src/node/statistics/sessions.rs rename to nym-node/src/node/metrics/handler/client_sessions.rs index eca940a21d..5f9e870a2a 100644 --- a/gateway/src/node/statistics/sessions.rs +++ b/nym-node/src/node/metrics/handler/client_sessions.rs @@ -1,52 +1,37 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -use nym_credentials_interface::TicketType; -use nym_gateway_stats_storage::models::FinishedSession; -use nym_gateway_stats_storage::PersistentStatsStorage; -use nym_gateway_stats_storage::{error::StatsStorageError, models::ActiveSession}; -use nym_node_http_api::state::metrics::SharedSessionStats; -use nym_sphinx::DestinationAddressBytes; -use sha2::{Digest, Sha256}; +use crate::node::metrics::handler::{ + MetricsHandler, OnStartMetricsHandler, OnUpdateMetricsHandler, +}; +use async_trait::async_trait; +use nym_gateway::node::PersistentStatsStorage; +use nym_gateway_stats_storage::error::StatsStorageError; +use nym_gateway_stats_storage::models::{TicketType, ToSessionType}; +use nym_node_metrics::entry::{ActiveSession, ClientSessions, FinishedSession}; +use nym_node_metrics::events::GatewaySessionEvent; +use nym_node_metrics::NymNodeMetrics; +use nym_sphinx_types::DestinationAddressBytes; use time::{Date, Duration, OffsetDateTime}; +use tracing::error; +use tracing::log::trace; -use nym_statistics_common::gateways::SessionEvent; - -pub(crate) struct SessionStatsHandler { +pub(crate) struct GatewaySessionStatsHandler { storage: PersistentStatsStorage, current_day: Date, - shared_session_stats: SharedSessionStats, + metrics: NymNodeMetrics, } -impl SessionStatsHandler { - pub fn new(shared_session_stats: SharedSessionStats, storage: PersistentStatsStorage) -> Self { - SessionStatsHandler { +impl GatewaySessionStatsHandler { + pub(crate) fn new(metrics: NymNodeMetrics, storage: PersistentStatsStorage) -> Self { + GatewaySessionStatsHandler { storage, current_day: OffsetDateTime::now_utc().date(), - shared_session_stats, + metrics, } } - pub(crate) async fn handle_event( - &mut self, - event: SessionEvent, - ) -> Result<(), StatsStorageError> { - match event { - SessionEvent::SessionStart { start_time, client } => { - self.handle_session_start(start_time, client).await - } - - SessionEvent::SessionStop { stop_time, client } => { - self.handle_session_stop(stop_time, client).await - } - - SessionEvent::EcashTicket { - ticket_type, - client, - } => self.handle_ecash_ticket(ticket_type, client).await, - } - } async fn handle_session_start( &mut self, start_time: OffsetDateTime, @@ -83,62 +68,38 @@ impl SessionStatsHandler { client: DestinationAddressBytes, ) -> Result<(), StatsStorageError> { self.storage - .update_active_session_type(client, ticket_type.into()) + .update_active_session_type(client, ticket_type.to_session_type()) .await?; Ok(()) } - pub(crate) async fn on_start(&mut self) -> Result<(), StatsStorageError> { - let yesterday = OffsetDateTime::now_utc().date() - Duration::DAY; - //publish yesterday's data if any - self.publish_stats(yesterday).await?; - //store "active" sessions as duration 0 - for active_session in self.storage.get_all_active_sessions().await? { - self.storage - .insert_finished_session( - self.current_day, - FinishedSession { - duration: Duration::ZERO, - typ: active_session.typ, - }, - ) - .await? - } - //cleanup active sessions - self.storage.cleanup_active_sessions().await?; + async fn handle_session_event( + &mut self, + event: GatewaySessionEvent, + ) -> Result<(), StatsStorageError> { + match event { + GatewaySessionEvent::SessionStart { start_time, client } => { + self.handle_session_start(start_time, client).await + } - //delete old entries - self.delete_old_stats(yesterday - Duration::DAY).await?; - Ok(()) + GatewaySessionEvent::SessionStop { stop_time, client } => { + self.handle_session_stop(stop_time, client).await + } + + GatewaySessionEvent::EcashTicket { + ticket_type, + client, + } => self.handle_ecash_ticket(ticket_type, client).await, + } } - //update shared state once a day has passed, with data from the previous day - async fn publish_stats(&mut self, stats_date: Date) -> Result<(), StatsStorageError> { - let finished_sessions = self.storage.get_finished_sessions(stats_date).await?; - let unique_users = self.storage.get_unique_users(stats_date).await?; - let unique_users_hash = unique_users - .into_iter() - .map(|address| format!("{:x}", Sha256::digest(address))) - .collect::>(); - let session_started = self.storage.get_started_sessions_count(stats_date).await? as u32; - { - let mut shared_state = self.shared_session_stats.write().await; - shared_state.update_time = stats_date; - shared_state.unique_active_users_count = unique_users_hash.len() as u32; - shared_state.unique_active_users_hashes = unique_users_hash; - shared_state.session_started = session_started; - shared_state.sessions = finished_sessions.iter().map(|s| s.serialize()).collect(); - } - - Ok(()) - } - pub(crate) async fn maybe_update_shared_state( + async fn maybe_update_shared_state( &mut self, update_time: OffsetDateTime, ) -> Result<(), StatsStorageError> { let update_date = update_time.date(); if update_date != self.current_day { - self.publish_stats(self.current_day).await?; + self.update_shared_stats(self.current_day).await?; self.delete_old_stats(self.current_day - Duration::DAY) .await?; self.reset_stats(update_date).await?; @@ -147,6 +108,26 @@ impl SessionStatsHandler { Ok(()) } + // update shared state once a day has passed, with data from the previous day + async fn update_shared_stats(&mut self, stats_date: Date) -> Result<(), StatsStorageError> { + let finished_sessions = self.storage.get_finished_sessions(stats_date).await?; + let unique_users = self.storage.get_unique_users(stats_date).await?; + let session_started = self.storage.get_started_sessions_count(stats_date).await? as u32; + + let new_sessions = ClientSessions::new( + stats_date, + unique_users, + session_started, + finished_sessions.into_iter().map(Into::into).collect(), + ); + self.metrics + .entry + .update_client_sessions(new_sessions) + .await; + + Ok(()) + } + async fn reset_stats(&mut self, reset_day: Date) -> Result<(), StatsStorageError> { //active users reset let new_active_users = self.storage.get_active_users().await?; @@ -162,4 +143,56 @@ impl SessionStatsHandler { self.storage.delete_unique_users(delete_before).await?; Ok(()) } + + async fn cleanup(&mut self) -> Result<(), StatsStorageError> { + let yesterday = OffsetDateTime::now_utc().date() - Duration::DAY; + //publish yesterday's data if any + self.update_shared_stats(yesterday).await?; + //store "active" sessions as duration 0 + for active_session in self.storage.get_all_active_sessions().await? { + self.storage + .insert_finished_session( + self.current_day, + FinishedSession::new(Default::default(), active_session.typ), + ) + .await? + } + //cleanup active sessions + self.storage.cleanup_active_sessions().await?; + + //delete old entries + self.delete_old_stats(yesterday - Duration::DAY).await?; + Ok(()) + } +} + +#[async_trait] +impl OnStartMetricsHandler for GatewaySessionStatsHandler { + async fn on_start(&mut self) { + if let Err(err) = self.cleanup().await { + error!("failed to cleanup gateway session stats handler: {err}"); + } + } +} + +#[async_trait] +impl OnUpdateMetricsHandler for GatewaySessionStatsHandler { + async fn on_update(&mut self) { + let now = OffsetDateTime::now_utc(); + if let Err(err) = self.maybe_update_shared_state(now).await { + error!("failed to update session stats: {err}"); + } + } +} + +#[async_trait] +impl MetricsHandler for GatewaySessionStatsHandler { + type Events = GatewaySessionEvent; + + async fn handle_event(&mut self, event: Self::Events) { + trace!("event: {event:?}"); + if let Err(err) = self.handle_session_event(event).await { + error!("failed to handle client session event '{event:?}': {err}") + } + } } diff --git a/nym-node/src/node/metrics/handler/legacy_packet_data.rs b/nym-node/src/node/metrics/handler/legacy_packet_data.rs new file mode 100644 index 0000000000..4b649e831a --- /dev/null +++ b/nym-node/src/node/metrics/handler/legacy_packet_data.rs @@ -0,0 +1,68 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::metrics::handler::{ + MetricsHandler, OnStartMetricsHandler, OnUpdateMetricsHandler, +}; +use async_trait::async_trait; +use nym_node_metrics::NymNodeMetrics; +use time::OffsetDateTime; + +// it can be anything, we just need a unique type_id to register our handler +pub struct LegacyMixingData; + +pub struct LegacyMixingStatsUpdater { + received_total_at_last_update: usize, + dropped_total_at_last_update: usize, + sent_total_at_last_update: usize, + + metrics: NymNodeMetrics, +} + +impl LegacyMixingStatsUpdater { + pub(crate) fn new(metrics: NymNodeMetrics) -> Self { + LegacyMixingStatsUpdater { + received_total_at_last_update: 0, + dropped_total_at_last_update: 0, + sent_total_at_last_update: 0, + metrics, + } + } +} + +#[async_trait] +impl OnStartMetricsHandler for LegacyMixingStatsUpdater {} + +#[async_trait] +impl OnUpdateMetricsHandler for LegacyMixingStatsUpdater { + async fn on_update(&mut self) { + let total_received = self.metrics.mixnet.ingress.forward_hop_packets_received(); + let total_dropped = self.metrics.mixnet.egress.forward_hop_packets_dropped(); + let total_sent = self.metrics.mixnet.egress.forward_hop_packets_sent(); + + let received_since_update = + total_received.saturating_sub(self.received_total_at_last_update); + let dropped_since_update = total_dropped.saturating_sub(self.dropped_total_at_last_update); + let sent_since_update = total_sent.saturating_sub(self.sent_total_at_last_update); + + self.received_total_at_last_update = total_received; + self.sent_total_at_last_update = total_sent; + self.dropped_total_at_last_update = total_dropped; + + self.metrics.mixnet.update_legacy_stats( + received_since_update, + sent_since_update, + dropped_since_update, + OffsetDateTime::now_utc().unix_timestamp(), + ); + } +} + +#[async_trait] +impl MetricsHandler for LegacyMixingStatsUpdater { + type Events = LegacyMixingData; + + async fn handle_event(&mut self, _event: Self::Events) { + panic!("this should have never been called! MetricsHandler has been incorrectly called on LegacyMixingStatsUpdater") + } +} diff --git a/nym-node/src/node/metrics/handler/mixnet_data_cleaner.rs b/nym-node/src/node/metrics/handler/mixnet_data_cleaner.rs new file mode 100644 index 0000000000..83c2041b5b --- /dev/null +++ b/nym-node/src/node/metrics/handler/mixnet_data_cleaner.rs @@ -0,0 +1,107 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::metrics::handler::{ + MetricsHandler, OnStartMetricsHandler, OnUpdateMetricsHandler, +}; +use async_trait::async_trait; +use nym_node_metrics::mixnet::{EgressRecipientStats, IngressRecipientStats}; +use nym_node_metrics::NymNodeMetrics; +use std::collections::HashMap; +use std::net::{IpAddr, SocketAddr}; + +// it can be anything, we just need a unique type_id to register our handler +pub struct StaleMixnetMetrics; + +#[derive(Default)] +pub struct LastSeen { + ingress_senders: HashMap, + egress_forward_recipients: HashMap, +} + +pub struct MixnetMetricsCleaner { + metrics: NymNodeMetrics, + last_seen: LastSeen, +} + +impl MixnetMetricsCleaner { + pub(crate) fn new(metrics: NymNodeMetrics) -> Self { + MixnetMetricsCleaner { + metrics, + last_seen: LastSeen::default(), + } + } +} + +#[async_trait] +impl OnStartMetricsHandler for MixnetMetricsCleaner {} + +#[async_trait] +impl OnUpdateMetricsHandler for MixnetMetricsCleaner { + async fn on_update(&mut self) { + let mut senders_to_remove = Vec::new(); + let mut recipients_to_remove = Vec::new(); + + for sender_entry in self.metrics.mixnet.ingress.senders().iter() { + if let Some(last_seen) = self.last_seen.ingress_senders.get(sender_entry.key()) { + if sender_entry.value() == last_seen { + senders_to_remove.push(*sender_entry.key()); + } + } + } + + for recipient_entry in self.metrics.mixnet.egress.forward_recipients().iter() { + if let Some(last_seen) = self + .last_seen + .egress_forward_recipients + .get(recipient_entry.key()) + { + if recipient_entry.value() == last_seen { + recipients_to_remove.push(*recipient_entry.key()); + } + } + } + + // no need to make copies if data hasn't changed + if !senders_to_remove.is_empty() { + let mut new_ingress_senders = HashMap::new(); + + for sender in senders_to_remove { + self.metrics.mixnet.ingress.remove_stale_sender(sender) + } + + for sender_entry in self.metrics.mixnet.ingress.senders() { + new_ingress_senders.insert(*sender_entry.key(), *sender_entry.value()); + } + + self.last_seen.ingress_senders = new_ingress_senders; + } + + if !recipients_to_remove.is_empty() { + let mut new_egress_forward_recipients = HashMap::new(); + + for recipient in recipients_to_remove { + self.metrics + .mixnet + .egress + .remove_stale_forward_recipient(recipient) + } + + for recipient_entry in self.metrics.mixnet.egress.forward_recipients() { + new_egress_forward_recipients + .insert(*recipient_entry.key(), *recipient_entry.value()); + } + + self.last_seen.egress_forward_recipients = new_egress_forward_recipients; + } + } +} + +#[async_trait] +impl MetricsHandler for MixnetMetricsCleaner { + type Events = StaleMixnetMetrics; + + async fn handle_event(&mut self, _event: Self::Events) { + panic!("this should have never been called! MetricsHandler has been incorrectly called on MixnetMetricsCleaner") + } +} diff --git a/nym-node/src/node/metrics/handler/mod.rs b/nym-node/src/node/metrics/handler/mod.rs new file mode 100644 index 0000000000..025b1419b7 --- /dev/null +++ b/nym-node/src/node/metrics/handler/mod.rs @@ -0,0 +1,122 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use std::any; +use std::any::Any; +use std::time::Duration; +use tokio::time::Instant; +use tracing::trace; + +pub(crate) mod client_sessions; +pub(crate) mod legacy_packet_data; +pub(crate) mod mixnet_data_cleaner; + +pub(crate) trait RegistrableHandler: + Downcast + OnStartMetricsHandler + OnUpdateMetricsHandler + Send + Sync + 'static +{ +} + +impl RegistrableHandler for T where + T: Downcast + OnStartMetricsHandler + OnUpdateMetricsHandler + Send + Sync + 'static +{ +} + +pub trait Downcast { + #[allow(dead_code)] + fn as_any(&'_ self) -> &'_ dyn Any + where + Self: 'static; + + fn as_any_mut(&'_ mut self) -> &mut dyn Any + where + Self: 'static; +} + +impl Downcast for T { + fn as_any(&'_ self) -> &'_ dyn Any + where + Self: 'static, + { + self + } + + fn as_any_mut(&'_ mut self) -> &'_ mut dyn Any + where + Self: 'static, + { + self + } +} + +#[async_trait] +pub(crate) trait MetricsHandler: RegistrableHandler { + type Events; + + async fn handle_event(&mut self, event: Self::Events); +} + +#[async_trait] +pub(crate) trait OnStartMetricsHandler { + async fn on_start(&mut self) {} +} + +#[async_trait] +pub(crate) trait OnUpdateMetricsHandler { + async fn on_update(&mut self); +} + +pub(crate) struct HandlerWrapper { + handler: Box>, + update_interval: Duration, + last_updated: Instant, +} + +impl HandlerWrapper { + pub fn new(update_interval: Duration, handler: U) -> Self + where + U: MetricsHandler, + { + HandlerWrapper { + handler: Box::new(handler), + update_interval, + last_updated: Instant::now(), + } + } +} + +impl HandlerWrapper +where + T: 'static, +{ + pub(crate) async fn handle_event(&mut self, event: T) { + self.handler.handle_event(event).await + } +} + +#[async_trait] +impl OnStartMetricsHandler for HandlerWrapper { + async fn on_start(&mut self) { + let name = any::type_name::(); + trace!("on start for handler for events of type {name}"); + + self.handler.on_start().await; + } +} + +#[async_trait] +impl OnUpdateMetricsHandler for HandlerWrapper { + async fn on_update(&mut self) { + let name = any::type_name::(); + trace!("on update for handler for events of type {name}"); + + let elapsed = self.last_updated.elapsed(); + if elapsed < self.update_interval { + trace!("too soon for updates"); + return; + } + + self.handler.on_update().await; + self.last_updated = Instant::now(); + } +} diff --git a/nym-node/src/node/metrics/mod.rs b/nym-node/src/node/metrics/mod.rs new file mode 100644 index 0000000000..d8f119efea --- /dev/null +++ b/nym-node/src/node/metrics/mod.rs @@ -0,0 +1,7 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub(crate) mod aggregator; +pub(crate) mod console_logger; +pub(crate) mod events_listener; +pub(crate) mod handler; diff --git a/nym-node/src/node/mixnet/handler.rs b/nym-node/src/node/mixnet/handler.rs new file mode 100644 index 0000000000..6cfb7d50d1 --- /dev/null +++ b/nym-node/src/node/mixnet/handler.rs @@ -0,0 +1,184 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::mixnet::shared::SharedData; +use futures::StreamExt; +use nym_metrics::nanos; +use nym_sphinx_forwarding::packet::MixPacket; +use nym_sphinx_framing::codec::NymCodec; +use nym_sphinx_framing::packet::FramedNymPacket; +use nym_sphinx_framing::processing::{ + process_framed_packet, MixProcessingResult, ProcessedFinalHop, +}; +use nym_sphinx_types::Delay; +use std::net::SocketAddr; +use tokio::net::TcpStream; +use tokio::time::Instant; +use tokio_util::codec::Framed; +use tracing::{debug, error, instrument, trace}; + +pub(crate) struct ConnectionHandler { + shared: SharedData, + mixnet_connection: Framed, + remote_address: SocketAddr, +} + +impl Drop for ConnectionHandler { + fn drop(&mut self) { + self.shared + .metrics + .network + .disconnected_ingress_mixnet_client() + } +} + +impl ConnectionHandler { + pub(crate) fn new( + shared: &SharedData, + tcp_stream: TcpStream, + remote_address: SocketAddr, + ) -> Self { + let mut task_client = shared.task_client.fork(remote_address.to_string()); + // we don't want dropped connections to cause global shutdown + task_client.disarm(); + + shared.metrics.network.new_active_ingress_mixnet_client(); + + ConnectionHandler { + shared: SharedData { + processing_config: shared.processing_config, + sphinx_key: shared.sphinx_key.clone(), + mixnet_forwarder: shared.mixnet_forwarder.clone(), + final_hop: shared.final_hop.clone(), + metrics: shared.metrics.clone(), + task_client, + }, + remote_address, + mixnet_connection: Framed::new(tcp_stream, NymCodec), + } + } + + /// Determine instant at which packet should get forwarded to the next hop. + /// By using [`Instant`] rather than explicit [`Duration`] we minimise effects of + /// the skew caused by being stuck in the channel queue. + /// This method also clamps the maximum allowed delay so that nobody could send a bunch of packets + /// with, for example, delays of 1 year thus causing denial of service + fn create_delay_target(&self, delay: Option) -> Option { + let delay = delay?.to_duration(); + let now = Instant::now(); + + let delay = if delay > self.shared.processing_config.maximum_packet_delay { + self.shared.processing_config.maximum_packet_delay + } else { + delay + }; + trace!( + "received packet will be delayed for {}ms", + delay.as_millis() + ); + + Some(now + delay) + } + + fn handle_forward_packet(&self, mix_packet: MixPacket, delay: Option) { + if !self.shared.processing_config.forward_hop_processing_enabled { + trace!("this nym-node does not support forward hop packets"); + self.shared.dropped_forward_packet(self.remote_address.ip()); + return; + } + + let forward_instant = self.create_delay_target(delay); + self.shared.forward_mix_packet(mix_packet, forward_instant); + } + + async fn handle_final_hop(&self, final_hop_data: ProcessedFinalHop) { + if !self.shared.processing_config.final_hop_processing_enabled { + trace!("this nym-node does not support final hop packets"); + self.shared + .dropped_final_hop_packet(self.remote_address.ip()); + return; + } + + let client = final_hop_data.destination; + let message = final_hop_data.message; + + // if possible attempt to push message directly to the client + match self.shared.try_push_message_to_client(client, message) { + Err(unsent_plaintext) => { + // if that failed, store it on disk (to be 🔥 soon...) + match self + .shared + .store_processed_packet_payload(client, unsent_plaintext) + .await + { + Err(err) => error!("Failed to store client data - {err}"), + Ok(_) => trace!("Stored packet for {client}"), + } + } + Ok(_) => trace!("Pushed received packet to {client}"), + } + + // if we managed to either push message directly to the [online] client or store it at + // its inbox, it means that it must exist at this gateway, hence we can send the + // received ack back into the network + self.shared.forward_ack_packet(final_hop_data.forward_ack); + } + + #[instrument(skip(self, packet), level = "debug")] + async fn handle_received_nym_packet(&self, packet: FramedNymPacket) { + // TODO: here be replay attack detection with bloomfilters and all the fancy stuff + // + + nanos!("handle_received_nym_packet", { + // 1. attempt to unwrap the packet + let unwrapped_packet = process_framed_packet(packet, &self.shared.sphinx_key); + + // 2. increment our favourite metrics stats + self.shared + .update_metrics(&unwrapped_packet, self.remote_address.ip()); + + // 3. forward the packet to the relevant sink (if enabled) + match unwrapped_packet { + Err(err) => trace!("failed to process received mix packet: {err}"), + Ok(MixProcessingResult::ForwardHop(forward_packet, delay)) => { + self.handle_forward_packet(forward_packet, delay); + } + Ok(MixProcessingResult::FinalHop(final_hop_data)) => { + self.handle_final_hop(final_hop_data).await; + } + } + }) + } + + #[instrument( + skip(self), + level = "debug", + fields( + remote = %self.remote_address + ) + )] + pub(crate) async fn handle_stream(&mut self) { + while !self.shared.task_client.is_shutdown() { + tokio::select! { + biased; + _ = self.shared.task_client.recv() => { + trace!("connection handler: received shutdown"); + } + maybe_framed_nym_packet = self.mixnet_connection.next() => { + match maybe_framed_nym_packet { + Some(Ok(packet)) => self.handle_received_nym_packet(packet).await, + Some(Err(err)) => { + debug!("connection got corrupted with: {err}"); + return + } + None => { + debug!("connection got closed by the remote"); + return + } + } + } + } + } + debug!("exiting and closing connection"); + } +} diff --git a/nym-node/src/node/mixnet/listener.rs b/nym-node/src/node/mixnet/listener.rs new file mode 100644 index 0000000000..34ae2918fe --- /dev/null +++ b/nym-node/src/node/mixnet/listener.rs @@ -0,0 +1,57 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::mixnet::SharedData; +use nym_task::TaskClient; +use std::net::SocketAddr; +use tokio::task::JoinHandle; +use tracing::{error, info, trace}; + +pub(crate) struct Listener { + bind_address: SocketAddr, + shutdown: TaskClient, + shared_data: SharedData, +} + +impl Listener { + pub(crate) fn new(bind_address: SocketAddr, shared_data: SharedData) -> Self { + Listener { + bind_address, + shutdown: shared_data.task_client.fork("socket-listener"), + shared_data, + } + } + + pub(crate) async fn run(&mut self) { + info!("attempting to run mixnet listener on {}", self.bind_address); + + let tcp_listener = match tokio::net::TcpListener::bind(self.bind_address).await { + Ok(listener) => listener, + Err(err) => { + error!("Failed to bind to {}: {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.bind_address); + + // that's a bit gnarly, but we need to make sure we trigger shutdown + let mut shutdown_bomb = self.shutdown.fork("shutdown-bomb"); + shutdown_bomb.rearm(); + drop(shutdown_bomb); + return; + } + }; + + while !self.shutdown.is_shutdown() { + tokio::select! { + biased; + _ = self.shutdown.recv() => { + trace!("mixnet listener: received shutdown"); + } + connection = tcp_listener.accept() => { + self.shared_data.try_handle_connection(connection); + } + } + } + } + + pub(crate) fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { self.run().await }) + } +} diff --git a/nym-node/src/node/mixnet/mod.rs b/nym-node/src/node/mixnet/mod.rs new file mode 100644 index 0000000000..b4f9f7e4f6 --- /dev/null +++ b/nym-node/src/node/mixnet/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub(crate) mod handler; +pub(crate) mod listener; +pub(crate) mod packet_forwarding; +pub(crate) mod shared; + +pub(crate) use listener::Listener; +pub(crate) use shared::{final_hop::SharedFinalHopData, SharedData}; diff --git a/nym-node/src/node/mixnet/packet_forwarding.rs b/nym-node/src/node/mixnet/packet_forwarding.rs new file mode 100644 index 0000000000..bcd51a52a9 --- /dev/null +++ b/nym-node/src/node/mixnet/packet_forwarding.rs @@ -0,0 +1,143 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use futures::StreamExt; +use nym_mixnet_client::forwarder::{ + mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, PacketToForward, +}; +use nym_mixnet_client::SendWithoutResponse; +use nym_node_metrics::NymNodeMetrics; +use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue}; +use nym_sphinx_forwarding::packet::MixPacket; +use std::io; +use tokio::time::Instant; +use tracing::{debug, error, trace, warn}; + +pub struct PacketForwarder { + delay_queue: NonExhaustiveDelayQueue, + mixnet_client: C, + + metrics: NymNodeMetrics, + + packet_sender: MixForwardingSender, + packet_receiver: MixForwardingReceiver, + shutdown: nym_task::TaskClient, +} + +impl PacketForwarder { + pub fn new(client: C, metrics: NymNodeMetrics, shutdown: nym_task::TaskClient) -> Self { + let (packet_sender, packet_receiver) = mix_forwarding_channels(); + + PacketForwarder { + delay_queue: NonExhaustiveDelayQueue::new(), + mixnet_client: client, + metrics, + packet_sender, + packet_receiver, + shutdown, + } + } + + pub fn sender(&self) -> MixForwardingSender { + self.packet_sender.clone() + } + + fn forward_packet(&mut self, packet: MixPacket) + where + C: SendWithoutResponse, + { + let next_hop = packet.next_hop(); + let packet_type = packet.packet_type(); + let packet = packet.into_packet(); + + if let Err(err) = self + .mixnet_client + .send_without_response(next_hop, packet, packet_type) + { + if err.kind() == io::ErrorKind::WouldBlock { + // we only know for sure if we dropped a packet if our sending queue was full + // in any other case the connection might still be re-established (or created for the first time) + // and the packet might get sent, but we won't know about it + self.metrics + .mixnet + .egress_dropped_forward_packet(next_hop.into()) + } else if err.kind() == io::ErrorKind::NotConnected { + // let's give the benefit of the doubt and assume we manage to establish connection + self.metrics + .mixnet + .egress_sent_forward_packet(next_hop.into()) + } + } else { + self.metrics + .mixnet + .egress_sent_forward_packet(next_hop.into()) + } + } + + /// Upon packet being finished getting delayed, forward it to the mixnet. + fn handle_done_delaying(&mut self, packet: Expired) + where + C: SendWithoutResponse, + { + let delayed_packet = packet.into_inner(); + self.forward_packet(delayed_packet) + } + + fn handle_new_packet(&mut self, new_packet: PacketToForward) + where + C: SendWithoutResponse, + { + // in case of a zero delay packet, don't bother putting it in the delay queue, + // just forward it immediately + if let Some(instant) = new_packet.forward_delay_target { + // check if the delay has already expired, if so, don't bother putting it through + // the delay queue only to retrieve it immediately. Just forward it. + if instant.checked_duration_since(Instant::now()).is_none() { + self.forward_packet(new_packet.packet) + } else { + self.delay_queue.insert_at(new_packet.packet, instant); + } + } else { + self.forward_packet(new_packet.packet) + } + } + + pub async fn run(&mut self) + where + C: SendWithoutResponse, + { + let mut processed = 0; + trace!("starting PacketForwarder"); + loop { + tokio::select! { + biased; + _ = self.shutdown.recv() => { + debug!("PacketForwarder: Received shutdown"); + break; + } + delayed = self.delay_queue.next() => { + // SAFETY: `stream` implementation of `NonExhaustiveDelayQueue` never returns `None` + #[allow(clippy::unwrap_used)] + self.handle_done_delaying(delayed.unwrap()); + } + new_packet = self.packet_receiver.next() => { + // this one is impossible to ever panic - the struct itself contains a sender + // and hence it can't happen that ALL senders are dropped + #[allow(clippy::unwrap_used)] + self.handle_new_packet(new_packet.unwrap()); + if processed % 1000 == 0 { + let queue_len = self.packet_sender.len(); + match queue_len { + n if n > 200 => error!("there are currently {n} mix packets waiting to get forwarded!"), + n if n > 50 => warn!("there are currently {n} mix packets waiting to get forwarded"), + n => trace!("there are currently {n} mix packets waiting to get forwarded"), + } + } + + processed += 1; + } + } + } + trace!("PacketForwarder: Exiting"); + } +} diff --git a/nym-node/src/node/mixnet/shared/final_hop.rs b/nym-node/src/node/mixnet/shared/final_hop.rs new file mode 100644 index 0000000000..d38fc99d9f --- /dev/null +++ b/nym-node/src/node/mixnet/shared/final_hop.rs @@ -0,0 +1,51 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_gateway::node::{ActiveClientsStore, GatewayStorage, GatewayStorageError}; +use nym_sphinx_types::DestinationAddressBytes; +use tracing::debug; + +#[derive(Clone)] +pub(crate) struct SharedFinalHopData { + active_clients: ActiveClientsStore, + storage: GatewayStorage, +} + +impl SharedFinalHopData { + pub fn new(active_clients: ActiveClientsStore, storage: GatewayStorage) -> Self { + Self { + active_clients, + storage, + } + } + + pub(crate) fn try_push_message_to_client( + &self, + client_address: DestinationAddressBytes, + message: Vec, + ) -> Result<(), Vec> { + match self.active_clients.get_sender(client_address) { + None => Err(message), + Some(sender_channel) => { + if let Err(unsent) = sender_channel.unbounded_send(vec![message]) { + // the unwrap here is fine as the original message got returned; + // plus we're only ever sending 1 message at the time (for now) + #[allow(clippy::unwrap_used)] + Err(unsent.into_inner().pop().unwrap()) + } else { + Ok(()) + } + } + } + } + + pub(crate) async fn store_processed_packet_payload( + &self, + client_address: DestinationAddressBytes, + message: Vec, + ) -> Result<(), GatewayStorageError> { + debug!("Storing received message for {client_address} on the disk...",); + + self.storage.store_message(client_address, message).await + } +} diff --git a/nym-node/src/node/mixnet/shared/mod.rs b/nym-node/src/node/mixnet/shared/mod.rs new file mode 100644 index 0000000000..9387704a53 --- /dev/null +++ b/nym-node/src/node/mixnet/shared/mod.rs @@ -0,0 +1,181 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::Config; +use crate::node::mixnet::handler::ConnectionHandler; +use crate::node::mixnet::SharedFinalHopData; +use nym_crypto::asymmetric::x25519; +use nym_gateway::node::GatewayStorageError; +use nym_mixnet_client::forwarder::{MixForwardingSender, PacketToForward}; +use nym_node_metrics::NymNodeMetrics; +use nym_sphinx_forwarding::packet::MixPacket; +use nym_sphinx_framing::processing::{MixProcessingResult, PacketProcessingError}; +use nym_sphinx_types::DestinationAddressBytes; +use nym_task::TaskClient; +use std::io; +use std::net::{IpAddr, SocketAddr}; +use std::sync::Arc; +use std::time::Duration; +use tokio::net::TcpStream; +use tokio::task::JoinHandle; +use tokio::time::Instant; +use tracing::{debug, error}; + +pub(crate) mod final_hop; + +#[derive(Clone, Copy)] +pub(crate) struct ProcessingConfig { + pub(crate) maximum_packet_delay: Duration, + + pub(crate) forward_hop_processing_enabled: bool, + pub(crate) final_hop_processing_enabled: bool, +} + +impl ProcessingConfig { + pub(crate) fn new(config: &Config) -> Self { + ProcessingConfig { + maximum_packet_delay: config.mixnet.debug.maximum_forward_packet_delay, + forward_hop_processing_enabled: config.modes.mixnode, + final_hop_processing_enabled: config.modes.expects_final_hop_traffic() + || config.wireguard.enabled, + } + } +} + +// explicitly do NOT derive clone as we want to manually apply relevant suffixes to the task clients +// as well as immediately disarm them +pub(crate) struct SharedData { + pub(super) processing_config: ProcessingConfig, + // TODO: this type is not `Zeroize` : ( + pub(super) sphinx_key: Arc, + + // used for FORWARD mix packets and FINAL ack packets + pub(super) mixnet_forwarder: MixForwardingSender, + + // data specific to the final hop (gateway) processing + pub(super) final_hop: SharedFinalHopData, + + pub(super) metrics: NymNodeMetrics, + pub(super) task_client: TaskClient, +} + +impl SharedData { + pub(crate) fn new( + processing_config: ProcessingConfig, + x25519_key: &x25519::PrivateKey, + mixnet_forwarder: MixForwardingSender, + final_hop: SharedFinalHopData, + metrics: NymNodeMetrics, + task_client: TaskClient, + ) -> Self { + SharedData { + processing_config, + sphinx_key: Arc::new(x25519_key.into()), + mixnet_forwarder, + final_hop, + metrics, + task_client, + } + } + + pub(super) fn log_connected_clients(&self) { + debug!( + "there are currently {} connected clients on the mixnet socket", + self.metrics + .network + .active_ingress_mixnet_connections_count() + ) + } + + pub(super) fn dropped_forward_packet(&self, source: IpAddr) { + self.metrics.mixnet.ingress_dropped_forward_packet(source) + } + + pub(super) fn dropped_final_hop_packet(&self, source: IpAddr) { + self.metrics.mixnet.ingress_dropped_final_hop_packet(source) + } + + pub(super) fn update_metrics( + &self, + processing_result: &Result, + source: IpAddr, + ) { + match processing_result { + Err(_) => self.metrics.mixnet.ingress_malformed_packet(source), + Ok(MixProcessingResult::ForwardHop(_, delay)) => { + self.metrics.mixnet.ingress_received_forward_packet(source); + + // check if the delay wasn't excessive + if let Some(delay) = delay { + if delay.to_duration() > self.processing_config.maximum_packet_delay { + self.metrics.mixnet.ingress_excessive_delay_packet() + } + } + } + Ok(MixProcessingResult::FinalHop(_)) => { + self.metrics + .mixnet + .ingress_received_final_hop_packet(source); + } + } + } + + pub(super) fn try_handle_connection( + &self, + accepted: io::Result<(TcpStream, SocketAddr)>, + ) -> Option> { + match accepted { + Ok((socket, remote_addr)) => { + debug!("accepted incoming mixnet connection from: {remote_addr}"); + let mut handler = ConnectionHandler::new(self, socket, remote_addr); + let join_handle = tokio::spawn(async move { handler.handle_stream().await }); + self.log_connected_clients(); + Some(join_handle) + } + Err(err) => { + debug!("failed to accept incoming mixnet connection: {err}"); + None + } + } + } + + pub(super) fn forward_mix_packet(&self, packet: MixPacket, delay_until: Option) { + if self + .mixnet_forwarder + .forward_packet(PacketToForward::new(packet, delay_until)) + .is_err() + && !self.task_client.is_shutdown() + { + error!("failed to forward sphinx packet on the channel while the process is not going through the shutdown!"); + // this is a critical error, we're in uncharted lands, we have to shut down + let mut shutdown_bomb = self.task_client.fork("shutdown bomb"); + shutdown_bomb.rearm(); + drop(shutdown_bomb) + } + } + + pub(super) fn forward_ack_packet(&self, forward_ack: Option) { + if let Some(forward_ack) = forward_ack { + self.forward_mix_packet(forward_ack, None); + self.metrics.mixnet.egress_sent_ack(); + } + } + + pub(super) fn try_push_message_to_client( + &self, + client: DestinationAddressBytes, + message: Vec, + ) -> Result<(), Vec> { + self.final_hop.try_push_message_to_client(client, message) + } + + pub(crate) async fn store_processed_packet_payload( + &self, + client_address: DestinationAddressBytes, + message: Vec, + ) -> Result<(), GatewayStorageError> { + self.final_hop + .store_processed_packet_payload(client_address, message) + .await + } +} diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index bebefa0aef..1311bb4f57 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -1,116 +1,117 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use self::helpers::load_x25519_wireguard_keypair; +use crate::config::helpers::gateway_tasks_config; +use crate::config::{ + Config, GatewayTasksConfig, NodeModes, ServiceProvidersConfig, Wireguard, DEFAULT_MIXNET_PORT, +}; +use crate::error::{EntryGatewayError, NymNodeError, ServiceProvidersError}; use crate::node::description::{load_node_description, save_node_description}; use crate::node::helpers::{ load_ed25519_identity_keypair, load_key, load_x25519_noise_keypair, load_x25519_sphinx_keypair, store_ed25519_identity_keypair, store_key, store_keypair, store_x25519_noise_keypair, store_x25519_sphinx_keypair, DisplayDetails, }; -use crate::node::http::{sign_host_details, system_info::get_system_info}; -use nym_bin_common::{bin_info, bin_info_owned}; +use crate::node::http::api::api_requests; +use crate::node::http::helpers::sign_host_details; +use crate::node::http::helpers::system_info::get_system_info; +use crate::node::http::state::AppState; +use crate::node::http::{HttpServerConfig, NymNodeHttpServer, NymNodeRouter}; +use crate::node::metrics::aggregator::MetricsAggregator; +use crate::node::metrics::console_logger::ConsoleLogger; +use crate::node::metrics::handler::client_sessions::GatewaySessionStatsHandler; +use crate::node::metrics::handler::legacy_packet_data::LegacyMixingStatsUpdater; +use crate::node::metrics::handler::mixnet_data_cleaner::MixnetMetricsCleaner; +use crate::node::mixnet::packet_forwarding::PacketForwarder; +use crate::node::mixnet::shared::ProcessingConfig; +use crate::node::mixnet::SharedFinalHopData; +use crate::node::shared_topology::NymNodeTopologyProvider; +use nym_bin_common::bin_info; use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_gateway::Gateway; -use nym_mixnode::MixNode; +use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder}; +use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_requester::{ set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, GatewayDetails, GatewayRegistration, }; -use nym_node::config::entry_gateway::ephemeral_entry_gateway_config; -use nym_node::config::exit_gateway::ephemeral_exit_gateway_config; -use nym_node::config::mixnode::ephemeral_mixnode_config; -use nym_node::config::persistence::AuthenticatorPaths; -use nym_node::config::{ - Config, EntryGatewayConfig, ExitGatewayConfig, MixnodeConfig, NodeMode, Wireguard, -}; -use nym_node::error::{EntryGatewayError, ExitGatewayError, MixnodeError, NymNodeError}; -use nym_node_http_api::api::api_requests; -use nym_node_http_api::api::api_requests::v1::node::models::{AnnouncePorts, NodeDescription}; -use nym_node_http_api::state::metrics::{SharedMixingStats, SharedSessionStats, SharedVerlocStats}; -use nym_node_http_api::state::AppState; -use nym_node_http_api::{NymNodeHTTPServer, NymNodeRouter}; +use nym_node_metrics::events::MetricEventsSender; +use nym_node_metrics::NymNodeMetrics; +use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription}; use nym_sphinx_acknowledgements::AckKey; use nym_sphinx_addressing::Recipient; use nym_task::{TaskClient, TaskManager}; +use nym_topology::NetworkAddress; use nym_validator_client::client::NymApiClientExt; use nym_validator_client::models::NodeRefreshBody; -use nym_validator_client::NymApiClient; +use nym_validator_client::{NymApiClient, UserAgent}; +use nym_verloc::measurements::SharedVerlocStats; +use nym_verloc::{self, measurements::VerlocMeasurer}; use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData}; use rand::rngs::OsRng; use rand::{CryptoRng, RngCore}; +use std::net::SocketAddr; use std::path::Path; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; use tokio::time::timeout; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, info, trace, warn}; use zeroize::Zeroizing; -use self::helpers::load_x25519_wireguard_keypair; - pub mod bonding_information; pub mod description; pub mod helpers; pub(crate) mod http; +pub(crate) mod metrics; +pub(crate) mod mixnet; +mod shared_topology; -pub struct MixnodeData { - mixing_stats: SharedMixingStats, -} - -impl MixnodeData { - pub fn initialise(_config: &MixnodeConfig) -> Result<(), MixnodeError> { - Ok(()) - } - - fn new(_config: &MixnodeConfig) -> Result { - Ok(MixnodeData { - mixing_stats: SharedMixingStats::new(), - }) - } -} - -pub struct EntryGatewayData { - mnemonic: Zeroizing, - client_storage: nym_gateway::node::PersistentStorage, +pub struct GatewayTasksData { + mnemonic: Arc>, + client_storage: nym_gateway::node::GatewayStorage, stats_storage: nym_gateway::node::PersistentStatsStorage, - sessions_stats: SharedSessionStats, } -impl EntryGatewayData { +impl GatewayTasksData { pub fn initialise( - config: &EntryGatewayConfig, + config: &GatewayTasksConfig, custom_mnemonic: Option>, ) -> Result<(), EntryGatewayError> { // SAFETY: // this unwrap is fine as 24 word count is a valid argument for generating entropy for a new bip39 mnemonic #[allow(clippy::unwrap_used)] - let mnemonic = custom_mnemonic - .unwrap_or_else(|| Zeroizing::new(bip39::Mnemonic::generate(24).unwrap())); + let mnemonic = Arc::new( + custom_mnemonic + .unwrap_or_else(|| Zeroizing::new(bip39::Mnemonic::generate(24).unwrap())), + ); config.storage_paths.save_mnemonic_to_file(&mnemonic)?; Ok(()) } - async fn new(config: &EntryGatewayConfig) -> Result { - Ok(EntryGatewayData { - mnemonic: config.storage_paths.load_mnemonic_from_file()?, - client_storage: nym_gateway::node::PersistentStorage::init( - &config.storage_paths.clients_storage, - config.debug.message_retrieval_limit, - ) - .await - .map_err(nym_gateway::GatewayError::from)?, - stats_storage: nym_gateway::node::PersistentStatsStorage::init( - &config.storage_paths.stats_storage, - ) - .await - .map_err(nym_gateway::GatewayError::from)?, - sessions_stats: SharedSessionStats::new(), + async fn new(config: &GatewayTasksConfig) -> Result { + let client_storage = nym_gateway::node::GatewayStorage::init( + &config.storage_paths.clients_storage, + config.debug.message_retrieval_limit, + ) + .await + .map_err(nym_gateway::GatewayError::from)?; + + let stats_storage = + nym_gateway::node::PersistentStatsStorage::init(&config.storage_paths.stats_storage) + .await + .map_err(nym_gateway::GatewayError::from)?; + + Ok(GatewayTasksData { + mnemonic: Arc::new(config.storage_paths.load_mnemonic_from_file()?), + client_storage, + stats_storage, }) } } -pub struct ExitGatewayData { +pub struct ServiceProvidersData { // ideally we'd be storing all the keys here, but unfortunately due to how the service providers // are currently implemented, they will be loading the data themselves from the provided paths @@ -121,21 +122,19 @@ pub struct ExitGatewayData { ipr_ed25519: ed25519::PublicKey, ipr_x25519: x25519::PublicKey, + // TODO: those should be moved to WG section auth_ed25519: ed25519::PublicKey, auth_x25519: x25519::PublicKey, - - client_storage: nym_gateway::node::PersistentStorage, - stats_storage: nym_gateway::node::PersistentStatsStorage, } -impl ExitGatewayData { +impl ServiceProvidersData { fn initialise_client_keys( rng: &mut R, typ: &str, ed25519_paths: nym_pemstore::KeyPairPath, x25519_paths: nym_pemstore::KeyPairPath, ack_key_path: &Path, - ) -> Result<(), ExitGatewayError> { + ) -> Result<(), ServiceProvidersError> { let ed25519_keys = ed25519::KeyPair::new(rng); let x25519_keys = x25519::KeyPair::new(rng); let aes128ctr_key = AckKey::new(rng); @@ -154,7 +153,7 @@ impl ExitGatewayData { async fn initialise_client_gateway_storage( storage_path: &Path, registration: &GatewayRegistration, - ) -> Result<(), ExitGatewayError> { + ) -> Result<(), ServiceProvidersError> { // insert all required information into the gateways store // (I hate that we have to do it, but that's currently the simplest thing to do) let storage = setup_fs_gateways_storage(storage_path).await?; @@ -165,9 +164,9 @@ impl ExitGatewayData { pub async fn initialise_network_requester( rng: &mut R, - config: &ExitGatewayConfig, + config: &ServiceProvidersConfig, registration: &GatewayRegistration, - ) -> Result<(), ExitGatewayError> { + ) -> Result<(), ServiceProvidersError> { trace!("initialising network requester keys"); Self::initialise_client_keys( rng, @@ -191,9 +190,9 @@ impl ExitGatewayData { pub async fn initialise_ip_packet_router_requester( rng: &mut R, - config: &ExitGatewayConfig, + config: &ServiceProvidersConfig, registration: &GatewayRegistration, - ) -> Result<(), ExitGatewayError> { + ) -> Result<(), ServiceProvidersError> { trace!("initialising ip packet router keys"); Self::initialise_client_keys( rng, @@ -215,10 +214,37 @@ impl ExitGatewayData { .await } + pub async fn initialise_authenticator( + rng: &mut R, + config: &ServiceProvidersConfig, + registration: &GatewayRegistration, + ) -> Result<(), ServiceProvidersError> { + trace!("initialising authenticator keys"); + Self::initialise_client_keys( + rng, + "authenticator", + config + .storage_paths + .authenticator + .ed25519_identity_storage_paths(), + config + .storage_paths + .authenticator + .x25519_diffie_hellman_storage_paths(), + &config.storage_paths.authenticator.ack_key_file, + )?; + Self::initialise_client_gateway_storage( + &config.storage_paths.authenticator.gateway_registrations, + registration, + ) + .await?; + Ok(()) + } + pub async fn initialise( - config: &ExitGatewayConfig, + config: &ServiceProvidersConfig, public_key: ed25519::PublicKey, - ) -> Result<(), ExitGatewayError> { + ) -> Result<(), ServiceProvidersError> { // generate all the keys for NR, IPR and AUTH let mut rng = OsRng; @@ -230,10 +256,13 @@ impl ExitGatewayData { // IPR: Self::initialise_ip_packet_router_requester(&mut rng, config, &gateway_details).await?; + // Authenticator + Self::initialise_authenticator(&mut rng, config, &gateway_details).await?; + Ok(()) } - async fn new(config: &ExitGatewayConfig) -> Result { + fn new(config: &ServiceProvidersConfig) -> Result { let nr_paths = &config.storage_paths.network_requester; let nr_ed25519 = load_key( &nr_paths.public_ed25519_identity_key_file, @@ -267,27 +296,13 @@ impl ExitGatewayData { "authenticator x25519", )?; - let client_storage = nym_gateway::node::PersistentStorage::init( - &config.storage_paths.clients_storage, - config.debug.message_retrieval_limit, - ) - .await - .map_err(nym_gateway::GatewayError::from)?; - - let stats_storage = - nym_gateway::node::PersistentStatsStorage::init(&config.storage_paths.stats_storage) - .await - .map_err(nym_gateway::GatewayError::from)?; - - Ok(ExitGatewayData { + Ok(ServiceProvidersData { nr_ed25519, nr_x25519, ipr_ed25519, ipr_x25519, auth_ed25519, auth_x25519, - client_storage, - stats_storage, }) } } @@ -308,7 +323,7 @@ impl WireguardData { Ok(WireguardData { inner, peer_rx }) } - pub(crate) fn initialise(config: &Wireguard) -> Result<(), ExitGatewayError> { + pub(crate) fn initialise(config: &Wireguard) -> Result<(), ServiceProvidersError> { let mut rng = OsRng; let x25519_keys = x25519::KeyPair::new(&mut rng); @@ -337,18 +352,16 @@ pub(crate) struct NymNode { description: NodeDescription, - // TODO: currently we're only making measurements in 'mixnode' mode; this should be changed + metrics: NymNodeMetrics, + verloc_stats: SharedVerlocStats, - #[allow(dead_code)] - mixnode: MixnodeData, - - entry_gateway: EntryGatewayData, + entry_gateway: GatewayTasksData, #[allow(dead_code)] - exit_gateway: ExitGatewayData, + service_providers: ServiceProvidersData, - wireguard: WireguardData, + wireguard: Option, ed25519_identity_keys: Arc, x25519_sphinx_keys: Arc, @@ -359,57 +372,6 @@ pub(crate) struct NymNode { } impl NymNode { - fn initialise_client_keys( - rng: &mut R, - typ: &str, - ed25519_paths: nym_pemstore::KeyPairPath, - x25519_paths: nym_pemstore::KeyPairPath, - ack_key_path: &Path, - ) -> Result<(), EntryGatewayError> { - let ed25519_keys = ed25519::KeyPair::new(rng); - let x25519_keys = x25519::KeyPair::new(rng); - let aes128ctr_key = AckKey::new(rng); - - store_keypair( - &ed25519_keys, - ed25519_paths, - format!("{typ}-ed25519-identity"), - )?; - store_keypair(&x25519_keys, x25519_paths, format!("{typ}-x25519-dh"))?; - store_key(&aes128ctr_key, ack_key_path, format!("{typ}-ack-key"))?; - - Ok(()) - } - - async fn initialise_client_gateway_storage( - storage_path: &Path, - registration: &GatewayRegistration, - ) -> Result<(), EntryGatewayError> { - // insert all required information into the gateways store - // (I hate that we have to do it, but that's currently the simplest thing to do) - let storage = setup_fs_gateways_storage(storage_path).await?; - store_gateway_details(&storage, registration).await?; - set_active_gateway(&storage, ®istration.gateway_id().to_base58_string()).await?; - Ok(()) - } - - pub async fn initialise_authenticator( - rng: &mut R, - paths: &AuthenticatorPaths, - registration: &GatewayRegistration, - ) -> Result<(), NymNodeError> { - trace!("initialising authenticator keys"); - Self::initialise_client_keys( - rng, - "authenticator", - paths.ed25519_identity_storage_paths(), - paths.x25519_diffie_hellman_storage_paths(), - &paths.ack_key_file, - )?; - Self::initialise_client_gateway_storage(&paths.gateway_registrations, registration).await?; - Ok(()) - } - pub(crate) async fn initialise( config: &Config, custom_mnemonic: Option>, @@ -446,24 +408,13 @@ impl NymNode { &NodeDescription::default(), )?; - // mixnode initialisation - MixnodeData::initialise(&config.mixnode)?; - // entry gateway initialisation - EntryGatewayData::initialise(&config.entry_gateway, custom_mnemonic)?; + GatewayTasksData::initialise(&config.gateway_tasks, custom_mnemonic)?; - // exit gateway initialisation - ExitGatewayData::initialise(&config.exit_gateway, *ed25519_identity_keys.public_key()) - .await?; - - // authenticator initialization: - Self::initialise_authenticator( - &mut rng, - &config.entry_gateway.storage_paths.authenticator, - &GatewayDetails::Custom(CustomGatewayDetails::new( - *ed25519_identity_keys.public_key(), - )) - .into(), + // service providers initialisation + ServiceProvidersData::initialise( + &config.service_providers, + *ed25519_identity_keys.public_key(), ) .await?; @@ -475,6 +426,7 @@ impl NymNode { pub(crate) async fn new(config: Config) -> Result { let wireguard_data = WireguardData::new(&config.wireguard)?; + Ok(NymNode { ed25519_identity_keys: Arc::new(load_ed25519_identity_keypair( config.storage_paths.keys.ed25519_identity_storage_paths(), @@ -486,16 +438,20 @@ impl NymNode { config.storage_paths.keys.x25519_noise_storage_paths(), )?), description: load_node_description(&config.storage_paths.description)?, + metrics: NymNodeMetrics::new(), verloc_stats: Default::default(), - mixnode: MixnodeData::new(&config.mixnode)?, - entry_gateway: EntryGatewayData::new(&config.entry_gateway).await?, - exit_gateway: ExitGatewayData::new(&config.exit_gateway).await?, - wireguard: wireguard_data, + entry_gateway: GatewayTasksData::new(&config.gateway_tasks).await?, + service_providers: ServiceProvidersData::new(&config.service_providers)?, + wireguard: Some(wireguard_data), config, accepted_operator_terms_and_conditions: false, }) } + pub(crate) fn config(&self) -> &Config { + &self.config + } + pub(crate) fn with_accepted_operator_terms_and_conditions( mut self, accepted_operator_terms_and_conditions: bool, @@ -506,48 +462,53 @@ impl NymNode { fn exit_network_requester_address(&self) -> Recipient { Recipient::new( - self.exit_gateway.nr_ed25519, - self.exit_gateway.nr_x25519, + self.service_providers.nr_ed25519, + self.service_providers.nr_x25519, *self.ed25519_identity_keys.public_key(), ) } fn exit_ip_packet_router_address(&self) -> Recipient { Recipient::new( - self.exit_gateway.ipr_ed25519, - self.exit_gateway.ipr_x25519, + self.service_providers.ipr_ed25519, + self.service_providers.ipr_x25519, *self.ed25519_identity_keys.public_key(), ) } fn exit_authenticator_address(&self) -> Recipient { Recipient::new( - self.exit_gateway.auth_ed25519, - self.exit_gateway.auth_x25519, + self.service_providers.auth_ed25519, + self.service_providers.auth_x25519, *self.ed25519_identity_keys.public_key(), ) } - fn x25519_wireguard_key(&self) -> &x25519::PublicKey { - self.wireguard.inner.keypair().public_key() + fn x25519_wireguard_key(&self) -> Result { + let wg_data = self + .wireguard + .as_ref() + .ok_or(NymNodeError::WireguardDataUnavailable)?; + + Ok(*wg_data.inner.keypair().public_key()) } - pub(crate) fn display_details(&self) -> DisplayDetails { - DisplayDetails { - current_mode: self.config.mode, + pub(crate) fn display_details(&self) -> Result { + Ok(DisplayDetails { + current_modes: self.config.modes, description: self.description.clone(), ed25519_identity_key: self.ed25519_identity_key().to_base58_string(), x25519_sphinx_key: self.x25519_sphinx_key().to_base58_string(), x25519_noise_key: self.x25519_noise_key().to_base58_string(), - x25519_wireguard_key: self.x25519_wireguard_key().to_base58_string(), + x25519_wireguard_key: self.x25519_wireguard_key()?.to_base58_string(), exit_network_requester_address: self.exit_network_requester_address().to_string(), exit_ip_packet_router_address: self.exit_ip_packet_router_address().to_string(), exit_authenticator_address: self.exit_authenticator_address().to_string(), - } + }) } - pub(crate) fn mode(&self) -> NodeMode { - self.config.mode + pub(crate) fn modes(&self) -> NodeModes { + self.config.modes } pub(crate) fn ed25519_identity_key(&self) -> &ed25519::PublicKey { @@ -562,85 +523,140 @@ impl NymNode { self.x25519_noise_keys.public_key() } - fn start_mixnode(self, task_client: TaskClient) -> Result<(), NymNodeError> { - info!("going to start the nym-node in MIXNODE mode"); - - let config = ephemeral_mixnode_config(self.config.clone())?; - let mut mixnode = MixNode::new_loaded( - config, - self.ed25519_identity_keys.clone(), - self.x25519_sphinx_keys.clone(), - ); - mixnode.set_task_client(task_client); - mixnode.set_mixing_stats(self.mixnode.mixing_stats.clone()); - mixnode.set_verloc_stats(self.verloc_stats.clone()); - - tokio::spawn(async move { mixnode.run().await }); - Ok(()) + // the reason it's here as opposed to in the gateway directly, + // is that other nym-node tasks will also eventually need it + // (such as the ones for obtaining noise keys of other nodes) + fn build_topology_provider(&self) -> Result { + Ok(NymNodeTopologyProvider::new( + self.as_gateway_topology_node()?, + self.config.debug.topology_cache_ttl, + self.user_agent(), + self.config.mixnet.nym_api_urls.clone(), + )) } - fn start_entry_gateway(self, task_client: TaskClient) -> Result<(), NymNodeError> { - info!("going to start the nym-node in ENTRY GATEWAY mode"); + fn as_gateway_topology_node(&self) -> Result { + let Some(ip) = self.config.host.public_ips.first() else { + return Err(NymNodeError::NoPublicIps); + }; - let config = - ephemeral_entry_gateway_config(self.config.clone(), &self.entry_gateway.mnemonic)?; - let mut entry_gateway = Gateway::new_loaded( + let mix_port = self + .config + .mixnet + .announce_port + .unwrap_or(DEFAULT_MIXNET_PORT); + let mix_host = SocketAddr::new(*ip, mix_port); + + let clients_ws_port = self + .config + .gateway_tasks + .announce_ws_port + .unwrap_or(self.config.gateway_tasks.bind_address.port()); + + Ok(nym_topology::gateway::LegacyNode { + node_id: u32::MAX, + mix_host, + host: NetworkAddress::IpAddr(*ip), + clients_ws_port, + clients_wss_port: self.config.gateway_tasks.announce_wss_port, + sphinx_key: *self.x25519_sphinx_key(), + identity_key: *self.ed25519_identity_key(), + version: env!("CARGO_PKG_VERSION").into(), + }) + } + + async fn start_gateway_tasks( + &mut self, + metrics_sender: MetricEventsSender, + active_clients_store: ActiveClientsStore, + mix_packet_sender: MixForwardingSender, + task_client: TaskClient, + ) -> Result<(), NymNodeError> { + let config = gateway_tasks_config(&self.config); + let topology_provider = Box::new(self.build_topology_provider()?); + + let mut gateway_tasks_builder = GatewayTasksBuilder::new( config.gateway, - config.nr_opts, - config.ipr_opts, - Some(config.auth_opts), self.ed25519_identity_keys.clone(), - self.x25519_sphinx_keys.clone(), self.entry_gateway.client_storage.clone(), - bin_info!().into(), - self.entry_gateway.stats_storage.clone(), + mix_packet_sender, + metrics_sender, + self.entry_gateway.mnemonic.clone(), + task_client, ); - entry_gateway.set_task_client(task_client); - entry_gateway.set_session_stats(self.entry_gateway.sessions_stats.clone()); - if self.config.wireguard.enabled { - entry_gateway.set_wireguard_data(self.wireguard.into()); + + // if we're running in entry mode, start the websocket + if self.modes().entry { + info!( + "starting the clients websocket... on {}", + self.config.gateway_tasks.bind_address + ); + let websocket = gateway_tasks_builder + .build_websocket_listener(active_clients_store.clone()) + .await?; + websocket.start(); + } else { + info!("node not running in entry mode: the websocket will remain closed"); + } + + // if we're running in exit mode, start the IPR and NR + if self.modes().exit { + info!("starting the exit service providers: NR + IPR"); + gateway_tasks_builder.set_network_requester_opts(config.nr_opts); + gateway_tasks_builder.set_ip_packet_router_opts(config.ipr_opts); + + let exit_sps = gateway_tasks_builder.build_exit_service_providers( + topology_provider.clone(), + topology_provider.clone(), + )?; + + // note, this has all the joinhandles for when we want to use joinset + let (started_nr, started_ipr) = exit_sps.start_service_providers().await?; + active_clients_store.insert_embedded(started_nr.handle); + active_clients_store.insert_embedded(started_ipr.handle); + info!("started NR at: {}", started_nr.on_start_data.address); + info!("started IPR at: {}", started_ipr.on_start_data.address); + } else { + info!("node not running in exit mode: the exit service providers (NR + IPR) will remain unavailable"); + } + + // if we're running wireguard, start the authenticator + // and the actual wireguard listener + if self.config.wireguard.enabled { + info!("starting the wireguard tasks: authenticator service provider + wireguard peer controller"); + + gateway_tasks_builder.set_authenticator_opts(config.auth_opts); + + // that's incredibly nasty, but unfortunately to change it, would require some refactoring... + let Some(wg_data) = self.wireguard.take() else { + return Err(NymNodeError::WireguardDataUnavailable); + }; + + gateway_tasks_builder.set_wireguard_data(wg_data.into()); + + let authenticator = gateway_tasks_builder + .build_wireguard_authenticator(topology_provider) + .await?; + let started_authenticator = authenticator.start_service_provider().await?; + active_clients_store.insert_embedded(started_authenticator.handle); + + info!( + "started authenticator at: {}", + started_authenticator.on_start_data.address + ); + + gateway_tasks_builder + .try_start_wireguard() + .await + .map_err(NymNodeError::GatewayTasksStartupFailure)?; + } else { + info!("node not running with wireguard: authenticator service provider and wireguard will remain unavailable"); } - tokio::spawn(async move { - if let Err(err) = entry_gateway.run().await { - error!("the entry gateway subtask has failed with the following message: {err}") - } - }); Ok(()) } - fn start_exit_gateway(self, task_client: TaskClient) -> Result<(), NymNodeError> { - info!("going to start the nym-node in EXIT GATEWAY mode"); - - let config = - ephemeral_exit_gateway_config(self.config.clone(), &self.entry_gateway.mnemonic)?; - - let mut exit_gateway = Gateway::new_loaded( - config.gateway, - config.nr_opts, - config.ipr_opts, - Some(config.auth_opts), - self.ed25519_identity_keys.clone(), - self.x25519_sphinx_keys.clone(), - self.exit_gateway.client_storage.clone(), - bin_info!().into(), - self.exit_gateway.stats_storage.clone(), - ); - exit_gateway.set_task_client(task_client); - exit_gateway.set_session_stats(self.entry_gateway.sessions_stats.clone()); //Weird naming I'll give you that, but Andrew is gonna rework it anyway - if self.config.wireguard.enabled { - exit_gateway.set_wireguard_data(self.wireguard.into()); - } - - tokio::spawn(async move { - if let Err(err) = exit_gateway.run().await { - error!("the exit gateway subtask has failed with the following message: {err}") - } - }); - Ok(()) - } - - pub(crate) async fn build_http_server(&self) -> Result { + pub(crate) async fn build_http_server(&self) -> Result { let host_details = sign_host_details( &self.config, self.x25519_sphinx_keys.public_key(), @@ -651,7 +667,7 @@ impl NymNode { let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails { location: self.config.host.location, announce_ports: AnnouncePorts { - verloc_port: self.config.mixnode.verloc.announce_port, + verloc_port: self.config.verloc.announce_port, mix_port: self.config.mixnet.announce_port, }, accepted_operator_terms_and_conditions: self.accepted_operator_terms_and_conditions, @@ -664,7 +680,7 @@ impl NymNode { let wireguard = if self.config.wireguard.enabled { Some(api_requests::v1::gateway::models::Wireguard { port: self.config.wireguard.announced_port, - public_key: self.wireguard.inner.keypair().public_key().to_string(), + public_key: self.x25519_wireguard_key()?.to_string(), }) } else { None @@ -672,13 +688,13 @@ impl NymNode { let mixnet_websockets = Some(api_requests::v1::gateway::models::WebSockets { ws_port: self .config - .entry_gateway + .gateway_tasks .announce_ws_port - .unwrap_or(self.config.entry_gateway.bind_address.port()), - wss_port: self.config.entry_gateway.announce_wss_port, + .unwrap_or(self.config.gateway_tasks.bind_address.port()), + wss_port: self.config.gateway_tasks.announce_wss_port, }); let gateway_details = api_requests::v1::gateway::models::Gateway { - enforces_zk_nyms: self.config.entry_gateway.enforce_zk_nyms, + enforces_zk_nyms: self.config.gateway_tasks.enforce_zk_nyms, client_interfaces: api_requests::v1::gateway::models::ClientInterfaces { wireguard, mixnet_websockets, @@ -687,20 +703,20 @@ impl NymNode { // exit gateway info let nr_details = api_requests::v1::network_requester::models::NetworkRequester { - encoded_identity_key: self.exit_gateway.nr_ed25519.to_base58_string(), - encoded_x25519_key: self.exit_gateway.nr_x25519.to_base58_string(), + encoded_identity_key: self.service_providers.nr_ed25519.to_base58_string(), + encoded_x25519_key: self.service_providers.nr_x25519.to_base58_string(), address: self.exit_network_requester_address().to_string(), }; let ipr_details = api_requests::v1::ip_packet_router::models::IpPacketRouter { - encoded_identity_key: self.exit_gateway.ipr_ed25519.to_base58_string(), - encoded_x25519_key: self.exit_gateway.ipr_x25519.to_base58_string(), + encoded_identity_key: self.service_providers.ipr_ed25519.to_base58_string(), + encoded_x25519_key: self.service_providers.ipr_x25519.to_base58_string(), address: self.exit_ip_packet_router_address().to_string(), }; let auth_details = api_requests::v1::authenticator::models::Authenticator { - encoded_identity_key: self.exit_gateway.auth_ed25519.to_base58_string(), - encoded_x25519_key: self.exit_gateway.auth_x25519.to_base58_string(), + encoded_identity_key: self.service_providers.auth_ed25519.to_base58_string(), + encoded_x25519_key: self.service_providers.auth_x25519.to_base58_string(), address: self.exit_authenticator_address().to_string(), }; @@ -709,7 +725,7 @@ impl NymNode { enabled: true, upstream_source: self .config - .exit_gateway + .service_providers .upstream_exit_policy_url .to_string(), last_updated: 0, @@ -717,7 +733,7 @@ impl NymNode { policy: None, }; - let mut config = nym_node_http_api::Config::new(bin_info_owned!(), host_details) + let mut config = HttpServerConfig::new(host_details) .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) .with_mixnode_details(mixnode_details) .with_gateway_details(gateway_details) @@ -734,29 +750,33 @@ impl NymNode { self.config.http.expose_crypto_hardware, )) } - match self.config.mode { - NodeMode::Mixnode => config.api.v1_config.node.roles.mixnode_enabled = true, - NodeMode::EntryGateway => config.api.v1_config.node.roles.gateway_enabled = true, - NodeMode::ExitGateway => { - config.api.v1_config.node.roles.gateway_enabled = true; - config.api.v1_config.node.roles.network_requester_enabled = true; - config.api.v1_config.node.roles.ip_packet_router_enabled = true; - } + if self.config.modes.mixnode { + config.api.v1_config.node.roles.mixnode_enabled = true; } - let app_state = AppState::new() - .with_mixing_stats(self.mixnode.mixing_stats.clone()) - .with_sessions_stats(self.entry_gateway.sessions_stats.clone()) - .with_verloc_stats(self.verloc_stats.clone()) + if self.config.modes.entry { + config.api.v1_config.node.roles.gateway_enabled = true + } + + if self.config.modes.exit { + config.api.v1_config.node.roles.network_requester_enabled = true; + config.api.v1_config.node.roles.ip_packet_router_enabled = true; + } + + let app_state = AppState::new(self.metrics.clone(), self.verloc_stats.clone()) .with_metrics_key(self.config.http.access_token.clone()); - Ok(NymNodeRouter::new(config, Some(app_state)) + Ok(NymNodeRouter::new(config, app_state) .build_server(&self.config.http.bind_address) .await?) } + fn user_agent(&self) -> UserAgent { + bin_info!().into() + } + async fn try_refresh_remote_nym_api_cache(&self) { - info!("attempting to request described cache request from nym-api..."); + info!("attempting to request described cache refresh from nym-api..."); if self.config.mixnet.nym_api_urls.is_empty() { warn!("no nym-api urls available"); return; @@ -764,7 +784,7 @@ impl NymNode { for nym_api in &self.config.mixnet.nym_api_urls { info!("trying {nym_api}..."); - let client = NymApiClient::new_with_user_agent(nym_api.clone(), bin_info_owned!()); + let client = NymApiClient::new_with_user_agent(nym_api.clone(), self.user_agent()); // make new request every time in case previous one takes longer and invalidates the signature let request = NodeRefreshBody::new(self.ed25519_identity_keys.private_key()); @@ -787,7 +807,154 @@ impl NymNode { } } - pub(crate) async fn run(self) -> Result<(), NymNodeError> { + pub(crate) fn start_verloc_measurements(&self, shutdown: TaskClient) { + info!( + "Starting the [verloc] round-trip-time measurer on {} ...", + self.config.verloc.bind_address + ); + + let mut base_agent = self.user_agent(); + base_agent.application = format!("{}-verloc", base_agent.application); + let config = nym_verloc::measurements::ConfigBuilder::new( + self.config.mixnet.nym_api_urls.clone(), + base_agent, + ) + .listening_address(self.config.verloc.bind_address) + .packets_per_node(self.config.verloc.debug.packets_per_node) + .connection_timeout(self.config.verloc.debug.connection_timeout) + .packet_timeout(self.config.verloc.debug.packet_timeout) + .delay_between_packets(self.config.verloc.debug.delay_between_packets) + .tested_nodes_batch_size(self.config.verloc.debug.tested_nodes_batch_size) + .testing_interval(self.config.verloc.debug.testing_interval) + .retry_timeout(self.config.verloc.debug.retry_timeout) + .build(); + + let mut verloc_measurer = + VerlocMeasurer::new(config, self.ed25519_identity_keys.clone(), shutdown); + verloc_measurer.set_shared_state(self.verloc_stats.clone()); + tokio::spawn(async move { verloc_measurer.run().await }); + } + + pub(crate) fn setup_metrics_backend(&self, shutdown: TaskClient) -> MetricEventsSender { + info!("setting up node metrics..."); + + // aggregator (to listen for any metrics events) + let mut metrics_aggregator = MetricsAggregator::new( + self.config.metrics.debug.aggregator_update_rate, + shutdown.fork("aggregator"), + ); + + // >>>> START: register all relevant handlers for custom events + + // legacy metrics updater on the deprecated endpoint + metrics_aggregator.register_handler( + LegacyMixingStatsUpdater::new(self.metrics.clone()), + self.config.metrics.debug.legacy_mixing_metrics_update_rate, + ); + + // stats for gateway client sessions (websocket-related information) + metrics_aggregator.register_handler( + GatewaySessionStatsHandler::new( + self.metrics.clone(), + self.entry_gateway.stats_storage.clone(), + ), + self.config.metrics.debug.clients_sessions_update_rate, + ); + + // handler for periodically cleaning up stale recipient/sender darta + metrics_aggregator.register_handler( + MixnetMetricsCleaner::new(self.metrics.clone()), + self.config.metrics.debug.stale_mixnet_metrics_cleaner_rate, + ); + + // note: we're still measuring things such as number of mixed packets, + // but since they're stored as atomic integers, they are incremented directly at source + // rather than going through event pipeline + // should we need custom mixnet events, we can add additional handler for that. that's not a problem + + // >>>> END: register all relevant handlers + + // console logger to preserve old mixnode functionalities + // if self.config.logging.debug.log_to_console { + if self.config.metrics.debug.log_stats_to_console { + ConsoleLogger::new( + self.config.metrics.debug.console_logging_update_interval, + self.metrics.clone(), + shutdown.named("metrics-console-logger"), + ) + .start(); + } else { + let mut shutdown = shutdown; + shutdown.disarm() + } + + let events_sender = metrics_aggregator.sender(); + + // spawn the aggregator task + metrics_aggregator.start(); + + events_sender + } + + pub(crate) fn start_mixnet_listener( + &self, + active_clients_store: &ActiveClientsStore, + shutdown: TaskClient, + ) -> MixForwardingSender { + let processing_config = ProcessingConfig::new(&self.config); + + // we're ALWAYS listening for mixnet packets, either for forward or final hops (or both) + info!( + "Starting the mixnet listener... on {} (forward: {}, final hop: {}))", + self.config.mixnet.bind_address, + processing_config.forward_hop_processing_enabled, + processing_config.final_hop_processing_enabled + ); + + let mixnet_client_config = nym_mixnet_client::Config::new( + self.config.mixnet.debug.packet_forwarding_initial_backoff, + self.config.mixnet.debug.packet_forwarding_maximum_backoff, + self.config.mixnet.debug.initial_connection_timeout, + self.config.mixnet.debug.maximum_connection_buffer_size, + ); + let mixnet_client = nym_mixnet_client::Client::new(mixnet_client_config); + + let mut packet_forwarder = PacketForwarder::new( + mixnet_client, + self.metrics.clone(), + shutdown.fork("mix-packet-forwarder"), + ); + let mix_packet_sender = packet_forwarder.sender(); + tokio::spawn(async move { packet_forwarder.run().await }); + + let final_hop_data = SharedFinalHopData::new( + active_clients_store.clone(), + self.entry_gateway.client_storage.clone(), + ); + + let shared = mixnet::SharedData::new( + processing_config, + self.x25519_sphinx_keys.private_key(), + mix_packet_sender.clone(), + final_hop_data, + self.metrics.clone(), + shutdown, + ); + + mixnet::Listener::new(self.config.mixnet.bind_address, shared).start(); + mix_packet_sender + } + + pub(crate) async fn run(mut self) -> Result<(), NymNodeError> { + info!("starting Nym Node {} with the following modes: mixnode: {}, entry: {}, exit: {}, wireguard: {}", + self.ed25519_identity_key(), + self.config.modes.mixnode, + self.config.modes.entry, + self.config.modes.exit, + self.config.wireguard.enabled + ); + debug!("config: {:#?}", self.config); + let mut task_manager = TaskManager::default().named("NymNode"); let http_server = self .build_http_server() @@ -796,29 +963,32 @@ impl NymNode { let bind_address = self.config.http.bind_address; tokio::spawn(async move { { - info!("Started NymNodeHTTPServer on {bind_address}"); + info!("started NymNodeHTTPServer on {bind_address}"); http_server.run().await } }); self.try_refresh_remote_nym_api_cache().await; - match self.config.mode { - NodeMode::Mixnode => { - self.start_mixnode(task_manager.subscribe_named("mixnode"))?; - let _ = task_manager.catch_interrupt().await; - Ok(()) - } - NodeMode::EntryGateway => { - self.start_entry_gateway(task_manager.subscribe_named("entry-gateway"))?; - let _ = task_manager.catch_interrupt().await; - Ok(()) - } - NodeMode::ExitGateway => { - self.start_exit_gateway(task_manager.subscribe_named("exit-gateway"))?; - let _ = task_manager.catch_interrupt().await; - Ok(()) - } - } + self.start_verloc_measurements(task_manager.subscribe_named("verloc-measurements")); + + let metrics_sender = self.setup_metrics_backend(task_manager.subscribe_named("metrics")); + let active_clients_store = ActiveClientsStore::new(); + + let mix_packet_sender = self.start_mixnet_listener( + &active_clients_store, + task_manager.subscribe_named("mixnet-traffic"), + ); + + self.start_gateway_tasks( + metrics_sender, + active_clients_store, + mix_packet_sender, + task_manager.subscribe_named("gateway-tasks"), + ) + .await?; + + let _ = task_manager.catch_interrupt().await; + Ok(()) } } diff --git a/gateway/src/node/helpers.rs b/nym-node/src/node/shared_topology.rs similarity index 80% rename from gateway/src/node/helpers.rs rename to nym-node/src/node/shared_topology.rs index d87b02afed..3513791455 100644 --- a/gateway/src/node/helpers.rs +++ b/nym-node/src/node/shared_topology.rs @@ -1,8 +1,8 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use async_trait::async_trait; -use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; +use nym_gateway::node::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; use nym_topology::{gateway, NymTopology, TopologyProvider}; use std::sync::Arc; use std::time::Duration; @@ -11,20 +11,22 @@ use tokio::sync::Mutex; use tracing::debug; use url::Url; +// I wouldn't be surprised if this became the start of the node topology cache + #[derive(Clone)] -pub struct GatewayTopologyProvider { - inner: Arc>, +pub struct NymNodeTopologyProvider { + inner: Arc>, } -impl GatewayTopologyProvider { +impl NymNodeTopologyProvider { pub fn new( gateway_node: gateway::LegacyNode, cache_ttl: Duration, user_agent: UserAgent, nym_api_url: Vec, - ) -> GatewayTopologyProvider { - GatewayTopologyProvider { - inner: Arc::new(Mutex::new(GatewayTopologyProviderInner { + ) -> NymNodeTopologyProvider { + NymNodeTopologyProvider { + inner: Arc::new(Mutex::new(NymNodeTopologyProviderInner { inner: NymApiTopologyProvider::new( NymApiTopologyProviderConfig { min_mixnode_performance: 50, @@ -43,7 +45,7 @@ impl GatewayTopologyProvider { } } -struct GatewayTopologyProviderInner { +struct NymNodeTopologyProviderInner { inner: NymApiTopologyProvider, cache_ttl: Duration, cached_at: OffsetDateTime, @@ -51,7 +53,7 @@ struct GatewayTopologyProviderInner { gateway_node: gateway::LegacyNode, } -impl GatewayTopologyProviderInner { +impl NymNodeTopologyProviderInner { fn cached_topology(&self) -> Option { if let Some(cached_topology) = &self.cached { if self.cached_at + self.cache_ttl > OffsetDateTime::now_utc() { @@ -85,7 +87,7 @@ impl GatewayTopologyProviderInner { } #[async_trait] -impl TopologyProvider for GatewayTopologyProvider { +impl TopologyProvider for NymNodeTopologyProvider { async fn get_new_topology(&mut self) -> Option { let mut guard = self.inner.lock().await; // check the cache diff --git a/nym-node/src/wireguard/error.rs b/nym-node/src/wireguard/error.rs index 3682017e37..6a0cd7a09e 100644 --- a/nym-node/src/wireguard/error.rs +++ b/nym-node/src/wireguard/error.rs @@ -4,10 +4,4 @@ use thiserror::Error; #[derive(Debug, Error)] -pub enum WireguardError { - #[error("the client is currently not in the process of being registered")] - RegistrationNotInProgress, - - #[error("the client mac failed to get verified correctly")] - MacVerificationFailure, -} +pub enum WireguardError {} diff --git a/sdk/rust/nym-sdk/src/lib.rs b/sdk/rust/nym-sdk/src/lib.rs index 545090e3ae..5b8afd246c 100644 --- a/sdk/rust/nym-sdk/src/lib.rs +++ b/sdk/rust/nym-sdk/src/lib.rs @@ -10,12 +10,15 @@ pub mod mixnet; pub mod tcp_proxy; pub use error::{Error, Result}; -pub use nym_client_core::client::{ - mix_traffic::transceiver::*, - topology_control::{ - GeoAwareTopologyProvider, NymApiTopologyProvider, NymApiTopologyProviderConfig, - TopologyProvider, +pub use nym_client_core::{ + client::{ + mix_traffic::transceiver::*, + topology_control::{ + GeoAwareTopologyProvider, NymApiTopologyProvider, NymApiTopologyProviderConfig, + TopologyProvider, + }, }, + config::DebugConfig, }; pub use nym_network_defaults::{ ChainDetails, DenomDetails, DenomDetailsOwned, NymContracts, NymNetworkDetails, diff --git a/service-providers/authenticator/src/authenticator.rs b/service-providers/authenticator/src/authenticator.rs index 1023059d35..268d96c1ef 100644 --- a/service-providers/authenticator/src/authenticator.rs +++ b/service-providers/authenticator/src/authenticator.rs @@ -7,7 +7,6 @@ use futures::channel::oneshot; use ipnetwork::IpNetwork; use nym_client_core::{HardcodedTopologyProvider, TopologyProvider}; use nym_credential_verification::ecash::EcashManager; -use nym_gateway_storage::Storage; use nym_sdk::{mixnet::Recipient, GatewayTransceiver}; use nym_task::{TaskClient, TaskHandle}; use nym_wireguard::WireguardGatewayData; @@ -25,20 +24,20 @@ impl OnStartData { } } -pub struct Authenticator { +pub struct Authenticator { #[allow(unused)] config: Config, wait_for_gateway: bool, custom_topology_provider: Option>, custom_gateway_transceiver: Option>, wireguard_gateway_data: WireguardGatewayData, - ecash_verifier: Option>>, + ecash_verifier: Option>, used_private_network_ips: Vec, shutdown: Option, on_start: Option>, } -impl Authenticator { +impl Authenticator { pub fn new( config: Config, wireguard_gateway_data: WireguardGatewayData, @@ -59,7 +58,7 @@ impl Authenticator { #[must_use] #[allow(unused)] - pub fn with_ecash_verifier(mut self, ecash_verifier: Arc>) -> Self { + pub fn with_ecash_verifier(mut self, ecash_verifier: Arc) -> Self { self.ecash_verifier = Some(ecash_verifier); self } diff --git a/service-providers/authenticator/src/cli/run.rs b/service-providers/authenticator/src/cli/run.rs index 42f9e9ab0e..22dc152701 100644 --- a/service-providers/authenticator/src/cli/run.rs +++ b/service-providers/authenticator/src/cli/run.rs @@ -1,8 +1,6 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; - use crate::cli::peer_handler::DummyHandler; use crate::cli::{override_config, OverrideConfig}; use crate::cli::{try_load_current_config, version_check}; @@ -11,10 +9,10 @@ use log::error; use nym_authenticator::error::AuthenticatorError; use nym_client_core::cli_helpers::client_run::CommonClientRunArgs; use nym_crypto::asymmetric::x25519::KeyPair; -use nym_gateway_storage::PersistentStorage; use nym_task::TaskHandle; use nym_wireguard::WireguardGatewayData; use rand::rngs::OsRng; +use std::sync::Arc; #[allow(clippy::struct_excessive_bools)] #[derive(Args, Clone)] @@ -54,11 +52,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), AuthenticatorError> { handler.run().await; }); - let mut server = nym_authenticator::Authenticator::::new( - config, - wireguard_gateway_data, - vec![], - ); + let mut server = nym_authenticator::Authenticator::new(config, wireguard_gateway_data, vec![]); if let Some(custom_mixnet) = &args.common_args.custom_mixnet { server = server.with_stored_topology(custom_mixnet)? } diff --git a/service-providers/authenticator/src/error.rs b/service-providers/authenticator/src/error.rs index 2f272e4d16..a0a5afe496 100644 --- a/service-providers/authenticator/src/error.rs +++ b/service-providers/authenticator/src/error.rs @@ -45,7 +45,7 @@ pub enum AuthenticatorError { FailedToSetupMixnetClient { source: nym_sdk::Error }, #[error("{0}")] - GatewayStorageError(#[from] nym_gateway_storage::error::StorageError), + GatewayStorageError(#[from] nym_gateway_storage::error::GatewayStorageError), #[error("internal error: {0}")] InternalError(String), diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/service-providers/authenticator/src/mixnet_listener.rs index 8fbd3e2b29..ede47a18e4 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/service-providers/authenticator/src/mixnet_listener.rs @@ -27,7 +27,6 @@ use nym_credential_verification::{ use nym_credentials_interface::CredentialSpendingData; use nym_crypto::asymmetric::x25519::KeyPair; use nym_gateway_requests::models::CredentialSpendingRequest; -use nym_gateway_storage::Storage; use nym_sdk::mixnet::{InputMessage, MixnetMessageSender, Recipient, TransmissionLane}; use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; use nym_sphinx::receiver::ReconstructedMessage; @@ -55,7 +54,7 @@ impl RegistredAndFree { } } -pub(crate) struct MixnetListener { +pub(crate) struct MixnetListener { // The configuration for the mixnet listener pub(crate) config: Config, @@ -70,19 +69,19 @@ pub(crate) struct MixnetListener { pub(crate) peer_manager: PeerManager, - pub(crate) ecash_verifier: Option>>, + pub(crate) ecash_verifier: Option>, pub(crate) timeout_check_interval: IntervalStream, } -impl MixnetListener { +impl MixnetListener { pub fn new( config: Config, free_private_network_ips: PrivateIPs, wireguard_gateway_data: WireguardGatewayData, mixnet_client: nym_sdk::mixnet::MixnetClient, task_handle: TaskHandle, - ecash_verifier: Option>>, + ecash_verifier: Option>, ) -> Self { let timeout_check_interval = IntervalStream::new(tokio::time::interval(DEFAULT_REGISTRATION_TIMEOUT_CHECK)); @@ -519,7 +518,7 @@ impl MixnetListener { } async fn credential_verification( - ecash_verifier: Arc>, + ecash_verifier: Arc, credential: CredentialSpendingData, client_id: i64, ) -> Result { diff --git a/service-providers/ip-packet-router/src/ip_packet_router.rs b/service-providers/ip-packet-router/src/ip_packet_router.rs index e002559ca1..cff86d6fb0 100644 --- a/service-providers/ip-packet-router/src/ip_packet_router.rs +++ b/service-providers/ip-packet-router/src/ip_packet_router.rs @@ -119,7 +119,7 @@ impl IpPacketRouter { log::error!("ip packet router service provider is not yet supported on this platform"); Ok(()) } else { - todo!("service provider is not yet supported on this platform") + unimplemented!("service provider is not yet supported on this platform") } } diff --git a/tools/internal/mixnet-connectivity-check/Cargo.toml b/tools/internal/mixnet-connectivity-check/Cargo.toml new file mode 100644 index 0000000000..af55790aa4 --- /dev/null +++ b/tools/internal/mixnet-connectivity-check/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mixnet-connectivity-check" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +[dependencies] +anyhow = { workspace = true } +clap = { workspace = true, features = ["cargo", "derive"] } +futures = { workspace = true } +tracing = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "signal", "macros"] } + +nym-network-defaults = { path = "../../../common/network-defaults" } +nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing", "output_format"] } +nym-crypto = { path = "../../../common/crypto", features = ["asymmetric"] } +nym-sdk = { path = "../../../sdk/rust/nym-sdk" } diff --git a/tools/internal/mixnet-connectivity-check/src/main.rs b/tools/internal/mixnet-connectivity-check/src/main.rs new file mode 100644 index 0000000000..be66b4f443 --- /dev/null +++ b/tools/internal/mixnet-connectivity-check/src/main.rs @@ -0,0 +1,156 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use clap::{Args, Parser, Subcommand}; +use futures::stream::StreamExt; +use nym_bin_common::output_format::OutputFormat; +use nym_bin_common::{bin_info, bin_info_owned}; +use nym_crypto::asymmetric::ed25519; +use nym_network_defaults::setup_env; +use nym_sdk::mixnet::MixnetMessageSender; +use nym_sdk::{mixnet, DebugConfig}; +use std::sync::OnceLock; +use std::time::Duration; +use tokio::time::timeout; + +fn pretty_build_info_static() -> &'static str { + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) +} + +#[derive(Parser)] +#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] +pub(crate) struct Cli { + /// Path pointing to an env file that configures the client. + #[clap(short, long)] + pub(crate) config_env_file: Option, + + #[clap(subcommand)] + command: Commands, +} + +impl Cli { + async fn execute(self) -> anyhow::Result<()> { + match self.command { + Commands::CheckConnectivity(args) => connectivity_test(args).await?, + Commands::BuildInfo(args) => build_info(args), + } + Ok(()) + } +} + +#[derive(Subcommand)] +#[allow(clippy::large_enum_variant)] +pub(crate) enum Commands { + /// Attempt to run a simple connectivity test + CheckConnectivity(ConnectivityArgs), + + /// Show build information of this binary + BuildInfo(BuildInfoArgs), +} + +#[derive(Args, Clone, Debug)] +struct ConnectivityArgs { + #[clap(long)] + gateway: Option, + + #[clap(long)] + ignore_performance: bool, +} + +#[derive(clap::Args, Debug)] +pub(crate) struct BuildInfoArgs { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +fn build_info(args: BuildInfoArgs) { + println!("{}", args.output.format(&bin_info_owned!())) +} + +async fn connectivity_test(args: ConnectivityArgs) -> anyhow::Result<()> { + let env = mixnet::NymNetworkDetails::new_from_env(); + let mut debug_config = DebugConfig::default(); + debug_config.cover_traffic.disable_loop_cover_traffic_stream = true; + debug_config + .traffic + .disable_main_poisson_packet_distribution = true; + + if args.ignore_performance { + debug_config.topology.minimum_mixnode_performance = 0; + debug_config.topology.minimum_gateway_performance = 0; + }; + + let client_builder = mixnet::MixnetClientBuilder::new_ephemeral() + .network_details(env) + .debug_config(debug_config); + + let mixnet_client = if let Some(gateway) = args.gateway { + client_builder + .request_gateway(gateway.to_string()) + .build()? + } else { + client_builder.build()? + }; + + print!("connecting to mixnet... "); + let mut client = match mixnet_client.connect_to_mixnet().await { + Ok(client) => { + println!("✅"); + client + } + Err(err) => { + println!("❌"); + println!("failed to connect: {err}"); + return Err(err.into()); + } + }; + let our_address = client.nym_address(); + + println!("attempting to send a message to ourselves ({our_address})"); + + client + .send_plain_message(*our_address, "hello there") + .await?; + + print!("awaiting response... "); + + match timeout(Duration::from_secs(5), client.next()).await { + Err(_timeout) => { + println!("❌"); + println!("timed out while waiting for the response..."); + } + Ok(Some(received)) => match String::from_utf8(received.message) { + Ok(message) => { + println!("✅"); + println!("received '{message}' back!"); + } + Err(err) => { + println!("❌"); + println!("the received message got malformed on the way to us: {err}"); + } + }, + Ok(None) => { + println!("❌"); + println!("failed to receive any message back..."); + } + } + + println!("disconnecting the client before shutting down..."); + client.disconnect().await; + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // std::env::set_var( + // "RUST_LOG", + // "debug,handlebars=warn,tendermint_rpc=warn,h2=warn,hyper=warn,rustls=warn,reqwest=warn,tungstenite=warn,async_tungstenite=warn,tokio_util=warn,tokio_tungstenite=warn,tokio-util=warn", + // ); + + let args = Cli::parse(); + setup_env(args.config_env_file.as_ref()); + // setup_tracing_logger(); + + args.execute().await +} diff --git a/tools/internal/testnet-manager/src/manager/local_apis.rs b/tools/internal/testnet-manager/src/manager/local_apis.rs index 124b2a324f..8ad28e8743 100644 --- a/tools/internal/testnet-manager/src/manager/local_apis.rs +++ b/tools/internal/testnet-manager/src/manager/local_apis.rs @@ -168,7 +168,7 @@ impl NetworkManager { let id = ctx.signer_id(signer); cmds.push(format!( - "{bin_canon_display} -c {env_canon_display} run --id {id}" + "{bin_canon_display} -c {env_canon_display} run --id {id} --allow-illegal-ips" )); } Ok(RunCommands(cmds)) diff --git a/tools/internal/testnet-manager/src/manager/local_client.rs b/tools/internal/testnet-manager/src/manager/local_client.rs index fac5e6828a..ebf333d256 100644 --- a/tools/internal/testnet-manager/src/manager/local_client.rs +++ b/tools/internal/testnet-manager/src/manager/local_client.rs @@ -96,7 +96,7 @@ impl NetworkManager { let wait_fut = async { let inner_fut = async { loop { - let mut nodes = match api_client.get_all_basic_nodes(None).await { + let nodes = match api_client.get_all_basic_nodes(None).await { Ok(nodes) => nodes, Err(err) => { ctx.println(format!( @@ -121,9 +121,13 @@ impl NetworkManager { } // otherwise look for ANY node - if let Some(node) = nodes.pop() { - return SocketAddr::new(node.ip_addresses[0], node.entry.unwrap().ws_port); + if let Some(node) = nodes.iter().find(|n| n.supported_roles.entry) { + return SocketAddr::new( + node.ip_addresses[0], + node.entry.as_ref().unwrap().ws_port, + ); } + sleep(Duration::from_secs(10)).await; } }; diff --git a/tools/internal/testnet-manager/src/manager/local_nodes.rs b/tools/internal/testnet-manager/src/manager/local_nodes.rs index 75edc76b7d..792c67e8fe 100644 --- a/tools/internal/testnet-manager/src/manager/local_nodes.rs +++ b/tools/internal/testnet-manager/src/manager/local_nodes.rs @@ -7,6 +7,7 @@ use crate::manager::network::LoadedNetwork; use crate::manager::node::NymNode; use crate::manager::NetworkManager; use console::style; +use nym_crypto::asymmetric::ed25519; use nym_mixnet_contract_common::nym_node::Role; use nym_mixnet_contract_common::RoleAssignment; use nym_validator_client::nyxd::contract_traits::MixnetSigningClient; @@ -89,28 +90,10 @@ impl<'a> LocalNodesCtx<'a> { } } -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "node_type")] -pub enum BondingInformationV1 { - Mixnode(MixnodeBondingInformation), - Gateway(GatewayBondingInformation), -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct MixnodeBondingInformation { - pub(crate) version: String, - pub(crate) host: String, - pub(crate) identity_key: String, - pub(crate) sphinx_key: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GatewayBondingInformation { - pub(crate) version: String, - pub(crate) host: String, - pub(crate) location: String, - pub(crate) identity_key: String, - pub(crate) sphinx_key: String, +#[derive(Debug, Deserialize, Serialize)] +pub struct BondingInformation { + host: String, + identity_key: ed25519::PublicKey, } #[derive(Deserialize)] @@ -168,6 +151,7 @@ impl NetworkManager { "--mnemonic", &Zeroizing::new(node.owner.mnemonic.to_string()), "--local", + "--accept-operator-terms-and-conditions", "--output", "json", "--bonding-information-output", @@ -180,6 +164,9 @@ impl NetworkManager { if is_gateway { cmd.args(["--mode", "entry"]); + } else { + // be explicit about it, even though we don't have to be + cmd.args(["--mode", "mixnode"]); } let mut child = cmd.spawn()?; @@ -190,20 +177,9 @@ impl NetworkManager { } let output_file = fs::File::open(&output_file_path)?; - let bonding_info: BondingInformationV1 = serde_json::from_reader(&output_file)?; + let bonding_info: BondingInformation = serde_json::from_reader(&output_file)?; - match bonding_info { - BondingInformationV1::Mixnode(bonding_info) => { - node.identity_key = bonding_info.identity_key; - node.sphinx_key = bonding_info.sphinx_key; - node.version = bonding_info.version; - } - BondingInformationV1::Gateway(bonding_info) => { - node.identity_key = bonding_info.identity_key; - node.sphinx_key = bonding_info.sphinx_key; - node.version = bonding_info.version; - } - } + node.identity_key = bonding_info.identity_key.to_string(); ctx.set_pb_message(format!("generating bonding signature for node {id}...")); @@ -225,6 +201,7 @@ impl NetworkManager { .stderr(Stdio::null()) .kill_on_drop(true) .output(); + let out = ctx.async_with_progress(child).await?; if !out.status.success() { return Err(NetworkManagerError::NymNodeExecutionFailure); diff --git a/tools/internal/testnet-manager/src/manager/node.rs b/tools/internal/testnet-manager/src/manager/node.rs index d744057f32..ec5eeae06d 100644 --- a/tools/internal/testnet-manager/src/manager/node.rs +++ b/tools/internal/testnet-manager/src/manager/node.rs @@ -14,9 +14,7 @@ pub(crate) struct NymNode { pub(crate) verloc_port: u16, pub(crate) http_port: u16, pub(crate) clients_port: u16, - pub(crate) sphinx_key: String, pub(crate) identity_key: String, - pub(crate) version: String, pub(crate) owner: Account, pub(crate) bonding_signature: String, @@ -29,9 +27,7 @@ impl NymNode { verloc_port: 0, http_port: 0, clients_port: 0, - sphinx_key: "".to_string(), identity_key: "".to_string(), - version: "".to_string(), owner: Account::new(), bonding_signature: "".to_string(), } From e5e7ddb0b60767507e5c844ba433e492ac9a1c27 Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:30:19 +0000 Subject: [PATCH 30/80] Create push-nyx-chain-watcher.yaml --- .github/workflows/push-nyx-chain-watcher.yaml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/push-nyx-chain-watcher.yaml diff --git a/.github/workflows/push-nyx-chain-watcher.yaml b/.github/workflows/push-nyx-chain-watcher.yaml new file mode 100644 index 0000000000..92b661d355 --- /dev/null +++ b/.github/workflows/push-nyx-chain-watcher.yaml @@ -0,0 +1,55 @@ +name: Build and upload Nyx Chain Watcher container to harbor.nymte.ch +on: + workflow_dispatch: + +env: + WORKING_DIRECTORY: "nyx-chain-watcher" + CONTAINER_NAME: "nyx-chain-watcher" + +jobs: + build-container: + runs-on: arc-ubuntu-22.04-dind + steps: + - name: Login to Harbor + uses: docker/login-action@v3 + with: + registry: harbor.nymte.ch + username: ${{ secrets.HARBOR_ROBOT_USERNAME }} + password: ${{ secrets.HARBOR_ROBOT_SECRET }} + + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Configure git identity + run: | + git config --global user.email "lawrence@nymtech.net" + git config --global user.name "Lawrence Stalder" + + - name: Get version from cargo.toml + uses: mikefarah/yq@v4.44.5 + id: get_version + with: + cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml + + - name: Check if tag exists + run: | + if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then + echo "Tag ${{ steps.get_version.outputs.value }} already exists" + fi + + - name: Remove existing tag if exists + run: | + if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then + git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} + git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} + fi + + - name: Create tag + run: | + git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}" + git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} + + - name: BuildAndPushImageOnHarbor + run: | + docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest + docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags From 72a4624acee625981d497c9ce88cefc52e7ff081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 9 Dec 2024 09:00:39 +0100 Subject: [PATCH 31/80] Add NYXD_WS to qa.env (#5226) --- envs/qa.env | 1 + 1 file changed, 1 insertion(+) diff --git a/envs/qa.env b/envs/qa.env index 81adaf299c..0a53e1aa0c 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -20,5 +20,6 @@ REWARDING_VALIDATOR_ADDRESS=n1rfvpsynktze6wvn6ldskj8xgwfzzk5v6pnff39 EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api/ NYXD=https://qa-validator.qa.nymte.ch +NYXD_WS=wss://qa-validator.qa.nymte.ch/websocket/ NYM_API=https://qa-nym-api.qa.nymte.ch/api/ NYM_VPN_API=https://nym-vpn-api-git-deploy-qa-nyx-network-staging.vercel.app/api/ From 1b8a929ff55b636d09a953f6e4a7c9193c91d296 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 9 Dec 2024 09:03:04 +0100 Subject: [PATCH 32/80] Nmv2 add debug config (#5212) * Add debug config to clients * Add deterministic traffic selection flag --- Cargo.lock | 1 + nym-network-monitor/Cargo.toml | 1 + nym-network-monitor/src/main.rs | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0b2959b750..f194e4e0f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5862,6 +5862,7 @@ dependencies = [ "futures", "log", "nym-bin-common", + "nym-client-core", "nym-crypto", "nym-network-defaults", "nym-sdk", diff --git a/nym-network-monitor/Cargo.toml b/nym-network-monitor/Cargo.toml index 5984b1ec48..3d80fed9a0 100644 --- a/nym-network-monitor/Cargo.toml +++ b/nym-network-monitor/Cargo.toml @@ -30,6 +30,7 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } # internal nym-bin-common = { path = "../common/bin-common" } +nym-client-core = { path = "../common/client-core" } nym-crypto = { path = "../common/crypto" } nym-network-defaults = { path = "../common/network-defaults" } nym-sdk = { path = "../sdk/rust/nym-sdk" } diff --git a/nym-network-monitor/src/main.rs b/nym-network-monitor/src/main.rs index e209a64533..141944eb8a 100644 --- a/nym-network-monitor/src/main.rs +++ b/nym-network-monitor/src/main.rs @@ -88,6 +88,7 @@ async fn make_client(topology: NymTopology) -> Result { let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral() .network_details(net) .custom_topology_provider(topology_provider) + .debug_config(mixnet_debug_config(0)) // .enable_credentials_mode() .build()?; @@ -216,3 +217,14 @@ async fn main() -> Result<()> { Ok(()) } + +fn mixnet_debug_config(min_gateway_performance: u8) -> nym_client_core::config::DebugConfig { + let mut debug_config = nym_client_core::config::DebugConfig::default(); + debug_config + .traffic + .disable_main_poisson_packet_distribution = true; + debug_config.cover_traffic.disable_loop_cover_traffic_stream = true; + debug_config.topology.minimum_gateway_performance = min_gateway_performance; + debug_config.traffic.deterministic_route_selection = true; + debug_config +} From c54760bb0bb2aa6441e41fc36b6f98aadba2ed7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 9 Dec 2024 22:53:52 +0100 Subject: [PATCH 33/80] TicketType derive Hash and Eq (#5233) --- common/credentials-interface/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index 651da25ed6..cf1b138677 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -225,8 +225,10 @@ impl From for NymPayInfo { Clone, Debug, PartialEq, + Eq, Serialize, Deserialize, + Hash, strum::Display, strum::EnumString, strum::EnumIter, From 236555e6c123fe8b610d0926eae175b9cce9ad2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:58:46 +0100 Subject: [PATCH 34/80] build(deps): bump mikefarah/yq from 4.44.5 to 4.44.6 (#5234) --- .github/workflows/push-credential-proxy.yaml | 2 +- .github/workflows/push-data-observatory.yaml | 2 +- .github/workflows/push-network-monitor.yaml | 2 +- .github/workflows/push-node-status-agent.yaml | 2 +- .github/workflows/push-node-status-api.yaml | 2 +- .github/workflows/push-nym-node.yaml | 2 +- .github/workflows/push-nyx-chain-watcher.yaml | 2 +- .github/workflows/push-validator-rewarder.yaml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/push-credential-proxy.yaml b/.github/workflows/push-credential-proxy.yaml index ff3b02f728..6eb29e361c 100644 --- a/.github/workflows/push-credential-proxy.yaml +++ b/.github/workflows/push-credential-proxy.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml diff --git a/.github/workflows/push-data-observatory.yaml b/.github/workflows/push-data-observatory.yaml index 8b5250995d..16026ee589 100644 --- a/.github/workflows/push-data-observatory.yaml +++ b/.github/workflows/push-data-observatory.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-network-monitor.yaml b/.github/workflows/push-network-monitor.yaml index 6856ec88d1..a63ae96be8 100644 --- a/.github/workflows/push-network-monitor.yaml +++ b/.github/workflows/push-network-monitor.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml diff --git a/.github/workflows/push-node-status-agent.yaml b/.github/workflows/push-node-status-agent.yaml index a30ef58f77..8400b7e7e1 100644 --- a/.github/workflows/push-node-status-agent.yaml +++ b/.github/workflows/push-node-status-agent.yaml @@ -31,7 +31,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-node-status-api.yaml b/.github/workflows/push-node-status-api.yaml index acf857109d..bd19db3bc8 100644 --- a/.github/workflows/push-node-status-api.yaml +++ b/.github/workflows/push-node-status-api.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-nym-node.yaml b/.github/workflows/push-nym-node.yaml index 52dfeae0e7..79c25e022c 100644 --- a/.github/workflows/push-nym-node.yaml +++ b/.github/workflows/push-nym-node.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-nyx-chain-watcher.yaml b/.github/workflows/push-nyx-chain-watcher.yaml index 92b661d355..084f4d11d7 100644 --- a/.github/workflows/push-nyx-chain-watcher.yaml +++ b/.github/workflows/push-nyx-chain-watcher.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml diff --git a/.github/workflows/push-validator-rewarder.yaml b/.github/workflows/push-validator-rewarder.yaml index ad675499da..db27ca4d64 100644 --- a/.github/workflows/push-validator-rewarder.yaml +++ b/.github/workflows/push-validator-rewarder.yaml @@ -26,7 +26,7 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.44.5 + uses: mikefarah/yq@v4.44.6 id: get_version with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml From 33bdf088048030865e2e2e92aacd0df1588bdec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 10 Dec 2024 10:35:19 +0100 Subject: [PATCH 35/80] Add FromStr impl for UserAgent (#5236) * Add FromStr impl for UserAgent * Convert error type to struct --- common/http-api-client/src/user_agent.rs | 114 ++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/common/http-api-client/src/user_agent.rs b/common/http-api-client/src/user_agent.rs index d47cb570c4..eeec96e424 100644 --- a/common/http-api-client/src/user_agent.rs +++ b/common/http-api-client/src/user_agent.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::fmt; +use std::{fmt, str::FromStr}; use http::HeaderValue; use nym_bin_common::build_information::{BinaryBuildInformation, BinaryBuildInformationOwned}; @@ -15,6 +15,36 @@ pub struct UserAgent { pub git_commit: String, } +#[derive(Clone, Debug, thiserror::Error)] +#[error("invalid user agent string: {0}")] +pub struct UserAgentError(String); + +impl FromStr for UserAgent { + type Err = UserAgentError; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split('/').collect(); + if parts.len() != 4 { + return Err(UserAgentError(s.to_string())); + } + + Ok(UserAgent { + application: parts[0].to_string(), + version: parts[1].to_string(), + platform: parts[2].to_string(), + git_commit: parts[3].to_string(), + }) + } +} + +impl TryFrom<&str> for UserAgent { + type Error = UserAgentError; + + fn try_from(s: &str) -> Result { + UserAgent::from_str(s) + } +} + impl fmt::Display for UserAgent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let abbreviated_commit = self.git_commit.chars().take(7).collect::(); @@ -55,3 +85,85 @@ impl From for UserAgent { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parsing_valid_user_agent() { + let user_agent = "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg"; + let parsed = UserAgent::from_str(user_agent).unwrap(); + assert_eq!( + parsed, + UserAgent { + application: "nym-mixnode".to_string(), + version: "0.11.0".to_string(), + platform: "x86_64-unknown-linux-gnu".to_string(), + git_commit: "abcdefg".to_string() + } + ); + } + + #[test] + fn parsing_invalid_user_agent() { + let user_agent = "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu"; + assert!(UserAgent::from_str(user_agent).is_err()); + } + + #[test] + fn converting_user_agent_to_string() { + let user_agent = UserAgent { + application: "nym-mixnode".to_string(), + version: "0.11.0".to_string(), + platform: "x86_64-unknown-linux-gnu".to_string(), + git_commit: "abcdefg".to_string(), + }; + + assert_eq!( + user_agent.to_string(), + "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg" + ); + } + + #[test] + fn converting_user_agent_to_display() { + let user_agent = UserAgent { + application: "nym-mixnode".to_string(), + version: "0.11.0".to_string(), + platform: "x86_64-unknown-linux-gnu".to_string(), + git_commit: "abcdefg".to_string(), + }; + + assert_eq!( + format!("{}", user_agent), + "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg" + ); + } + + #[test] + fn converting_user_agent_to_header_value_fails() { + let user_agent = UserAgent { + application: "nym-mixnode".to_string(), + version: "0.11.0".to_string(), + platform: "x86_64-unknown-linux-gnu".to_string(), + git_commit: "abcdefg".to_string(), + }; + + let header_value: Result = user_agent.clone().try_into(); + assert!(header_value.is_ok()); + } + + #[test] + fn converting_user_agent_to_header_value_has_same_string_representation() { + let user_agent = UserAgent { + application: "nym-mixnode".to_string(), + version: "0.11.0".to_string(), + platform: "x86_64-unknown-linux-gnu".to_string(), + git_commit: "abcdefg".to_string(), + }; + + let header_value: HeaderValue = user_agent.clone().try_into().unwrap(); + assert_eq!(header_value.to_str().unwrap(), user_agent.to_string()); + } +} From c29fce08563a47374b3ff7f1c7fe35552b3f7800 Mon Sep 17 00:00:00 2001 From: benedetta davico <46782255+benedettadavico@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:16:16 +0100 Subject: [PATCH 36/80] Update NS-api version in Cargo.toml --- nym-node-status-api/nym-node-status-api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 44c61b455f..98684f75f5 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "1.0.0-rc.6" +version = "1.0.0-rc.7" authors.workspace = true repository.workspace = true homepage.workspace = true From 66fea38d2092d628c061839378cdc98abbcb64b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 10 Dec 2024 13:35:29 +0000 Subject: [PATCH 37/80] bugfix: make sure to update timestamp of last batch verification to prevent double redemption (#5239) --- Cargo.lock | 2 +- nym-api/src/ecash/api_routes/spending.rs | 8 +++- nym-api/src/ecash/state/mod.rs | 10 ++++- nym-api/src/ecash/storage/manager.rs | 54 +++++++++++++++++------- nym-api/src/ecash/storage/mod.rs | 16 +++++++ 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f194e4e0f6..f3585f8d7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6058,7 +6058,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "1.0.0-rc.6" +version = "1.0.0-rc.7" dependencies = [ "anyhow", "axum 0.7.7", diff --git a/nym-api/src/ecash/api_routes/spending.rs b/nym-api/src/ecash/api_routes/spending.rs index 3bda629174..f52aeda2eb 100644 --- a/nym-api/src/ecash/api_routes/spending.rs +++ b/nym-api/src/ecash/api_routes/spending.rs @@ -203,7 +203,7 @@ async fn batch_redeem_tickets( // 5. check if **every** serial number included in the request has been verified by us // if we have more than requested, tough luck, they're going to lose them - let verified = state.get_redeemable_tickets(provider_info).await?; + let verified = state.get_redeemable_tickets(&provider_info).await?; let verified_tickets: HashSet<_> = verified.iter().map(|sn| sn.deref()).collect(); for sn in &received { @@ -215,8 +215,14 @@ async fn batch_redeem_tickets( } } + // 6. vote on the proposal // TODO: offload it to separate task with work queue and batching (of tx messages) to vote for multiple proposals in the same tx + // similarly to what we do inside the credential proxy state.accept_proposal(proposal_id).await?; + + // 7. update the time of the last verification for this provider + state.update_last_batch_verification(&provider_info).await?; + Ok(Json(EcashBatchTicketRedemptionResponse { proposal_accepted: true, })) diff --git a/nym-api/src/ecash/state/mod.rs b/nym-api/src/ecash/state/mod.rs index f262177480..3e716d08f4 100644 --- a/nym-api/src/ecash/state/mod.rs +++ b/nym-api/src/ecash/state/mod.rs @@ -890,7 +890,7 @@ impl EcashState { pub async fn get_redeemable_tickets( &self, - provider_info: TicketProvider, + provider_info: &TicketProvider, ) -> Result> { let since = provider_info .last_batch_verification @@ -903,6 +903,14 @@ impl EcashState { .map_err(Into::into) } + pub async fn update_last_batch_verification(&self, provider: &TicketProvider) -> Result<()> { + Ok(self + .aux + .storage + .update_last_batch_verification(provider.id, OffsetDateTime::now_utc()) + .await?) + } + pub async fn get_ticket_data_by_serial_number( &self, serial_number: &[u8], diff --git a/nym-api/src/ecash/storage/manager.rs b/nym-api/src/ecash/storage/manager.rs index f3b227b5b0..7e49215c70 100644 --- a/nym-api/src/ecash/storage/manager.rs +++ b/nym-api/src/ecash/storage/manager.rs @@ -82,6 +82,11 @@ pub trait EcashStorageManagerExt { provider_id: i64, since: OffsetDateTime, ) -> Result, sqlx::Error>; + async fn update_last_batch_verification( + &self, + provider_id: i64, + last_batch_verification: OffsetDateTime, + ) -> Result<(), sqlx::Error>; async fn get_spent_tickets_on( &self, @@ -215,15 +220,15 @@ impl EcashStorageManagerExt for StorageManager { "#, expiration_date ) - .fetch_all(&self.connection_pool) - .await? - .into_iter() - .filter_map(|r| r.merkle_leaf.try_into().inspect_err(|_| error!("possible database corruption: one of the stored merkle leaves is not a valid 32byte hash")).ok().map(|merkle_leaf| IssuedHash { - deposit_id: r.deposit_id, - merkle_leaf, - merkle_index: r.merkle_index as usize, - })) - .collect()) + .fetch_all(&self.connection_pool) + .await? + .into_iter() + .filter_map(|r| r.merkle_leaf.try_into().inspect_err(|_| error!("possible database corruption: one of the stored merkle leaves is not a valid 32byte hash")).ok().map(|merkle_leaf| IssuedHash { + deposit_id: r.deposit_id, + merkle_leaf, + merkle_index: r.merkle_index as usize, + })) + .collect()) } /// Store the provided issued credential information. @@ -344,8 +349,8 @@ impl EcashStorageManagerExt for StorageManager { verified_at, provider_id ) - .execute(&self.connection_pool) - .await?; + .execute(&self.connection_pool) + .await?; Ok(()) } @@ -382,6 +387,25 @@ impl EcashStorageManagerExt for StorageManager { .await } + async fn update_last_batch_verification( + &self, + provider_id: i64, + last_batch_verification: OffsetDateTime, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + UPDATE ticket_providers + SET last_batch_verification = ? + WHERE id = ? + "#, + last_batch_verification, + provider_id + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + async fn get_spent_tickets_on( &self, date: Date, @@ -510,8 +534,8 @@ impl EcashStorageManagerExt for StorageManager { epoch_id, data ) - .execute(&self.connection_pool) - .await?; + .execute(&self.connection_pool) + .await?; Ok(()) } @@ -544,8 +568,8 @@ impl EcashStorageManagerExt for StorageManager { epoch_id, data ) - .execute(&self.connection_pool) - .await?; + .execute(&self.connection_pool) + .await?; Ok(()) } diff --git a/nym-api/src/ecash/storage/mod.rs b/nym-api/src/ecash/storage/mod.rs index 019ab43a72..2d0d802f06 100644 --- a/nym-api/src/ecash/storage/mod.rs +++ b/nym-api/src/ecash/storage/mod.rs @@ -112,6 +112,11 @@ pub trait EcashStorageExt { provider_id: i64, since: OffsetDateTime, ) -> Result, NymApiStorageError>; + async fn update_last_batch_verification( + &self, + provider_id: i64, + last_batch_verification: OffsetDateTime, + ) -> Result<(), NymApiStorageError>; async fn get_all_spent_tickets_on( &self, @@ -395,6 +400,17 @@ impl EcashStorageExt for NymApiStorage { .map_err(Into::into) } + async fn update_last_batch_verification( + &self, + provider_id: i64, + last_batch_verification: OffsetDateTime, + ) -> Result<(), NymApiStorageError> { + Ok(self + .manager + .update_last_batch_verification(provider_id, last_batch_verification) + .await?) + } + async fn get_all_spent_tickets_on( &self, date: Date, From daa680d6b8d7386d231da1d1a40a26aea654a785 Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:49:22 +0000 Subject: [PATCH 38/80] [DOCs/operators]: Release notes v2024.14-crunch & config score updates (#5222) * initialise tokenomics graph * generate reward version config graph * update tokenomics * edit typo * initialise release crunch release notes * operators update * add points to changelog * update version graph and selection * update iptables configuration * add features to changelog * comment redundant * address review comments * PR finish --- .../nyx-outputs/circulating-supply.md | 2 +- .../nyx-outputs/stake-saturation.md | 2 +- .../nyx-outputs/staking-target.md | 2 +- .../nyx-outputs/token-table.md | 6 +- .../outputs/api-scraping-outputs/time-now.md | 2 +- .../outputs/command-outputs/nym-api-help.md | 4 +- .../command-outputs/nym-node-run-help.md | 4 + .../docs/pages/operators/changelog.mdx | 273 +++++++++++++++++- .../nodes/nym-node/configuration.mdx | 19 +- .../nodes/nym-node/configuration/_meta.json | 2 +- .../pages/operators/nodes/nym-node/setup.mdx | 9 +- .../operators/tokenomics/mixnet-rewards.mdx | 89 +++++- .../tokenomics/reward_version_graph.png | Bin 0 -> 103951 bytes .../scripts/rewards_version_graph.py | 67 +++++ 14 files changed, 450 insertions(+), 31 deletions(-) create mode 100644 documentation/docs/public/images/operators/tokenomics/reward_version_graph.png create mode 100644 documentation/scripts/rewards_version_graph.py diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md index 0d7c399aa9..7f5aad7cd4 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md @@ -1 +1 @@ -803_103_234 +804_560_131 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md index 8bc02c386e..b2ed9e28ef 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md @@ -1 +1 @@ -1_016_987 +1_020_023 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md index af97e1d311..49bf2c6564 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md @@ -1 +1 @@ -401_551_617 +402_280_065 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md index b1f5c4dea3..413c6d0177 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md @@ -1,7 +1,7 @@ | **Item** | **Description** | **Amount in NYM** | |:-------------------|:------------------------------------------------------|--------------------:| | Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 | -| Mixmining Reserve | Tokens releasing for operators rewards | 196_896_265 | +| Mixmining Reserve | Tokens releasing for operators rewards | 195_439_368 | | Vesting Tokens | Tokens locked outside of cicrulation for future claim | 500 | -| Circulating Supply | Amount of unlocked tokens | 803_103_234 | -| Stake Saturation | Optimal size of node self-bond + delegation | 1_016_987 | +| Circulating Supply | Amount of unlocked tokens | 804_560_131 | +| Stake Saturation | Optimal size of node self-bond + delegation | 1_020_023 | diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index 1d7a6e3d3b..2710b013a7 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Monday, November 25th 2024, 13:24:04 UTC +Friday, December 6th 2024, 10:16:06 UTC diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-help.md b/documentation/docs/components/outputs/command-outputs/nym-api-help.md index fcaf94ce6b..1ba192c6e8 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-help.md @@ -9,9 +9,9 @@ Commands: Options: -c, --config-env-file - Path pointing to an env file that configures the Nym API + Path pointing to an env file that configures the Nym API [env: NYMAPI_CONFIG_ENV_FILE_ARG=] --no-banner - A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops) + A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops) [env: NYMAPI_NO_BANNER_ARG=] -h, --help Print help -V, --version diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md index 0a5070b615..29da832b97 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md @@ -44,6 +44,8 @@ Options: Specify whether detailed system crypto hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] --mixnet-bind-address Address this node will bind to for listening for mixnet packets default: `0.0.0.0:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] + --mixnet-announce-port + If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=] --nym-api-urls Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] --nyxd-urls @@ -60,6 +62,8 @@ Options: The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] --verloc-bind-address Socket address this node will use for binding its verloc API. default: `0.0.0.0:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] + --verloc-announce-port + If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=] --entry-bind-address Socket address this node will use for binding its client websocket API. default: `0.0.0.0:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] --announce-ws-port diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 06ac73d2dc..6c156b5f22 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -25,14 +25,285 @@ export const CiConfig = () => ( ); +export const TunnelManagerCommands = () => ( +

+ Commands to update IP tables rules with a new network_tunnel_manager.sh +
+); + # Changelog -This page displays a full list of all the changes during our release cycle from `v2024.3-eclipse` onwards. Operators can find here the newest updates together with links to relevant documentation. The list is sorted so that the newest changes appear first. +This page displays a full list of all the changes during our release cycle from `v2024.3-eclipse` onward. Operators can find here the newest updates together with links to relevant documentation. The list is sorted so that the newest changes appear first. +## `v2024.14-crunch` + +- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.14-crunch) +- [`nym-node`](nodes/nym-node.mdx) version `1.2.0` +- [Releae operators updates and tools](changelog#operators-updates--tools) +- [Github CHANGELOG.md](https://github.com/nymtech/nym/blob/nym-binaries-v2024.14-crunch/CHANGELOG.md) + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2024-12-11T13:49:11.974104790Z +Build Version: 1.2.0 +Commit SHA: a491e6a71a8cf862d77defd740a4ee8d65d8292a +Commit Date: 2024-12-11T10:28:47.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.83.0 +rustc Channel: stable +cargo Profile: release +``` + +### Features + +- [Bump elliptic from `6.5.4` to `6.5.7` in /testnet-faucet](https://github.com/nymtech/nym/pull/4768): Bumps [elliptic](https://github.com/indutny/elliptic) from `6.5.4` to `6.5.7`. + +- [build(deps): bump micromatch from `4.0.4` to `4.0.8` in /nym-wallet/webdriver](https://github.com/nymtech/nym/pull/4789): Bumps [micromatch](https://github.com/micromatch/micromatch) from `4.0.4` to `4.0.8`. + +- [build(deps): bump axios from 1.6.0 to 1.7.5 in /nym-api/tests](https://github.com/nymtech/nym/pull/4790) Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.5. + +- [Sync code with `.env` in build.rs](https://github.com/nymtech/nym/pull/4876): Keep `dotenv` file always up to date + +- [build(deps): bump lazy_static from `1.4.0` to `1.5.0`](https://github.com/nymtech/nym/pull/4913): Bumps [lazy_static](https://github.com/rust-lang-nursery/lazy-static.rs) from `1.4.0` to `1.5.0`. + +- [Create TaskStatusEvent trait instead of piggybacking on Error](https://github.com/nymtech/nym/pull/4919) + +- [build(deps): bump once_cell from `1.19.0` to `1.20.2`](https://github.com/nymtech/nym/pull/4952): Bumps [`once_cell`](https://github.com/matklad/once_cell) from `1.19.0` to `1.20.2` + +- [Bump the patch-updates group across 1 directory with 10 updates](https://github.com/nymtech/nym/pull/5011): Bumps the patch-updates group with 9 updates in the / directory: + +| Package | From | To | +| --- | --- | --- | +| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.89` | `1.0.90` | +| [clap](https://github.com/clap-rs/clap) | `4.5.18` | `4.5.20` | +| [clap_complete](https://github.com/clap-rs/clap) | `4.5.29` | `4.5.33` | +| [pin-project](https://github.com/taiki-e/pin-project) | `1.1.5` | `1.1.6` | +| [serde](https://github.com/serde-rs/serde) | `1.0.210` | `1.0.211` | +| [serde_json](https://github.com/serde-rs/json) | `1.0.128` | `1.0.132` | +| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.93` | `0.2.95` | +| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.43` | `0.4.45` | +| [web-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.70` | `0.3.72` | +| Updates `anyhow` | `1.0.89` | `1.0.90` | + +- [[Product Data] Introduce data persistence on gateways](https://github.com/nymtech/nym/pull/5022): This PR builds on top of [\#4974](https://github.com/nymtech/nym/pull/4974), not changing the behavior of the data collection, but persisting them in a sqlite database so they can be kept across restarts and crashes. It also leave the door open for other stats module to use that storage if needed. Here are some points of interest: + - New [`gateway_stats_storage`](https://github.com/nymtech/nym/tree/simon/gateway_stats_persistence/common/gateway-stats-storage) crate + - [Config migration](https://github.com/nymtech/nym/blob/simon/gateway_stats_persistence/nym-node/src/config/old_configs/old_config_v4.rs) resulting from the added database. + - Resulting changes in the [`statistics`](https://github.com/nymtech/nym/tree/simon/gateway_stats_persistence/gateway/src/node/statistics) module to account the new storage system + +- [Integrate nym-credential-proxy into workspace](https://github.com/nymtech/nym/pull/5027): Integrate `nym-credential-proxy` into the main workspace + +- [Authenticator CLI client mode](https://github.com/nymtech/nym/pull/5044) + +- [Node Status API](https://github.com/nymtech/nym/pull/5050): merging a long-diverged feature branch - all commits here were their own merge requests + +- [IPv6 support for wireguard](https://github.com/nymtech/nym/pull/5059) + +- [Add nym node GH workflow](https://github.com/nymtech/nym/pull/5080) + +- [[Product Data] Better unique user count on gateways](https://github.com/nymtech/nym/pull/5084): To avoid double counting clients across gateways, we add a user ID to the gateway session data. + +- [chore: ecash contract migration to remove unused 'redemption_gateway_share'](https://github.com/nymtech/nym/pull/5104) + +- [[Product Data] Client-side stats collection ](https://github.com/nymtech/nym/pull/5107): The goal is to anonymously gather stats from nym-clients. These stats will be sent through the mixnet to a Nym run service provider that will gather them. This PR sets the scene to send stats in a mixnet message to an address. The address can be set when the client is created. Current stats include some infos on sent packets along with platform information. If a receiving address is set, the client will send a mixnet packet every 5min to this address. Otherwise, nothing happens and the client runs as usual. + +- [Send mixnet packet stats using task client](https://github.com/nymtech/nym/pull/5109) + +- [Add granular log on nym-node](https://github.com/nymtech/nym/pull/5111) and make use of it for `defguard_wireguard_rs` big info logs + +- [Rewarding for ticketbook issuance](https://github.com/nymtech/nym/pull/5112): Revamps the current validator rewarder to allow for rewards for issuing the zk-nym ticketbooks. + +- [[Product Data] Add stats reporting configuration in client config ](https://github.com/nymtech/nym/pull/5115): Adds the stats reporting address to client configs. It can be set in the config file, as a CLI argument and as an env var in a `.env` file. As the stats reporting config in now in the `DebugConfig`, the `StatsReportingConfig` is no longer required, making the propagation of these changes more readable + +- [config score](https://github.com/nymtech/nym/pull/5117): introduces a concept of a `config_score` to a nym node which influences performance and thus rewarding amounts and chances of being in the rewarded set. Currently it's influenced by the following factors: + - Accepting terms and conditions (not accepted: 0) + - Exposing self-described API (not exposed: 0) + - Running "nym-node" binary (legacy binary: 0) + - Number of versions behind the core (`score = 0.995 ^ (X * versions_behind ^ 1.65)`) + - The old performance is now treated as `routing_score` + - the "new" performance = `routing_score * config_score` + +- [Add Dockerfile and add env vars for clap arguments](https://github.com/nymtech/nym/pull/5118) + +- [Aadd GH workflow for nym-validator-rewarder](https://github.com/nymtech/nym/pull/5119) + +- [[Product data] Data consumption with ecash ticket](https://github.com/nymtech/nym/pull/5120): Send an event each time an ecash ticket get successfully spent. This allows to approximate how much data each client is using. + +- [[Product Data] Config deserialization bug fix](https://github.com/nymtech/nym/pull/5126): Fixes a bug where a `None` value was serialized into an empty string, and incorrectly deserialized into a `Some` variant. + +- [NS Agent auth with NS API](https://github.com/nymtech/nym/pull/5127): NS Agent authenticates with key that was registered with NS API + - Added flag to Agent to generate keypairs + - Agent requests are signed by agent + - Server-side requests are checked for authentication + +- [CI: reduce jobs running on cluster](https://github.com/nymtech/nym/pull/5132) + +- [Removed ci-nym-api-tests.yml which was running outdated (and broken) tests](https://github.com/nymtech/nym/pull/5133) + +- [[Product Data] Set up country reporting from vpn-client](https://github.com/nymtech/nym/pull/5134): Add the ability to report exit country, along with a small refactoring of a module. + +- [chore: remove standalone legacy mixnode/gateway binaries](https://github.com/nymtech/nym/pull/5135) + +- [Update `serde_json_path` due to compilation issue](https://github.com/nymtech/nym/pull/5144) + +- [Add version to clientStatsReport](https://github.com/nymtech/nym/pull/5147): Add a `kind` and `api_version` field for `ClientStatsReport` + +- [Start session collection for exit gateways](https://github.com/nymtech/nym/pull/5148): Apparently, exit gateways are also entry gateways so we need to start session stats for them as well + +- [build(deps): bump mikefarah/yq from `4.44.3` to `4.44.5`](https://github.com/nymtech/nym/pull/5149): Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from `4.44.3` to `4.44.5`. + +- [build(deps): bump cross-spawn from `7.0.3` to `7.0.6` in /testnet-faucet](https://github.com/nymtech/nym/pull/5150): Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from `7.0.3` to `7.0.6`. + +- [Add export_to_env to NymNetworkDetails](https://github.com/nymtech/nym/pull/5162): In `nym-vpn-core` we've started to read the network environment from a json file and then try to pass around `NymNetworkDetails` directly instead of relying on the exported environment. However we still need to bridge with old code so we need to export the network details instance to the environment. + +- [Add strum::EnumIter for TicketType](https://github.com/nymtech/nym/pull/5164) + +- [Fix env var name](https://github.com/nymtech/nym/pull/5165) + +- [Add support for DELETE to nym-http-api-client](https://github.com/nymtech/nym/pull/5166): Add delete support to `http-api-client` + +- [Add derive_extended_private_key to DirectSecp256k1HdWallet](https://github.com/nymtech/nym/pull/5167): Add `derive_extended_private_key` to `DirectSectp256k1HdWallet` to support seeding ecash keys + +- [Move two minor jobs to free tier github hosted runners](https://github.com/nymtech/nym/pull/5169): In an attempt to easy the load on the self-hosted runners, move two minor workflows over to GH hosted free tier runners. + +- [Remove peers with no allowed ip from storage](https://github.com/nymtech/nym/pull/5175) + +- [Add indexes to monitor run and testing route](https://github.com/nymtech/nym/pull/5181) + +- [Add `monitor_run` and testing_route indexes](https://github.com/nymtech/nym/pull/5182) + +- [`explorer-api`: add nym node endpoints + UI to show nym-nodes and account balances](https://github.com/nymtech/nym/pull/5183): Explorer API: + - Existing endpoints stay identical + - Adds new endpoints to get: + - `nym-nodes` (list + by id) + - account balance + delegations + rewarding + vesting + + - Explorer UI (NextJS) + - List of nym-nodes + - Remove service providers routes (Harbour Master shows these) + - Updates summary page to show nym-nodes + - Adds legacy markers to old gateway and mixnode bond lists + +- [Add `monitor_run` and testing_route indexes](https://github.com/nymtech/nym/pull/5182) + +- [Bugfix/credential proxy sequencing](https://github.com/nymtech/nym/pull/5187) + +- [improvement: make internal gateway clients use the same topology cache](https://github.com/nymtech/nym/pull/5191): This should result in 66% reduction in queries for topology within `nym-node` as all the clients should rely on the same cache + +- [chore: apply 1.84 linter suggestions](https://github.com/nymtech/nym/pull/5192) + +- [Guard storage access with cache](https://github.com/nymtech/nym/pull/5193) + +- [Update Security disclosure email, public key and policy](https://github.com/nymtech/nym/pull/5195) + +- [merge crunch into develop](https://github.com/nymtech/nym/pull/5199) + +- [Fix backwards compat mac generation](https://github.com/nymtech/nym/pull/5202) + +- [adjusted config score penalty calculation](https://github.com/nymtech/nym/pull/5206) + +- [`nym-api` NMv1 adjustments](https://github.com/nymtech/nym/pull/5209) + +- [Nmv2 add debug config](https://github.com/nymtech/nym/pull/5212): Adds debug config to disable poisson process, cover traffic and min performance filtering + +- [introduce UNSTABLE endpoints for returning network monitor run details](https://github.com/nymtech/nym/pull/5214) + +- [Don't consider legacy nodes for rewarded set selection](https://github.com/nymtech/nym/pull/5215) + +- [Derive serialize for UserAgent (#5210)](https://github.com/nymtech/nym/pull/5217): Cherry-pick PR [\#5210](https://github.com/nymtech/nym/pull/5210) + +- [Backport \#5218](https://github.com/nymtech/nym/pull/5220) + +- [Remove any filtering on node semver](https://github.com/nymtech/nym/pull/5224): Removed any filtering on version of nodes. however, the parameters can still be passed to `nym-api` queries to not break existing clients, but they will happily ignore them + +- [Further config score adjustments](https://github.com/nymtech/nym/pull/5225): I still want to add helper endpoints on `nym-api` to expose some of this data. but for now, I'll let this PR bake over the weekend. + +### Bugfix + +- [Correct IPv6 address generation](https://github.com/nymtech/nym/pull/5113) + +- [bugfix: don't send empty BankMsg in ecash contract](https://github.com/nymtech/nym/pull/5121): If ticketbook prices were to be set so low the resultant redemption would have created `BankMsg` with value of 0, that message is no longer going to be sent + +- [fix: validator-rewarder GH job](https://github.com/nymtech/nym/pull/5151) + +- [bugfix: correctly expose ecash-related data on nym-api](https://github.com/nymtech/nym/pull/5155): This PR makes fixes to ecash-related endpoints on `nym-api` + - global data (such as aggregated signatures and keys) are actually always available by all apis + - global data (such as aggregated signatures and keys) are actually always available by all apis + +- [bugfix: use default value for verloc config when deserialising missing values](https://github.com/nymtech/nym/pull/5177) + +- [bugfix: fixed nym-node config migrations (again)](https://github.com/nymtech/nym/pull/5179) + +- [bugfix: added explicit openapi servers to account for route prefixes](https://github.com/nymtech/nym/pull/5237) + +### Operators Updates & Tools + + +**Nym Network will now only allow nodes which [migrated](nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract) their node in Nym mixnet smart contract to Nym Node. All nodes which are still bonded as a legacy one (Mixnode or Gateway) in the wallet will have no chance to take part in the [Rewarded set selection](tokenomics/mixnet-rewards#rewarded-set-selection).** + +**Operators taking part in Delegation program or Service Grant program must migrate their nodes latest by December 16th, 08:00 UTC.** + + +#### Updates + +- [Version count as a part of config score](tokenomics/mixnet-rewards#config-score-calculation) has been introduced. To familiarize yourself with Nym Node operator rewards calculation, read [this page](tokenomics/mixnet-rewards). +- Nym nodes running as Exit Gateway in Service Grant program received delegation. Nym team is now delegating total of **64,800,000 NYM on top 241 Nym Nodes** (137 in Mixnode mode and 104 as Gateways). Our delegation aims to incentivise committed operators who support bootstrapping of Nym network before paying users come. + +
+ + +- 250k NYM - Upgrading to magura in time - 2 nodes +- 300k NYM - Upgrading to magura + bonus for a quick patch upgrade - 102 nodes +- No delegation - not upgrading in time - 2 nodes + + +- `nym-node` has now implemented [IPv6 support for wireguard](https://github.com/nymtech/nym/pull/5059) + +- [`network_tunnel_manager.sh` updated](network): run the commands below to make sure + +
+ +}> +These commands can be run one by one or copy-pasted and run as a block. +```sh +mkdir $HOME/nym-binaries; \ + +curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh -o $HOME/nym-binaries/network_tunnel_manager.sh && \ +chmod +x $HOME/nym-binaries/network_tunnel_manager.sh; \ + +$HOME/nym-binaries/network_tunnel_manager.sh check_nymtun_iptables ; \ +$HOME/nym-binaries/network_tunnel_manager.sh remove_duplicate_rules nymtun0 ;\ +$HOME/nym-binaries/network_tunnel_manager.sh remove_duplicate_rules nymwg;\ +$HOME/nym-binaries/network_tunnel_manager.sh check_nymtun_iptables ; \ +$HOME/nym-binaries/network_tunnel_manager.sh adjust_ip_forwarding ; \ +$HOME/nym-binaries/network_tunnel_manager.sh apply_iptables_rules ; \ +$HOME/nym-binaries/network_tunnel_manager.sh check_nymtun_iptables ; \ +$HOME/nym-binaries/network_tunnel_manager.sh apply_iptables_rules_wg ; \ +$HOME/nym-binaries/network_tunnel_manager.sh configure_dns_and_icmp_wg ; \ +$HOME/nym-binaries/network_tunnel_manager.sh adjust_ip_forwarding ; \ +$HOME/nym-binaries/network_tunnel_manager.sh check_ipv6_ipv4_forwarding; \ + +systemctl daemon-reload && service nym-node restart && journalctl -u nym-node -f +``` + +Then run the jokes in a new window for control +```sh +$HOME/nym-binaries/network_tunnel_manager.sh joke_through_the_mixnet +$HOME/nym-binaries/network_tunnel_manager.sh joke_through_wg_tunnel +``` + + +#### Tools + +- **[New APIs documentation](../apis/introduction)** with interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. +- [Nym Harbourmaster](https://harbourmaster.nymtech.net/) has a new tab called `CONTRACT EXPLORER` querying data from Nym mixnet contract in real time. +- [Nym Explorer](https://explorer.nymtech.net) is updated to read migrated nodes correctly +- [New community explorer by SpectreDAO](https://explorer.nym.spectredao.net/dashboard) offers Nym Network dashboard, Node overview and Account stats view functions for operators and delegators. +- [`nym-vpnc`](../developers/nymvpncli) build and run documentation, for those who don't want to use the Nym VPN GUIs. ## `magura-drift` diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx index 63ba5b3f0d..9a82c0081f 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx @@ -301,8 +301,10 @@ chmod +x network_tunnel_manager.sh && \ ###### 3. Setup IP tables rules -- Apply the rules for IPv4 and IPv6: +- 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 ``` @@ -363,9 +365,11 @@ operation check_nymtun_iptables completed successfully. ```
-###### 5. Apply rules for wireguad routing +###### 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 ``` @@ -374,8 +378,15 @@ operation check_nymtun_iptables completed successfully. ```sh ./network_tunnel_manager.sh configure_dns_and_icmp_wg ``` +###### 7. Adjust and validate IP forwarding -###### 7. Check `nymtun0` interface and test routing configuration +```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 @@ -409,7 +420,7 @@ ip addr show nymtun0 - **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! -###### 8. Enable wireguard +###### 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): diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration/_meta.json b/documentation/docs/pages/operators/nodes/nym-node/configuration/_meta.json index 3d0ff2819e..a48b50f426 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration/_meta.json +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration/_meta.json @@ -1,3 +1,3 @@ { - "proxy-configuration": "WSS & Reverese Proxy" + "proxy-configuration": "WSS & Reverse Proxy" } diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index f2d6197fee..33e7e3f6c3 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -17,10 +17,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: 2024-11-29T13:10:51.813092288Z -Build Version: 1.1.12 -Commit SHA: 4a9a5579c40ad956163ea02e01d7b53aef2ac8ef -Commit Date: 2024-11-29T14:06:32.000000000+01:00 +Build Timestamp: 2024-12-11T13:49:11.974104790Z +Build Version: 1.2.0 +Commit SHA: a491e6a71a8cf862d77defd740a4ee8d65d8292a +Commit Date: 2024-12-11T10:28:47.000000000+01:00 Commit Branch: HEAD rustc Version: 1.83.0 rustc Channel: stable @@ -33,6 +33,7 @@ cargo Profile: release ## Summary + To run a new node, you can simply execute the `nym-node` command without any flags. By default, the node will set necessary configurations. If you later decide to change a setting, you can use the `-w` flag. diff --git a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx index facd3860f3..569cd12e08 100644 --- a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx +++ b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx @@ -12,6 +12,10 @@ import { Clt } from 'components/callout-custom/CalloutCustom.jsx'; # Nym Operators Rewards + +**Nym Network Rewarded set selection had been upgraded recently. Make sure to read the chapter *[Rewarded Set Selection](#rewarded-set-selection)* below carefully to fully understand all requirements to be rewarded!** + + * Nym tokenomics are based on the research paper [*Reward Sharing for Mixnets*](https://nymtech.net/nym-cryptoecon-paper.pdf) @@ -42,6 +46,7 @@ To make it easier for the reader, we use a highlighting line on the left side, w Nodes bonded with vesting tokens are [not allowed to join rewarded set](https://github.com/nymtech/nym/pull/5129) - read more on [Nym operators forum](https://forum.nymtech.net/t/vesting-accounts-are-no-longer-supported/827). + ## Overview This is a quick summary, to understand the full picture, please see detailed [*Rewards Logic & Calculation*](#rewards-logic--calculation) chapter below. @@ -126,34 +131,94 @@ This is a quick summary, to understand the full picture, please see detailed [*R -### Active Set Selection - -*Performance matters!* +### Rewarded Set Selection For a node to be rewarded, the node must be part of a [Rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params) (which currently = active set) in the first place. The active set is selected in the beginning of each epoch (every 60min) where total of 240 Nym nodes - represented by 120 mixnodes and 120 gateways, are randomly allocated across the layers. -The algorithm choosing nodes into the active set takes into account node's performance and [stake saturation](../tolkenomics.mdx#stake-saturation), both values being between 0 and 1 and config score which is either 0 or 1. +The algorithm choosing nodes into the active set takes into account these parameters: -**Config score is introduced:** The nodes selection to the active set has a new parameter - `config_score`. Config score currently looks if the node binary is `nym-node` (not legacy `nym-mixnode` or `nym-gateway`) **AND** if [Terms & Conditions](nodes/nym-node/setup.mdx#terms--conditions) are accepted. Config score has binary values of either 0 or 1, with a following logic: +1. [Config score](#config-score-calculation) +2. [Performance](#performance-calculation) +3. [Stake saturation](../tokenomics.mdx#stake-saturation) -| **Run `nym-node` binary** | **T&C's accepted** | **`config_score`** | +Besides these values, the API is also looking whther the node is bonded in Mixnet smart contract as a Nym Node or legacy node (Mixnode or Gateway). **Only nodes bonded as Nym Node in Mixnet smart contract can be selected to the Rewrded set, if you haven't migrated your node yet, please [follow these steps](../nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract)!** + +**The Rewarded set selection probablity formula:** + + +> **active_set_selection_probability = config_score \* ( node_performance ^ 20 ) \* stake_saturation** + + +#### Config Score Calculation + +The nodes selection to the active set has a new parameter - `config_score`. Config score currently looks into three paramteres: + +1. If the node binary is `nym-node` (not legacy `nym-mixnode` or `nym-gateway`) +2. If [Terms & Conditions](../nodes/nym-node/setup.mdx#terms--conditions) are accepted. +3. Version of `nym-node` binary + +**The `config_score` parameter calculation formula:** + + +> **config_score = is_tc_accepted \* is_nym-node_binary \* ( 0.995 ^ ( ( X * versions_behind) ^ 1.65 ) )** + + +First two points have binary values of either 0 or 1, with a following logic: + +| **Run `nym-node` binary** | **T&C's accepted** | **Value** | | :-- | :-- | ---: | +| True | True | 1 | | True | False | 0 | | False | True | 0 | | False | False | 0 | -| True | True | 1 | +Only if both conditions above are `True` the node can have any chance to be selected, as otherwise the probability will always be 0. -The entire active set selection probablity: +**The `version_behind` parameter in `config_score` calculation** + +From release `2024.14-crunch` (`nym-node v1.2.0`), the `config_score` parameter takes into account also nodes version. Current version is the +one marked as `Latest` in our repository. From that one we count the parameter `version_behind`, where every version back the number of `versions_behind` increases by 1 in this formula: -> **active_set_selection_probability = config_score \* stake_saturation \* node_performance ^ 20** +> **0.995 ^ ( ( X * versions_behind ) ^ 1.65 )** +> +> where:
+> **X = 1; for patches**
+> **X = 10; for minor versions**
+> **X = 100; for major versions**
-For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1 and not 0): +> The exact parameters are live accessible on [`/v1/status/config-score-details`](https://validator.nymtech.net/api/swagger/index.html#/Status/config_score_details). + +Our versioning convention is: `major_version . minor_version . patch` + +For example `nym-node` on version `1.2.0` is on 1st major version, 2nd minor and 0 patches. See the the table and graph below: + +| **Version behind** | **Patches (X = 1)** | **Minor versions (X = 10)** | **Major versions (X = 100)** | +| :-- | --: | --: | --: | +| 0 (current version) | 1.0 | 1.0 | 1.0 | +| 1 | 0.995 | 0.7994 | 0.0000 | +| 2 | 0.9844 | 0.4953 | 0.0000 | +| 3 | 0.9698 | 0.2536 | 0.0000 | +| 4 | 0.9518 | 0.1102 | 0.0000 | +| 5 | 0.9311 | 0.0413 | 0.0000 | + + +![](/images/operators/tokenomics/reward_version_graph.png) + +As you can see on above, the algorithm is designed to give maximum probability (`1`) to the latest version and exponentialy dicrease the probability to non-upgraded nodes where the more important version the node is behind, the faster the cliff. This eliminates any older nodes despite their saturation and performance to take place in the Rewarded set and gives a priority to the operators running up-to-date nodes, ensuring as strong network as possible. + + +#### Performance Calculation + +Performance is measured by Nym Network Monitor which sends thousands of packages through different routes every 15 minutes and measures how many were dropped on the way. Test result represents percentage of packets succesfully returned (can be anything between 0 and 1). Performance value is nodes average of these tests in last 24h. + +Good performance is much more essential than [stake saturation](../tokenomics.mdx#stake-saturation), because it's lifted to 20th power in the selection formula. + +For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1):
- + > node_1 = 1.00 ^ 20 \* 1.0 = 1
> node_2 = 1.00 ^ 20 \* 0.5 = 0.5
> node_3 = 0.99 ^ 20 \* 1.0 = 0.818
@@ -201,7 +266,7 @@ $33\% - 67\%$ ## Roadmap -We are working on the final architecture of [*Fair Mixnet*](#fair-mixnet) tokenomics implementation. The current design is called [*Naive rewarding*](#naive-rewarding). This is an intermediate step, allowing operators to migrate to `nym-node` in Mixnet smart contract and for the first time recieve delegations and earn rewards for any `nym-node` functionality, in opposite to the past system, where only Mixnodes were able to recieve delegations and rewards. +We are working on the final architecture of [*Fair Mixnet*](#fair-mixnet) tokenomics implementation. The current design is called [*Naive rewarding*](#naive-rewarding). This is an intermediate step, expecting operators to migrate to `nym-node` in Mixnet smart contract and be able to recieve delegations and earn rewards for any `nym-node` functionality, in opposite to the past system, where only Mixnodes were able to recieve delegations and rewards. On November 5th, we presented a release roadmap in live [Operators Townhall](https://www.youtube.com/watch?v=3G1pJqvO2VM) where we explained in detail the steps of Nym node and tokenomics development and the effect it will have on node operators and put it into a rough timeline. diff --git a/documentation/docs/public/images/operators/tokenomics/reward_version_graph.png b/documentation/docs/public/images/operators/tokenomics/reward_version_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..57948742702e50ff96e9701be5a00c3cbe9e37d1 GIT binary patch literal 103951 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
    HqJWUdu=4{O1w&$e?~+L{Yi&C& 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�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 ZJiqv_>WvQPgBAR5!Z-`l_%U Date: Wed, 11 Dec 2024 16:00:26 +0000 Subject: [PATCH 39/80] nymvpncli guide (#5243) * nymvpn guide * move nymvpn guide frm operators -> developers --- .../docs/pages/developers/_meta.json | 1 + .../docs/pages/developers/nymvpncli.mdx | 278 ++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 documentation/docs/pages/developers/nymvpncli.mdx diff --git a/documentation/docs/pages/developers/_meta.json b/documentation/docs/pages/developers/_meta.json index d0103c08a5..b3e5134275 100644 --- a/documentation/docs/pages/developers/_meta.json +++ b/documentation/docs/pages/developers/_meta.json @@ -5,6 +5,7 @@ "integrations": "Integration Options", "clients": "Clients", "tools": "Tools", + "nymvpncli": "Nym VPN CLI", "chain": "Interacting with Nyx", "--": { "type": "separator" diff --git a/documentation/docs/pages/developers/nymvpncli.mdx b/documentation/docs/pages/developers/nymvpncli.mdx new file mode 100644 index 0000000000..d8687cc5b9 --- /dev/null +++ b/documentation/docs/pages/developers/nymvpncli.mdx @@ -0,0 +1,278 @@ +import { Callout } from 'nextra/components' + +# Nym VPN CLI + +This is a short guide to setting up and using the `nym-vpnc` tool, which is used in conjunction with the `nym-vpnd` daemon. + + + These binaries have superceded the older `nym-vpn-cli` binary. This still operates for the moment as it is being used in testing scenarios but will go out of date quickly. + + +Download and run instructions for the GUIs can be found [here](https://nymvpn.com/en/download/linux). + +## Download & Extract Binary +Check the [release page](https://github.com/nymtech/nym-vpn-client/releases/) page for the latest release version and modify the instructions accordingly. These instructions use the latest as of the time of writing. +```sh +wget -q https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-core-v1.1.0-beta.3/nym-vpn-core-v1.1.0-beta.3_.tar.gz && +tar -xzf nym-vpn-core-v1.1.0-beta.3_.tar.gz && +cd nym-vpn-core-v1.1.0-beta.3_/ && +chmod u+x * +``` + +## Build from Source +### Prerequisites +All operating systems require both [Rust](https://www.rust-lang.org/tools/install) and [Go](https://go.dev/doc/install). + +**Arch specific packages:** +```sh +yay -S gcc make protobuf base-devel clang +``` + +**Ubuntu24 specific packages:** +```sh +apt install gcc make protobuf-compiler pkconfig libdbus-1-dev build-essential clang +``` + + + Older Debian/Ubuntu versions need to manually install `protobuf-compiler` >= v3.21.12 + + +### Clone & `make` +```sh +git clone https://github.com/nymtech/nym-vpn-client.git +cd nym-vpn-client/ +make +``` + +## Mnemonic Generation +Create an account at [nymvpn.com](nymvpn.com) to obtain your mnemonic. + +## Start the daemon +```sh +sudo ./PATH/TO/nym-vpnd +``` + +If you are running for the first time you will see the following: + +```sh +2024-12-11T11:03:58.202159Z INFO nym_vpnd::environment: Setting up environment by discovering the network: mainnet +2024-12-11T11:03:58.202205Z INFO nym_vpn_network_config::discovery: No discovery file found, writing default discovery file +2024-12-11T11:03:59.905505Z INFO nym_vpnd::command_interface::start: Starting command interface +2024-12-11T11:03:59.905660Z INFO nym_vpnd::service::vpn_service: Starting VPN service +2024-12-11T11:03:59.905879Z INFO nym_vpnd::command_interface::start: Starting socket listener on: /var/run/nym-vpn.sock +2024-12-11T11:03:59.906227Z INFO nym_vpn_account_controller::controller: Starting account controller +2024-12-11T11:03:59.906285Z INFO nym_vpn_account_controller::controller: Account controller: data directory: "/var/lib/nym-vpnd/mainnet" +2024-12-11T11:03:59.906313Z INFO nym_vpn_account_controller::controller: Account controller: credential mode: false +2024-12-11T11:03:59.913215Z INFO nym_vpnd::command_interface::listener: Removed previous command interface socket: "/var/run/nym-vpn.sock" +2024-12-11T11:03:59.977206Z INFO nym_vpnd::service::vpn_service: VPN service initialized successfully +2024-12-11T11:03:59.979246Z INFO nym_vpn_account_controller::controller: Account id: (unset) +2024-12-11T11:03:59.979265Z INFO nym_vpn_account_controller::controller: Device id: BZWA5MRnEvRYD8WWrH9KULdj2Q1uTssu6idjgWFae9dv +2024-12-11T11:03:59.979762Z INFO nym_vpn_account_controller::storage: Ticketbooks stored: 0 +2024-12-11T11:03:59.982125Z INFO nym_vpn_account_controller::controller: Received command: UpdateAccountState +2024-12-11T11:03:59.982181Z INFO nym_vpn_account_controller::shared_state: Setting mnemonic state to NotStored +2024-12-11T11:03:59.982200Z WARN nym_vpn_account_controller::commands: Returning error: NoAccountStored +2024-12-11T11:03:59.982218Z INFO nym_vpn_account_controller::controller: Received command: UpdateDeviceState +2024-12-11T11:03:59.982230Z INFO nym_vpn_account_controller::shared_state: Setting mnemonic state to NotStored +2024-12-11T11:03:59.982240Z WARN nym_vpn_account_controller::commands: Returning error: NoAccountStored +``` + +Ignore the `NoAccountStored` errors: these will disappear after the next step. **Leave the daemon running and run the following commands in another terminal window** or create an init file for `nym-vpnd`. + +## Run VPN +We have to first store the account we have created online: +```sh +./PATH/TO/nym-vpn-cli store-account --mnemonic "" +``` + +You will see this registration in the daemon logs: + +```sh +2024-12-11T11:04:31.918455Z INFO grpc_vpnd: ← StoreAccount () +2024-12-11T11:04:31.919296Z INFO nym_vpnd::service::vpn_service: Storing account +2024-12-11T11:04:31.919531Z INFO nym_vpn_store::mnemonic::on_disk: Storing mnemonic to: /var/lib/nym-vpnd/mainnet/mnemonic.json +2024-12-11T11:04:31.920327Z INFO nym_vpn_account_controller::controller: Received command: UpdateAccountState +2024-12-11T11:04:31.950720Z INFO nym_vpn_account_controller::shared_state: Setting mnemonic state to Stored { id: "n1nghj6qnmfww22tq6wyntnf709lr90qjem0uezz" } +2024-12-11T11:04:34.616249Z INFO nym_vpn_account_controller::shared_state: Setting account to Registered +2024-12-11T11:04:34.616363Z INFO nym_vpn_account_controller::shared_state: Setting account summary to AccountSummary { account: Active, subscription: Active, device_summary: DeviceSummary { active: 0, max: 10, remaining: 10 }, fair_usage: FairUsage { used_gb: None, limit_gb: None, resets_on_utc: Some("2025-01-09 15:43:37.223Z") } } +2024-12-11T11:04:34.981875Z INFO nym_vpn_account_controller::controller: Received command: RegisterDevice +2024-12-11T11:04:35.008575Z INFO register_device: nym_vpn_account_controller::shared_state: Setting device registration result to InProgress id=09876a3a +2024-12-11T11:04:35.008611Z INFO register_device: nym_vpn_account_controller::commands::register_device: Registering device: Device { identity_key: BZWA5MRnEvRYD8WWrH9KULdj2Q1uTssu6idjgWFae9dv } id=09876a3a +2024-12-11T11:04:36.765850Z INFO register_device: nym_vpn_account_controller::commands::register_device: Response: NymVpnDevice { + created_on_utc: "2024-12-11 11:04:36.432Z", + last_updated_utc: "2024-12-11 11:04:36.432Z", + device_identity_key: "BZWA5MRnEvRYD8WWrH9KULdj2Q1uTssu6idjgWFae9dv", + status: Active, +} id=09876a3a +2024-12-11T11:04:36.765998Z INFO register_device: nym_vpn_account_controller::commands::register_device: Device registered: BZWA5MRnEvRYD8WWrH9KULdj2Q1uTssu6idjgWFae9dv id=09876a3a +``` + +You can then connect `nym-vpnc` (in this case, with 2 hop wireguard mode enabled): +```sh +./PATH/TO/nym-vpn-cli nym-vpnc connect --enable-two-hop +``` + +Which shows as such in the daemon logs: +```sh +2024-12-11T11:05:25.727784Z INFO grpc_vpnd: ← VpnConnect () +2024-12-11T11:05:25.728107Z INFO grpc_vpnd: nym_vpnd::command_interface::listener: Got connect request: Request { metadata: MetadataMap { headers: {"te": "trailers", "content-type": "application/grpc", "user-agent": "tonic/0.11.0"} }, message: ConnectRequest { entry: None, exit: None, dns: None, disable_routing: false, enable_two_hop: true, netstack: false, disable_poisson_rate: false, disable_background_cover_traffic: false, enable_credentials_mode: false, user_agent: Some(UserAgent { application: "nym-vpnc", version: "1.1.0-beta.3 (1.1.0-beta.3)", platform: "Manjaro Linux; Linux 24.2.0 Manjaro Linux; x86_64", git_commit: "59c0714f1dac1a2d8bf77f3d2705a5c9bb57a5be (59c0714f1dac1a2d8bf77f3d2705a5c9bb57a5be)" }), min_mixnode_performance: None, min_gateway_mixnet_performance: None, min_gateway_vpn_performance: None }, extensions: Extensions } +2024-12-11T11:05:25.728225Z INFO grpc_vpnd: nym_vpnd::command_interface::connection_handler: Starting VPN +2024-12-11T11:05:25.728430Z INFO nym_vpnd::service::vpn_service: Using entry point: None +2024-12-11T11:05:25.728450Z INFO nym_vpnd::service::vpn_service: Using exit point: None +2024-12-11T11:05:25.728468Z INFO nym_vpnd::service::vpn_service: Using options: ConnectOptions { dns: None, disable_routing: false, enable_two_hop: true, netstack: false, disable_poisson_rate: false, disable_background_cover_traffic: true, enable_credentials_mode: false, min_mixnode_performance: None, min_gateway_mixnet_performance: None, min_gateway_vpn_performance: None } +2024-12-11T11:05:25.729112Z INFO nym_vpnd::service::config: Config file updated at "/etc/nym/mainnet/nym-vpnd.toml" +2024-12-11T11:05:25.729161Z INFO nym_vpnd::service::vpn_service: Using config: entry point: Random, exit point: Random +2024-12-11T11:05:25.729611Z INFO nym_vpnd::service::vpn_service: Tunnel event: Connecting +2024-12-11T11:05:25.730108Z INFO nym_gateway_directory::gateway_client: Fetching gateways from nym-vpn-api... +2024-12-11T11:05:26.387699Z INFO nym_vpn_lib::tunnel_state_machine::tunnel::gateway_selector: Found 113 entry gateways +2024-12-11T11:05:26.387744Z INFO nym_vpn_lib::tunnel_state_machine::tunnel::gateway_selector: Found 113 exit gateways +2024-12-11T11:05:26.387752Z INFO nym_gateway_directory::entries::exit_point: Selecting a random exit gateway +2024-12-11T11:05:26.387794Z INFO nym_vpn_lib::tunnel_state_machine::tunnel::gateway_selector: Using entry gateway: CcYinhLeFU8n6xs78FG6Rz3wvosGTCU2hLB1CZyfkMVe, location: IN, performance: 96% +2024-12-11T11:05:26.387814Z INFO nym_vpn_lib::tunnel_state_machine::tunnel::gateway_se +lector: Using exit gateway: Atcji22Wnfwi6nEkGC5BmgbqNPLYdhx5r4NxTqXAzFeq, location: GB, performance: 99% +2024-12-11T11:05:26.387852Z INFO nym_vpn_lib::tunnel_state_machine::tunnel::gateway_selector: Using exit router address 3MJSnmUeH54a7DJ8C4C8oZPkCjtENSfwcMLJ39zUk9Ys.59h9HKGTM4MPXVJRDfaJYFg1aoAdeBGjLHMFxQ6fBsfF@Atcji22Wnfwi6nEkGC5BmgbqNPLYdhx5r4NxTqXAzFeq +2024-12-11T11:05:26.388144Z INFO nym_vpn_lib::mixnet::connect: mixnet client poisson rate limiting: disabled +2024-12-11T11:05:26.388154Z INFO nym_vpn_lib::mixnet::connect: mixnet client background loop cover traffic stream: disabled +2024-12-11T11:05:26.388158Z INFO nym_vpn_lib::mixnet::connect: mixnet client minimum mixnode performance: 50 +2024-12-11T11:05:26.388163Z INFO nym_vpn_lib::mixnet::connect: mixnet client minimum gateway performance: 50 +2024-12-11T11:05:26.388434Z INFO nym_client_core::client::base_client::non_wasm_helpers: loading existing surb database +2024-12-11T11:05:26.391135Z INFO nym_client_core_surb_storage::backend::fs_backend::manager: Database migration finished! +2024-12-11T11:05:26.503966Z INFO nym_client_core::init::helpers: nym-api reports 195 valid gateways +2024-12-11T11:05:27.888231Z INFO nym_client_core::client::base_client: Starting nym client +2024-12-11T11:05:27.894924Z INFO nym_client_core::client::base_client: Starting statistics control... +2024-12-11T11:05:27.895105Z INFO nym_client_core::client::base_client: Obtaining initial network topology +2024-12-11T11:05:28.047235Z INFO nym_client_core::client::base_client: Starting topology refresher... +2024-12-11T11:05:29.024114Z INFO perform_initial_authentication: nym_gateway_client::bandwidth: remaining bandwidth: 0.00 B gateway=CcYinhLeFU8n6xs78FG6Rz3wvosGTCU2hLB1CZyfkMVe gateway_address=wss://gateway4.lunardao.net:9001/ +2024-12-11T11:05:29.024375Z INFO nym_gateway_client::client: Claiming more bandwidth with existing credentials. Stop the process now if you don't want that to happen. +2024-12-11T11:05:29.024413Z WARN nym_gateway_client::client: Not enough bandwidth. Trying to get more bandwidth, this might take a while +2024-12-11T11:05:29.024430Z INFO nym_gateway_client::client: The client is running in disabled credentials mode - attempting to claim bandwidth without a credential +2024-12-11T11:05:29.171514Z INFO nym_gateway_client::client: managed to claim testnet bandwidth +2024-12-11T11:05:29.175555Z INFO nym_client_core::client::base_client: Starting received messages buffer controller... +2024-12-11T11:05:29.175648Z INFO nym_client_core::client::base_client: Starting mix traffic controller... +2024-12-11T11:05:29.175698Z INFO nym_client_core::client::base_client: Starting real traffic stream... +2024-12-11T11:05:29.176182Z INFO nym_task::manager: Starting status message listener +2024-12-11T11:05:29.189388Z INFO nym_vpn_lib::bandwidth_controller: Registering with wireguard gateway +2024-12-11T11:05:29.189449Z INFO nym_gateway_directory::gateway_client: Fetching gateway ip from nym-vpn-api... +2024-12-11T11:05:32.373749Z INFO nym_vpn_lib::bandwidth_controller: Registering with wireguard gateway +2024-12-11T11:05:32.373860Z INFO nym_gateway_directory::gateway_client: Fetching gateway ip from nym-vpn-api... +2024-12-11T11:05:36.675679Z INFO nym_vpn_lib::tunnel_state_machine::tunnel_monitor: Created entry tun device: tun0 +2024-12-11T11:05:36.679774Z INFO nym_vpn_lib::tunnel_state_machine::tunnel_monitor: Created exit tun device: tun1 +2024-12-11T11:05:36.681435Z INFO nym_dns: Setting DNS servers: Tunnel DNS: {1.1.1.1, 1.0.0.1, 2606:4700:4700::1111, 2606:4700:4700::1001} Non-tunnel DNS: {} +2024-12-11T11:05:36.690464Z INFO nym_vpnd::service::vpn_service: Tunnel event: Connecting WireGuard tunnel with entry 213.210.21.111:51822 and exit 45.140.167.83:51822 +2024-12-11T11:05:36.690577Z INFO nym_vpnd::service::vpn_service: Tunnel event: Connected WireGuard tunnel with entry 213.210.21.111:51822 and exit 45.140.167.83:51822 +2024-12-11T11:05:37.734615Z INFO nym_wg_gateway_client: Remaining wireguard bandwidth with gateway CcYinhLeFU8n6xs78FG6Rz3wvosGTCU2hLB1CZyfkMVe for today: 256000.00 MB +2024-12-11T11:05:39.240143Z INFO nym_wg_gateway_client: Remaining wireguard bandwidth with gateway Atcji22Wnfwi6nEkGC5BmgbqNPLYdhx5r4NxTqXAzFeq for today: 256000.00 MB +``` + +You should see the `Remaining wireguard bandwidth` decrease as you use your allowance. + +There are a lot of configuration options available to you regarding how to connect: + +```sh +❯ ./nym-vpn-core/target/debug/nym-vpnc connect --help +Connect to the Nym network + +Usage: nym-vpnc connect [OPTIONS] + +Options: + --entry-gateway-id + Mixnet public ID of the entry gateway + --entry-gateway-country + Auto-select entry gateway by country ISO + --entry-gateway-low-latency + Auto-select entry gateway by latency + --entry-gateway-random + Auto-select entry gateway randomly + --exit-router-address + Mixnet recipient address + --exit-gateway-id + Mixnet public ID of the exit gateway + --exit-gateway-country + Auto-select exit gateway by country ISO + --exit-gateway-random + Auto-select exit gateway randomly + --dns + Set the IP address of the DNS server to use + --disable-routing + Disable routing all traffic through the nym TUN device. When the flag is set, + the nym TUN device will be created, but to route traffic through it you will + need to do it manually, e.g. ping -Itun0 + --enable-two-hop + Enable two-hop wireguard traffic. This means that traffic jumps directly from + entry gateway to exit gateway using Wireguard protocol + -w, --wait-until-connected + Blocks until the connection is established or failed + --netstack + Use netstack based implementation for two-hop wireguard + --enable-credentials-mode + Enable credentials mode + --min-gateway-mixnet-performance + An integer between 0 and 100 representing the minimum gateway performance + required to consider a gateway for routing traffic + --min-gateway-vpn-performance + An integer between 0 and 100 representing the minimum gateway performance + required to consider a gateway for routing traffic + -h, --help + Print help +``` + +## Command Reference +```sh +❯ ./nym-vpn-core/target/debug/nym-vpnc --help +NymVPN commandline client + +Usage: nym-vpnc [OPTIONS] + +Commands: + connect Connect to the Nym network + disconnect Disconnect from the Nym network + status Get the current status of the connection + info Get info about the current client. Things like version and + network details + set-network Set the network to be used. This requires a restart of the + daemon (`nym-vpnd`) + store-account Store the account recovery phrase + is-account-stored Check if the account is stored + forget-account Forget the stored account. This removes the stores recovery + phrase, device and mixnet keys, stored local credentials, etc + get-account-id Get the account ID + get-account-state Get the current account state + get-account-links Get URLs for managing your nym-vpn account + get-device-id Get the device ID + list-entry-gateways List the set of entry gateways for mixnet mode + list-exit-gateways List the set of exit gateways for mixnet mode + list-vpn-gateways List the set of entry and exit gateways for dVPN mode + list-entry-countries List the set of countries with available entry gateways for + mixnet mode + list-exit-countries List the set of countries with available exit gateways for + mixnet mode + list-vpn-countries List the set of countries with available entry and exit + gateways for dVPN mode + help Print this message or the help of the given subcommand(s) + +Options: + --http Use HTTP instead of socket file for IPC with the daemon + --verbose + -h, --help Print help + -V, --version Print version +``` + +```sh +❯ ./nym-vpn-core/target/debug/nym-vpnd --help +NymVPN daemon + +Usage: nym-vpnd [OPTIONS] + +Options: + -c, --config-env-file + Path pointing to an env file describing the network + --enable-http-listener + + --disable-socket-listener + + --run-as-service + + -h, --help + Print help + -V, --version + Print version +``` From a3368931161a945777c25543d979ab5c465a2574 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 11 Dec 2024 16:30:17 +0000 Subject: [PATCH 40/80] Max/openapi docs (#5219) * first pass redoc apis * new landing + component update * added intro * new structure * link list * add sandbox sdk * remove theme colours * revert credit to ticket & ticketbook and actually get all the instances to replace * Max/zknym doc tweak (#5223) * revert credit to ticket & ticketbook * revert credit to ticket & ticketbook and actually get all the instances to replace * theme tweak to widen text area * theme redoc component * tweak padding topbar * modified socks5 page to be in line with websocket client * modify h size of autodoc generated command info * tweak script to build from master * add autodoc to workspace * auto commit generated command files * clean autodoc-generated-markdown in script * auto commit generated command files * tweak works * clippy * fix borked toml from cherrypick * remove rm command * auto commit generated command files * blow away images * auto commit generated command files * remove redoc for nymapi for the moment but retain everything else * fix double paste * temp remove sandbox --- Cargo.lock | 23 +- Cargo.toml | 2 +- documentation/.gitignore | 1 + .../nym-api-build-info-help.md | 9 - .../nym-api-build-info.md | 13 - .../nym-api-commands.md | 95 -- .../nym-api-init-help.md | 25 - .../nym-client-build-info-help.md | 9 - .../nym-client-build-info.md | 13 - .../nym-client-completions-help.md | 11 - .../nym-client-generate-fig-spec-help.md | 8 - .../nym-client-generate-fig-spec.md | 585 --------- .../nym-client-import-credential-help.md | 15 - .../nym-client-init-help.md | 27 - .../nym-client-list-gateways-help.md | 10 - .../nym-client-run-help.md | 21 - .../nym-client-switch-gateway-help.md | 10 - .../nym-node-bonding-information-help.md | 12 - .../nym-node-bonding-information.md | 3 - .../nym-node-build-info-help.md | 9 - .../nym-node-build-info.md | 13 - .../nym-node-commands.md | 237 ---- .../nym-node-migrate-help.md | 68 -- .../nym-node-node-details-help.md | 11 - .../nym-node-node-details.md | 3 - .../nym-node-sign-help.md | 13 - .../nym-socks5-client-add-gateway-help.md | 15 - .../nym-socks5-client-build-info-help.md | 9 - .../nym-socks5-client-build-info.md | 13 - .../nym-socks5-client-completions-help.md | 11 - ...ym-socks5-client-generate-fig-spec-help.md | 8 - .../nym-socks5-client-generate-fig-spec.md | 619 ---------- ...ym-socks5-client-import-credential-help.md | 15 - .../nym-socks5-client-init-help.md | 44 - .../nym-socks5-client-list-gateways-help.md | 10 - .../nym-socks5-client-run-help.md | 34 - .../nymvisor-add-upgrade-help.md | 19 - .../nymvisor-build-info-help.md | 9 - .../nymvisor-build-info.md | 13 - .../nymvisor-commands.md | 154 --- .../nymvisor-config-help.md | 9 - .../nymvisor-daemon-build-info-help.md | 9 - .../nymvisor-init-help.md | 48 - documentation/autodoc/src/main.rs | 15 +- documentation/docs/components/api-table.tsx | 52 + .../docs/components/landing-page.tsx | 30 +- .../outputs/api-scraping-outputs/time-now.md | 2 +- .../nym-api-build-info-help.md | 1 - .../command-outputs/nym-api-build-info.md | 15 +- .../command-outputs/nym-api-init-help.md | 1 - .../outputs/command-outputs/nym-api-init.md | 2 +- .../command-outputs/nym-client-build-info.md | 14 +- .../nym-node-bonding-information.md | 2 +- .../command-outputs/nym-node-build-info.md | 14 +- .../command-outputs/nym-node-node-details.md | 2 +- .../nym-socks5-client-build-info.md | 14 +- .../command-outputs/nymvisor-build-info.md | 14 +- documentation/docs/next-env.d.ts | 2 +- documentation/docs/package.json | 2 + documentation/docs/pages/_meta.json | 4 + documentation/docs/pages/apis/_meta.json | 12 + .../docs/pages/apis/cosmos-sdk-nyx.mdx | 3 + .../pages/apis/cosmos-sdk-nyx/mainnet.mdx | 18 + .../pages/apis/cosmos-sdk-nyx/sandbox.mdx | 16 + .../docs/pages/apis/explorer-api.mdx | 8 + .../docs/pages/apis/explorer-api/mainnet.mdx | 17 + .../docs/pages/apis/explorer-api/sandbox.mdx | 16 + .../docs/pages/apis/introduction.mdx | 5 + documentation/docs/pages/apis/ns-api.mdx | 11 + .../docs/pages/apis/ns-api/mainnet.mdx | 16 + .../docs/pages/apis/ns-api/sandbox.mdx | 16 + documentation/docs/pages/apis/nym-api.mdx | 5 + .../docs/pages/apis/nym-api/mainnet.mdx | 10 + .../docs/pages/developers/clients.mdx | 4 + .../docs/pages/developers/clients/socks5.mdx | 136 --- .../developers/clients/socks5/_meta.json | 6 + .../developers/clients/socks5/commands.mdx | 30 +- .../developers/clients/socks5/config.mdx | 29 + .../pages/developers/clients/socks5/setup.mdx | 41 + .../pages/developers/clients/socks5/usage.mdx | 62 + .../developers/clients/websocket/_meta.json | 7 + .../developers/clients/websocket/commands.mdx | 30 +- .../developers/tools/nym-cli/commands.mdx | 64 +- .../cryptography/zk-nym/double-spend-prot.mdx | 6 +- .../cryptography/zk-nym/rerandomise.mdx | 13 +- .../cryptography/zk-nym/unlinkability.mdx | 8 +- .../cryptography/zk-nym/zk-nym-overview.mdx | 16 +- documentation/docs/pages/styles.css | 59 +- documentation/docs/pnpm-lock.yaml | 1047 +++++++++++++++++ documentation/docs/public/favicon.ico | Bin 0 -> 1793 bytes documentation/docs/theme.config.tsx | 16 +- documentation/scripts/next-scripts/autodoc.sh | 6 +- 92 files changed, 1595 insertions(+), 2609 deletions(-) delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-api-build-info-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-api-build-info.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-api-commands.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-api-init-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-build-info-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-build-info.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-completions-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-import-credential-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-init-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-list-gateways-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-run-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-client-switch-gateway-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-build-info-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-build-info.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-commands.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-migrate-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-node-details-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-node-details.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-node-sign-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-add-gateway-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-completions-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-import-credential-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-init-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-list-gateways-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-run-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nymvisor-add-upgrade-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nymvisor-commands.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nymvisor-config-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nymvisor-daemon-build-info-help.md delete mode 100644 documentation/autodoc/autodoc-generated-markdown/nymvisor-init-help.md create mode 100644 documentation/docs/components/api-table.tsx create mode 100644 documentation/docs/pages/apis/_meta.json create mode 100644 documentation/docs/pages/apis/cosmos-sdk-nyx.mdx create mode 100644 documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx create mode 100644 documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx create mode 100644 documentation/docs/pages/apis/explorer-api.mdx create mode 100644 documentation/docs/pages/apis/explorer-api/mainnet.mdx create mode 100644 documentation/docs/pages/apis/explorer-api/sandbox.mdx create mode 100644 documentation/docs/pages/apis/introduction.mdx create mode 100644 documentation/docs/pages/apis/ns-api.mdx create mode 100644 documentation/docs/pages/apis/ns-api/mainnet.mdx create mode 100644 documentation/docs/pages/apis/ns-api/sandbox.mdx create mode 100644 documentation/docs/pages/apis/nym-api.mdx create mode 100644 documentation/docs/pages/apis/nym-api/mainnet.mdx create mode 100644 documentation/docs/pages/developers/clients/socks5/_meta.json create mode 100644 documentation/docs/pages/developers/clients/socks5/config.mdx create mode 100644 documentation/docs/pages/developers/clients/socks5/setup.mdx create mode 100644 documentation/docs/pages/developers/clients/socks5/usage.mdx create mode 100644 documentation/docs/pages/developers/clients/websocket/_meta.json create mode 100644 documentation/docs/public/favicon.ico diff --git a/Cargo.lock b/Cargo.lock index f90b3493ab..4b69324120 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,6 +430,14 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "autodoc" +version = "0.1.0" +dependencies = [ + "env_logger 0.11.5", + "log", +] + [[package]] name = "axum" version = "0.6.20" @@ -2354,6 +2362,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime 2.1.0", + "log", +] + [[package]] name = "envy" version = "0.4.2" @@ -7523,7 +7544,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ - "env_logger", + "env_logger 0.7.1", "log", ] diff --git a/Cargo.toml b/Cargo.toml index 7fcf6070d4..d833eb8fe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,7 @@ members = [ "common/wasm/utils", "common/wireguard", "common/wireguard-types", - # "documentation/autodoc", + "documentation/autodoc", "explorer-api", "explorer-api/explorer-api-requests", "explorer-api/explorer-client", diff --git a/documentation/.gitignore b/documentation/.gitignore index 95b579c427..4329a0d6c0 100644 --- a/documentation/.gitignore +++ b/documentation/.gitignore @@ -1 +1,2 @@ todo.md +scripts/generate-api diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-api-build-info-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-api-build-info-help.md deleted file mode 100644 index db52a92b4e..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-api-build-info-help.md +++ /dev/null @@ -1,9 +0,0 @@ -```sh -Show build information of this binary - -Usage: nym-api build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-api-build-info.md b/documentation/autodoc/autodoc-generated-markdown/nym-api-build-info.md deleted file mode 100644 index c04a06ee09..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-api-build-info.md +++ /dev/null @@ -1,13 +0,0 @@ -```sh - -Binary Name: nym-api -Build Timestamp: 2024-10-09T13:56:14.428750844Z -Build Version: 1.1.42 -Commit SHA: fac373c1db4fa5389ba61de7943c77023467bccb -Commit Date: 2024-10-09T14:59:40.000000000+02:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable -cargo Profile: release - -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-api-commands.md b/documentation/autodoc/autodoc-generated-markdown/nym-api-commands.md deleted file mode 100644 index 185d88277b..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-api-commands.md +++ /dev/null @@ -1,95 +0,0 @@ -# `nym-api` Binary Commands (Autogenerated) - -These docs are autogenerated by the [`autodocs`](https://github.com/nymtech/nym/tree/max/new-docs-framework/documentation/autodoc) script. -```sh -2024-10-29T09:51:44.008469Z  INFO nym-api/src/main.rs:41: Starting nym api... -Usage: nym-api [OPTIONS] - -Commands: - init Initialise a Nym Api instance with persistent config.toml file - run Run the Nym Api with provided configuration optionally overriding set parameters - build-info Show build information of this binary - help Print this message or the help of the given subcommand(s) - -Options: - -c, --config-env-file - Path pointing to an env file that configures the Nym API [env: NYMAPI_CONFIG_ENV_FILE_ARG=] - --no-banner - A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops) [env: NYMAPI_NO_BANNER_ARG=] - -h, --help - Print help - -V, --version - Print version -``` - -### `init` -```sh -2024-10-29T09:51:44.013292Z  INFO nym-api/src/main.rs:41: Starting nym api... -Initialise a Nym Api instance with persistent config.toml file - -Usage: nym-api init [OPTIONS] - -Options: - --id - Id of the nym-api we want to initialise. if unspecified, a default value will be used. default: "default" [env: NYMAPI_ID_ARG=] [default: default] - -m, --enable-monitor - Specifies whether network monitoring is enabled on this API default: false [env: NYMAPI_ENABLE_MONITOR_ARG=] - -r, --enable-rewarding - Specifies whether network rewarding is enabled on this API default: false [env: NYMAPI_ENABLE_REWARDING_ARG=] - --nyxd-validator - Endpoint to nyxd instance used for contract information. default: http://localhost:26657 [env: NYMAPI_NYXD_VALIDATOR_ARG=] - --mnemonic - Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions default: None [env: NYMAPI_MNEMONIC_ARG=] - --enable-zk-nym - Flag to indicate whether credential signer authority is enabled on this API default: false [env: NYMAPI_ENABLE_ZK_NYM_ARG=] - --announce-address - Announced address that is going to be put in the DKG contract where zk-nym clients will connect to obtain their credentials default: None [env: NYMAPI_ANNOUNCE_ADDRESS_NYM_ARG=] - --monitor-credentials-mode - Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement [env: NYMAPI_MONITOR_CREDENTIALS_MODE_ARG=] - --bind-address - Socket address this api will use for binding its http API. default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release` - -h, --help - Print help -``` - -### `run` -```sh -2024-10-29T09:51:44.032688Z  INFO nym-api/src/main.rs:41: Starting nym api... -Run the Nym Api with provided configuration optionally overriding set parameters - -Usage: nym-api run [OPTIONS] - -Options: - --id - Id of the nym-api we want to run.if unspecified, a default value will be used. default: "default" [env: NYMAPI_ID_ARG=] [default: default] - -m, --enable-monitor - Specifies whether network monitoring is enabled on this API default: None - config value will be used instead [env: NYMAPI_ENABLE_MONITOR_ARG=] [possible values: true, false] - -r, --enable-rewarding - Specifies whether network rewarding is enabled on this API default: None - config value will be used instead [env: NYMAPI_ENABLE_REWARDING_ARG=] [possible values: true, false] - --nyxd-validator - Endpoint to nyxd instance used for contract information. default: None - config value will be used instead [env: NYMAPI_NYXD_VALIDATOR_ARG=] - --mnemonic - Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions default: None - config value will be used instead [env: NYMAPI_MNEMONIC_ARG=] - --enable-zk-nym - Flag to indicate whether coconut signer authority is enabled on this API default: None - config value will be used instead [env: NYMAPI_ENABLE_ZK_NYM_ARG=] [possible values: true, false] - --announce-address - Announced address that is going to be put in the DKG contract where zk-nym clients will connect to obtain their credentials default: None - config value will be used instead [env: NYMAPI_ANNOUNCE_ADDRESS_ARG=] - --monitor-credentials-mode - Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement default: None - config value will be used instead [env: NYMAPI_MONITOR_CREDENTIALS_MODE_ARG=] [possible values: true, false] - --bind-address - Socket address this api will use for binding its http API. default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release` - -h, --help - Print help -``` - -### `build-info` -```sh -2024-10-29T09:51:44.037915Z  INFO nym-api/src/main.rs:41: Starting nym api... -Show build information of this binary - -Usage: nym-api build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-api-init-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-api-init-help.md deleted file mode 100644 index bd4c714a28..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-api-init-help.md +++ /dev/null @@ -1,25 +0,0 @@ -```sh -Initialise a Nym Api instance with persistent config.toml file - -Usage: nym-api init [OPTIONS] - -Options: - --id - Id of the nym-api we want to initialise. if unspecified, a default value will be used. default: "default" [default: default] - -m, --enable-monitor - Specifies whether network monitoring is enabled on this API default: false - -r, --enable-rewarding - Specifies whether network rewarding is enabled on this API default: false - --nyxd-validator - Endpoint to nyxd instance used for contract information. default: http://localhost:26657 - --mnemonic - Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions default: None - --enable-zk-nym - Flag to indicate whether credential signer authority is enabled on this API default: false - --announce-address - Announced address that is going to be put in the DKG contract where zk-nym clients will connect to obtain their credentials default: None - --monitor-credentials-mode - Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement - -h, --help - Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-build-info-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-build-info-help.md deleted file mode 100644 index f65dc4e7c4..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-build-info-help.md +++ /dev/null @@ -1,9 +0,0 @@ -```sh -Show build information of this binary - -Usage: nym-client build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-build-info.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-build-info.md deleted file mode 100644 index a0c7d2c612..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-build-info.md +++ /dev/null @@ -1,13 +0,0 @@ -```sh - -Binary Name: nym-client -Build Timestamp: 2024-10-09T13:56:14.428750844Z -Build Version: 1.1.39 -Commit SHA: fac373c1db4fa5389ba61de7943c77023467bccb -Commit Date: 2024-10-09T14:59:40.000000000+02:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable -cargo Profile: release - -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-completions-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-completions-help.md deleted file mode 100644 index f09b17f785..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-completions-help.md +++ /dev/null @@ -1,11 +0,0 @@ -```sh -Generate shell completions - -Usage: nym-client completions - -Arguments: - [possible values: bash, elvish, fish, power-shell, zsh] - -Options: - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec-help.md deleted file mode 100644 index ecce720ea9..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec-help.md +++ /dev/null @@ -1,8 +0,0 @@ -```sh -Generate Fig specification - -Usage: nym-client generate-fig-spec - -Options: - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec.md deleted file mode 100644 index 154c25a968..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-generate-fig-spec.md +++ /dev/null @@ -1,585 +0,0 @@ -```sh -const completion: Fig.Spec = { - name: "nym-native-client", - description: "Implementation of the Nym Client", - subcommands: [ - { - name: "init", - description: "Initialise a Nym client. Do this first!", - options: [ - { - name: "--id", - description: "Id of client we want to create config for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway", - description: "Id of the gateway we are going to connect to", - isRepeatable: true, - args: { - name: "gateway", - isOptional: true, - }, - }, - { - name: "--nyxd-urls", - description: "Comma separated list of rest endpoints of the nyxd validators", - hidden: true, - isRepeatable: true, - args: { - name: "nyxd_urls", - isOptional: true, - }, - }, - { - name: "--nym-apis", - description: "Comma separated list of rest endpoints of the API validators", - isRepeatable: true, - args: { - name: "nym_apis", - isOptional: true, - }, - }, - { - name: "--custom-mixnet", - description: "Path to .json file containing custom network specification", - hidden: true, - isRepeatable: true, - args: { - name: "custom_mixnet", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--enabled-credentials-mode", - description: "Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement", - hidden: true, - isRepeatable: true, - args: { - name: "enabled_credentials_mode", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: "--disable-socket", - description: "Whether to not start the websocket", - isRepeatable: true, - args: { - name: "disable_socket", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: ["-p", "--port"], - description: "Port for the socket (if applicable) to listen on in all subsequent runs", - isRepeatable: true, - args: { - name: "port", - isOptional: true, - }, - }, - { - name: "--host", - description: "Ip for the socket (if applicable) to listen for requests", - isRepeatable: true, - args: { - name: "host", - isOptional: true, - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: "--force-tls-gateway", - description: "Specifies whether the client will attempt to enforce tls connection to the desired gateway", - }, - { - name: "--latency-based-selection", - description: "Specifies whether the new gateway should be determined based by latency as opposed to being chosen uniformly", - exclusiveOn: [ - "--gateway", - ], - }, - { - name: "--fastmode", - description: "Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init", - }, - { - name: "--no-cover", - description: "Disable loop cover traffic and the Poisson rate limiter (for debugging only)", - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "run", - description: "Run the Nym client with provided configuration client optionally overriding set parameters", - options: [ - { - name: "--id", - description: "Id of client we want to create config for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway", - description: "Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened", - isRepeatable: true, - args: { - name: "gateway", - isOptional: true, - }, - }, - { - name: "--nyxd-urls", - description: "Comma separated list of rest endpoints of the nyxd validators", - hidden: true, - isRepeatable: true, - args: { - name: "nyxd_urls", - isOptional: true, - }, - }, - { - name: "--nym-apis", - description: "Comma separated list of rest endpoints of the API validators", - isRepeatable: true, - args: { - name: "nym_apis", - isOptional: true, - }, - }, - { - name: "--custom-mixnet", - description: "Path to .json file containing custom network specification", - hidden: true, - isRepeatable: true, - args: { - name: "custom_mixnet", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--enabled-credentials-mode", - description: "Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement", - hidden: true, - isRepeatable: true, - args: { - name: "enabled_credentials_mode", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: "--disable-socket", - description: "Whether to not start the websocket", - isRepeatable: true, - args: { - name: "disable_socket", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: ["-p", "--port"], - description: "Port for the socket to listen on", - isRepeatable: true, - args: { - name: "port", - isOptional: true, - }, - }, - { - name: "--host", - description: "Ip for the socket (if applicable) to listen for requests", - isRepeatable: true, - args: { - name: "host", - isOptional: true, - }, - }, - { - name: "--fastmode", - description: "Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init", - }, - { - name: "--no-cover", - description: "Disable loop cover traffic and the Poisson rate limiter (for debugging only)", - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "import-credential", - description: "Import a pre-generated credential", - options: [ - { - name: "--id", - description: "Id of client that is going to import the credential", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--credential-data", - description: "Explicitly provide the encoded credential data (as base58)", - isRepeatable: true, - args: { - name: "credential_data", - isOptional: true, - }, - }, - { - name: "--credential-path", - description: "Specifies the path to file containing binary credential data", - isRepeatable: true, - args: { - name: "credential_path", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--version", - hidden: true, - isRepeatable: true, - args: { - name: "version", - isOptional: true, - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "list-gateways", - description: "List all registered with gateways", - options: [ - { - name: "--id", - description: "Id of client we want to list gateways for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "add-gateway", - description: "Add new gateway to this client", - options: [ - { - name: "--id", - description: "Id of client we want to add gateway for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway-id", - description: "Explicitly specify id of the gateway to register with. If unspecified, a random gateway will be chosen instead", - isRepeatable: true, - args: { - name: "gateway_id", - isOptional: true, - }, - }, - { - name: "--nym-apis", - description: "Comma separated list of rest endpoints of the API validators", - isRepeatable: true, - args: { - name: "nym_apis", - isOptional: true, - }, - }, - { - name: "--custom-mixnet", - description: "Path to .json file containing custom network specification", - hidden: true, - isRepeatable: true, - args: { - name: "custom_mixnet", - isOptional: true, - template: "filepaths", - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: "--force-tls-gateway", - description: "Specifies whether the client will attempt to enforce tls connection to the desired gateway", - }, - { - name: "--latency-based-selection", - description: "Specifies whether the new gateway should be determined based by latency as opposed to being chosen uniformly", - exclusiveOn: [ - "--gateway-id", - ], - }, - { - name: "--set-active", - description: "Specify whether this new gateway should be set as the active one", - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "switch-gateway", - description: "Change the currently active gateway. Note that you must have already registered with the new gateway!", - options: [ - { - name: "--id", - description: "Id of client we want to list gateways for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway-id", - description: "Id of the gateway we want to switch to", - isRepeatable: true, - args: { - name: "gateway_id", - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "show-ticketbooks", - description: "Display information associated with the imported ticketbooks,", - options: [ - { - name: "--id", - description: "Id of client that is going to display the ticketbook information", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "build-info", - description: "Show build information of this binary", - options: [ - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "completions", - description: "Generate shell completions", - options: [ - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - args: { - name: "shell", - suggestions: [ - "bash", - "elvish", - "fish", - "power-shell", - "zsh", - ], - }, - }, - { - name: "generate-fig-spec", - description: "Generate Fig specification", - options: [ - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "help", - description: "Print this message or the help of the given subcommand(s)", - subcommands: [ - { - name: "init", - description: "Initialise a Nym client. Do this first!", - }, - { - name: "run", - description: "Run the Nym client with provided configuration client optionally overriding set parameters", - }, - { - name: "import-credential", - description: "Import a pre-generated credential", - }, - { - name: "list-gateways", - description: "List all registered with gateways", - }, - { - name: "add-gateway", - description: "Add new gateway to this client", - }, - { - name: "switch-gateway", - description: "Change the currently active gateway. Note that you must have already registered with the new gateway!", - }, - { - name: "show-ticketbooks", - description: "Display information associated with the imported ticketbooks,", - }, - { - name: "build-info", - description: "Show build information of this binary", - }, - { - name: "completions", - description: "Generate shell completions", - }, - { - name: "generate-fig-spec", - description: "Generate Fig specification", - }, - { - name: "help", - description: "Print this message or the help of the given subcommand(s)", - }, - ], - }, - ], - options: [ - { - name: ["-c", "--config-env-file"], - description: "Path pointing to an env file that configures the client", - isRepeatable: true, - args: { - name: "config_env_file", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--no-banner", - description: "Flag used for disabling the printed banner in tty", - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - { - name: ["-V", "--version"], - description: "Print version", - }, - ], -}; - -export default completion; -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-import-credential-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-import-credential-help.md deleted file mode 100644 index 2e1b652e39..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-import-credential-help.md +++ /dev/null @@ -1,15 +0,0 @@ -```sh -Import a pre-generated credential - -Usage: nym-client import-credential --id <--credential-data |--credential-path > - -Options: - --id - Id of client that is going to import the credential - --credential-data - Explicitly provide the encoded credential data (as base58) - --credential-path - Specifies the path to file containing binary credential data - -h, --help - Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-init-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-init-help.md deleted file mode 100644 index 3d58fe52c2..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-init-help.md +++ /dev/null @@ -1,27 +0,0 @@ -```sh -Initialise a Nym client. Do this first! - -Usage: nym-client init [OPTIONS] --id - -Options: - --id - Id of client we want to create config for - --gateway - Id of the gateway we are going to connect to - --force-tls-gateway - Specifies whether the client will attempt to enforce tls connection to the desired gateway - --latency-based-selection - Specifies whether the new gateway should be determined based by latency as opposed to being chosen uniformly - --nym-apis - Comma separated list of rest endpoints of the API validators - --disable-socket - Whether to not start the websocket [possible values: true, false] - -p, --port - Port for the socket (if applicable) to listen on in all subsequent runs - --host - Ip for the socket (if applicable) to listen for requests - -o, --output - [default: text] [possible values: text, json] - -h, --help - Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-list-gateways-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-list-gateways-help.md deleted file mode 100644 index 2cddc84edc..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-list-gateways-help.md +++ /dev/null @@ -1,10 +0,0 @@ -```sh -List all registered with gateways - -Usage: nym-client list-gateways [OPTIONS] --id - -Options: - --id Id of client we want to list gateways for - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-run-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-run-help.md deleted file mode 100644 index deb33eab62..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-run-help.md +++ /dev/null @@ -1,21 +0,0 @@ -```sh -Run the Nym client with provided configuration client optionally overriding set parameters - -Usage: nym-client run [OPTIONS] --id - -Options: - --id - Id of client we want to create config for - --gateway - Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened - --nym-apis - Comma separated list of rest endpoints of the API validators - --disable-socket - Whether to not start the websocket [possible values: true, false] - -p, --port - Port for the socket to listen on - --host - Ip for the socket (if applicable) to listen for requests - -h, --help - Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-client-switch-gateway-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-client-switch-gateway-help.md deleted file mode 100644 index 53277ffdd2..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-client-switch-gateway-help.md +++ /dev/null @@ -1,10 +0,0 @@ -```sh -Change the currently active gateway. Note that you must have already registered with the new gateway! - -Usage: nym-client switch-gateway --id --gateway-id - -Options: - --id Id of client we want to list gateways for - --gateway-id Id of the gateway we want to switch to - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information-help.md deleted file mode 100644 index 8a54a3c853..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information-help.md +++ /dev/null @@ -1,12 +0,0 @@ -```sh -Show bonding information of this node depending on its currently selected mode - -Usage: nym-node bonding-information [OPTIONS] - -Options: - --id Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file Path to a configuration file of this node [env: NYMNODE_CONFIG=] - --mode [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway] - -o, --output Specify the output format of the bonding information (`text` or `json`) [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information.md deleted file mode 100644 index edc2294fb2..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-bonding-information.md +++ /dev/null @@ -1,3 +0,0 @@ -```sh -2024-10-22T13:00:32.542213Z ERROR nym-node/src/config/upgrade_helpers.rs:16: Failed to finish upgrade - failed to load config file using path '/home/m/.nym/nym-nodes/default-nym-node/config/config.toml'. detailed message: No such file or directory (os error 2) -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-build-info-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-build-info-help.md deleted file mode 100644 index 0f369b8537..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-build-info-help.md +++ /dev/null @@ -1,9 +0,0 @@ -```sh -Show build information of this binary - -Usage: nym-node build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-build-info.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-build-info.md deleted file mode 100644 index 8113e9c0a8..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-build-info.md +++ /dev/null @@ -1,13 +0,0 @@ -```sh - -Binary Name: nym-node -Build Timestamp: 2024-10-09T13:56:14.428750844Z -Build Version: 1.1.6 -Commit SHA: fac373c1db4fa5389ba61de7943c77023467bccb -Commit Date: 2024-10-09T14:59:40.000000000+02:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable -cargo Profile: release - -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-commands.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-commands.md deleted file mode 100644 index f305d5c6eb..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-commands.md +++ /dev/null @@ -1,237 +0,0 @@ -# `nym-node` Binary Commands (Autogenerated) - -These docs are autogenerated by the [`autodocs`](https://github.com/nymtech/nym/tree/max/new-docs-framework/documentation/autodoc) script. -```sh -Usage: nym-node [OPTIONS] - -Commands: - build-info Show build information of this binary - bonding-information Show bonding information of this node depending on its currently selected mode - node-details Show details of this node - migrate Attempt to migrate an existing mixnode or gateway into a nym-node - run Start this nym-node - sign Use identity key of this node to sign provided message - help Print this message or the help of the given subcommand(s) - -Options: - -c, --config-env-file - Path pointing to an env file that configures the nym-node and overrides any preconfigured values [env: NYMNODE_CONFIG_ENV_FILE_ARG=] - --no-banner - Flag used for disabling the printed banner in tty [env: NYMNODE_NO_BANNER=] - -h, --help - Print help - -V, --version - Print version -``` - -### `build-info` -```sh -Show build information of this binary - -Usage: nym-node build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` - -### `bonding-information` -```sh -Show bonding information of this node depending on its currently selected mode - -Usage: nym-node bonding-information [OPTIONS] - -Options: - --id Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file Path to a configuration file of this node [env: NYMNODE_CONFIG=] - --mode [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway] - -o, --output Specify the output format of the bonding information (`text` or `json`) [default: text] [possible values: text, json] - -h, --help Print help -``` - -### `node-details` -```sh -Show details of this node - -Usage: nym-node node-details [OPTIONS] - -Options: - --id Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file Path to a configuration file of this node [env: NYMNODE_CONFIG=] - -o, --output Specify the output format of the node details (`text` or `json`) [default: text] [possible values: text, json] - -h, --help Print help -``` - -### `migrate` -```sh -Attempt to migrate an existing mixnode or gateway into a nym-node - -Usage: nym-node migrate [OPTIONS] <--id |--config-file > - -Arguments: - Type of node (mixnode or gateway) to migrate into a nym-node [possible values: mixnode, gateway] - -Options: - --id - Id of the node that's going to get migrated - --config-file - Path to a configuration file of the node that's going to get migrated - --preserve-id - Specify whether to preserve id of the imported node - --public-ips - Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. In nearly all circumstances, it's going to be identical to the address you're going to use for bonding [env: NYMNODE_PUBLIC_IPS=] - --hostname - Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients [env: NYMNODE_HOSTNAME=] - --location - Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'), three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: NYMNODE_LOCATION=] - --http-bind-address - Socket address this node will use for binding its http API. default: `0.0.0.0:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] - --landing-page-assets-path - Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=] - --http-access-token - An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env: NYMNODE_HTTP_ACCESS_TOKEN=] - --expose-system-info - Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] [possible values: true, false] - --expose-system-hardware - Specify whether basic system hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] [possible values: true, false] - --expose-crypto-hardware - Specify whether detailed system crypto hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] - --mixnet-bind-address - Address this node will bind to for listening for mixnet packets default: `0.0.0.0:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] - --mixnet-announce-port - If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=] - --nym-api-urls - Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] - --nyxd-urls - Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=] - --wireguard-enabled - Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false] - --wireguard-bind-address - Socket address this node will use for binding its wireguard interface. default: `0.0.0.0:51822` [env: NYMNODE_WG_BIND_ADDRESS=] - --wireguard-private-ip - Private IP address of the wireguard gateway. default: `10.1.0.1` [env: NYMNODE_WG_IP=] - --wireguard-announced-port - Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=] - --wireguard-private-network-prefix - The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] - --verloc-bind-address - Socket address this node will use for binding its verloc API. default: `0.0.0.0:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] - --verloc-announce-port - If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=] - --entry-bind-address - Socket address this node will use for binding its client websocket API. default: `0.0.0.0:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] - --announce-ws-port - Custom announced port for listening for websocket client traffic. If unspecified, the value from the `bind_address` will be used instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=] - --announce-wss-port - If applicable, announced port for listening for secure websocket client traffic [env: NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=] - --enforce-zk-nyms - Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false] - --mnemonic - Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env: NYMNODE_MNEMONIC=] - --upstream-exit-policy-url - Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] - --open-proxy - Specifies whether this exit node should run in 'open-proxy' mode and thus would attempt to resolve **ANY** request it receives [env: NYMNODE_OPEN_PROXY=] [possible values: true, false] - -h, --help - Print help -``` - -### `run` -```sh -Start this nym-node - -Usage: nym-node run [OPTIONS] - -Options: - --id - Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file - Path to a configuration file of this node [env: NYMNODE_CONFIG=] - --accept-operator-terms-and-conditions - Explicitly specify whether you agree with the terms and conditions of a nym node operator as defined at [env: NYMNODE_ACCEPT_OPERATOR_TERMS=] - --deny-init - Forbid a new node from being initialised if configuration file for the provided specification doesn't already exist [env: NYMNODE_DENY_INIT=] - --init-only - If this is a brand new nym-node, specify whether it should only be initialised without actually running the subprocesses [env: NYMNODE_INIT_ONLY=] - --local - Flag specifying this node will be running in a local setting [env: NYMNODE_LOCAL=] - --mode - Specifies the current mode of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway] - -w, --write-changes - If this node has been initialised before, specify whether to write any new changes to the config file [env: NYMNODE_WRITE_CONFIG_CHANGES=] - --bonding-information-output - Specify output file for bonding information of this nym-node, i.e. its encoded keys. NOTE: the required bonding information is still a subject to change and this argument should be treated only as a preview of future features [env: NYMNODE_BONDING_INFORMATION_OUTPUT=] - -o, --output - Specify the output format of the bonding information (`text` or `json`) [env: NYMNODE_OUTPUT=] [default: text] [possible values: text, json] - --public-ips - Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. In nearly all circumstances, it's going to be identical to the address you're going to use for bonding [env: NYMNODE_PUBLIC_IPS=] - --hostname - Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients [env: NYMNODE_HOSTNAME=] - --location - Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'), three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: NYMNODE_LOCATION=] - --http-bind-address - Socket address this node will use for binding its http API. default: `0.0.0.0:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] - --landing-page-assets-path - Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=] - --http-access-token - An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env: NYMNODE_HTTP_ACCESS_TOKEN=] - --expose-system-info - Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] [possible values: true, false] - --expose-system-hardware - Specify whether basic system hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] [possible values: true, false] - --expose-crypto-hardware - Specify whether detailed system crypto hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] - --mixnet-bind-address - Address this node will bind to for listening for mixnet packets default: `0.0.0.0:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] - --mixnet-announce-port - If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=] - --nym-api-urls - Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] - --nyxd-urls - Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=] - --wireguard-enabled - Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false] - --wireguard-bind-address - Socket address this node will use for binding its wireguard interface. default: `0.0.0.0:51822` [env: NYMNODE_WG_BIND_ADDRESS=] - --wireguard-private-ip - Private IP address of the wireguard gateway. default: `10.1.0.1` [env: NYMNODE_WG_IP=] - --wireguard-announced-port - Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=] - --wireguard-private-network-prefix - The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] - --verloc-bind-address - Socket address this node will use for binding its verloc API. default: `0.0.0.0:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] - --verloc-announce-port - If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=] - --entry-bind-address - Socket address this node will use for binding its client websocket API. default: `0.0.0.0:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] - --announce-ws-port - Custom announced port for listening for websocket client traffic. If unspecified, the value from the `bind_address` will be used instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=] - --announce-wss-port - If applicable, announced port for listening for secure websocket client traffic [env: NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=] - --enforce-zk-nyms - Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false] - --mnemonic - Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env: NYMNODE_MNEMONIC=] - --upstream-exit-policy-url - Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] - --open-proxy - Specifies whether this exit node should run in 'open-proxy' mode and thus would attempt to resolve **ANY** request it receives [env: NYMNODE_OPEN_PROXY=] [possible values: true, false] - -h, --help - Print help -``` - -### `sign` -```sh -Use identity key of this node to sign provided message - -Usage: nym-node sign [OPTIONS] <--text |--contract-msg > - -Options: - --id Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file Path to a configuration file of this node [env: NYMNODE_CONFIG=] - --text Signs an arbitrary piece of text with your identity key - --contract-msg Signs a transaction-specific payload, that is going to be sent to the smart contract, with your identity key - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-migrate-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-migrate-help.md deleted file mode 100644 index ee32ab638b..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-migrate-help.md +++ /dev/null @@ -1,68 +0,0 @@ -```sh -Attempt to migrate an existing mixnode or gateway into a nym-node - -Usage: nym-node migrate [OPTIONS] <--id |--config-file > - -Arguments: - Type of node (mixnode or gateway) to migrate into a nym-node [possible values: mixnode, gateway] - -Options: - --id - Id of the node that's going to get migrated - --config-file - Path to a configuration file of the node that's going to get migrated - --preserve-id - Specify whether to preserve id of the imported node - --public-ips - Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. In nearly all circumstances, it's going to be identical to the address you're going to use for bonding [env: NYMNODE_PUBLIC_IPS=] - --hostname - Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients [env: NYMNODE_HOSTNAME=] - --location - Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'), three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: NYMNODE_LOCATION=] - --http-bind-address - Socket address this node will use for binding its http API. default: `0.0.0.0:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] - --landing-page-assets-path - Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=] - --http-access-token - An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env: NYMNODE_HTTP_ACCESS_TOKEN=] - --expose-system-info - Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] [possible values: true, false] - --expose-system-hardware - Specify whether basic system hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] [possible values: true, false] - --expose-crypto-hardware - Specify whether detailed system crypto hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] - --mixnet-bind-address - Address this node will bind to for listening for mixnet packets default: `0.0.0.0:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] - --nym-api-urls - Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] - --nyxd-urls - Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=] - --wireguard-enabled - Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false] - --wireguard-bind-address - Socket address this node will use for binding its wireguard interface. default: `0.0.0.0:51822` [env: NYMNODE_WG_BIND_ADDRESS=] - --wireguard-private-ip - Private IP address of the wireguard gateway. default: `10.1.0.1` [env: NYMNODE_WG_IP=] - --wireguard-announced-port - Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=] - --wireguard-private-network-prefix - The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] - --verloc-bind-address - Socket address this node will use for binding its verloc API. default: `0.0.0.0:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] - --entry-bind-address - Socket address this node will use for binding its client websocket API. default: `0.0.0.0:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] - --announce-ws-port - Custom announced port for listening for websocket client traffic. If unspecified, the value from the `bind_address` will be used instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=] - --announce-wss-port - If applicable, announced port for listening for secure websocket client traffic [env: NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=] - --enforce-zk-nyms - Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false] - --mnemonic - Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env: NYMNODE_MNEMONIC=] - --upstream-exit-policy-url - Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] - --open-proxy - Specifies whether this exit node should run in 'open-proxy' mode and thus would attempt to resolve **ANY** request it receives [env: NYMNODE_OPEN_PROXY=] [possible values: true, false] - -h, --help - Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-node-details-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-node-details-help.md deleted file mode 100644 index cad77bc2a8..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-node-details-help.md +++ /dev/null @@ -1,11 +0,0 @@ -```sh -Show details of this node - -Usage: nym-node node-details [OPTIONS] - -Options: - --id Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file Path to a configuration file of this node [env: NYMNODE_CONFIG=] - -o, --output Specify the output format of the node details (`text` or `json`) [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-node-details.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-node-details.md deleted file mode 100644 index c2b0a34c97..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-node-details.md +++ /dev/null @@ -1,3 +0,0 @@ -```sh -2024-10-22T13:00:32.562750Z ERROR nym-node/src/config/upgrade_helpers.rs:16: Failed to finish upgrade - failed to load config file using path '/home/m/.nym/nym-nodes/default-nym-node/config/config.toml'. detailed message: No such file or directory (os error 2) -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-node-sign-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-node-sign-help.md deleted file mode 100644 index 5d26baf2f2..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-node-sign-help.md +++ /dev/null @@ -1,13 +0,0 @@ -```sh -Use identity key of this node to sign provided message - -Usage: nym-node sign [OPTIONS] <--text |--contract-msg > - -Options: - --id Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file Path to a configuration file of this node [env: NYMNODE_CONFIG=] - --text Signs an arbitrary piece of text with your identity key - --contract-msg Signs a transaction-specific payload, that is going to be sent to the smart contract, with your identity key - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-add-gateway-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-add-gateway-help.md deleted file mode 100644 index ee209bdcb0..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-add-gateway-help.md +++ /dev/null @@ -1,15 +0,0 @@ -```sh -Add new gateway to this client - -Usage: nym-socks5-client add-gateway [OPTIONS] --id - -Options: - --id Id of client we want to add gateway for - --gateway-id Explicitly specify id of the gateway to register with. If unspecified, a random gateway will be chosen instead - --force-tls-gateway Specifies whether the client will attempt to enforce tls connection to the desired gateway - --latency-based-selection Specifies whether the new gateway should be determined based by latency as opposed to being chosen uniformly - --set-active Specify whether this new gateway should be set as the active one - --nym-apis Comma separated list of rest endpoints of the API validators - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info-help.md deleted file mode 100644 index 000204a113..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info-help.md +++ /dev/null @@ -1,9 +0,0 @@ -```sh -Show build information of this binary - -Usage: nym-socks5-client build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info.md deleted file mode 100644 index dbf3e77072..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-build-info.md +++ /dev/null @@ -1,13 +0,0 @@ -```sh - -Binary Name: nym-socks5-client -Build Timestamp: 2024-10-09T13:56:14.428750844Z -Build Version: 1.1.39 -Commit SHA: fac373c1db4fa5389ba61de7943c77023467bccb -Commit Date: 2024-10-09T14:59:40.000000000+02:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable -cargo Profile: release - -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-completions-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-completions-help.md deleted file mode 100644 index 8b780c6f17..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-completions-help.md +++ /dev/null @@ -1,11 +0,0 @@ -```sh -Generate shell completions - -Usage: nym-socks5-client completions - -Arguments: - [possible values: bash, elvish, fish, power-shell, zsh] - -Options: - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec-help.md deleted file mode 100644 index 94340b5898..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec-help.md +++ /dev/null @@ -1,8 +0,0 @@ -```sh -Generate Fig specification - -Usage: nym-socks5-client generate-fig-spec - -Options: - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec.md deleted file mode 100644 index b76ef9d558..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-generate-fig-spec.md +++ /dev/null @@ -1,619 +0,0 @@ -```sh -const completion: Fig.Spec = { - name: "nym-socks5-client", - description: "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address", - subcommands: [ - { - name: "init", - description: "Initialise a Nym client. Do this first!", - options: [ - { - name: "--id", - description: "Id of client we want to create config for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway", - description: "Id of the gateway we are going to connect to", - isRepeatable: true, - args: { - name: "gateway", - isOptional: true, - }, - }, - { - name: "--nyxd-urls", - description: "Comma separated list of rest endpoints of the nyxd validators", - hidden: true, - isRepeatable: true, - args: { - name: "nyxd_urls", - isOptional: true, - }, - }, - { - name: "--nym-apis", - description: "Comma separated list of rest endpoints of the API validators", - isRepeatable: true, - args: { - name: "nym_apis", - isOptional: true, - }, - }, - { - name: "--custom-mixnet", - description: "Path to .json file containing custom network specification", - hidden: true, - isRepeatable: true, - args: { - name: "custom_mixnet", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--enabled-credentials-mode", - description: "Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement", - hidden: true, - isRepeatable: true, - args: { - name: "enabled_credentials_mode", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: "--provider", - description: "Address of the socks5 provider to send messages to", - isRepeatable: true, - args: { - name: "provider", - }, - }, - { - name: "--use-reply-surbs", - description: "Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. While this is going to hide its actual address information, it will make the actual communication slower and consume nearly double the bandwidth as it will require sending reply SURBs", - isRepeatable: true, - args: { - name: "use_reply_surbs", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: ["-p", "--port"], - description: "Port for the socket to listen on in all subsequent runs", - isRepeatable: true, - args: { - name: "port", - isOptional: true, - }, - }, - { - name: "--host", - description: "The custom host on which the socks5 client will be listening for requests", - isRepeatable: true, - args: { - name: "host", - isOptional: true, - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: "--force-tls-gateway", - description: "Specifies whether the client will attempt to enforce tls connection to the desired gateway", - }, - { - name: "--latency-based-selection", - description: "Specifies whether the new gateway should be determined based by latency as opposed to being chosen uniformly", - exclusiveOn: [ - "--gateway", - ], - }, - { - name: "--fastmode", - description: "Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init", - }, - { - name: "--no-cover", - description: "Disable loop cover traffic and the Poisson rate limiter (for debugging only)", - }, - { - name: ["-h", "--help"], - description: "Print help (see more with '--help')", - }, - ], - }, - { - name: "run", - description: "Run the Nym client with provided configuration client optionally overriding set parameters", - options: [ - { - name: "--id", - description: "Id of client we want to create config for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway", - description: "Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened", - isRepeatable: true, - args: { - name: "gateway", - isOptional: true, - }, - }, - { - name: "--nyxd-urls", - description: "Comma separated list of rest endpoints of the nyxd validators", - hidden: true, - isRepeatable: true, - args: { - name: "nyxd_urls", - isOptional: true, - }, - }, - { - name: "--nym-apis", - description: "Comma separated list of rest endpoints of the API validators", - isRepeatable: true, - args: { - name: "nym_apis", - isOptional: true, - }, - }, - { - name: "--custom-mixnet", - description: "Path to .json file containing custom network specification", - hidden: true, - isRepeatable: true, - args: { - name: "custom_mixnet", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--enabled-credentials-mode", - description: "Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement", - hidden: true, - isRepeatable: true, - args: { - name: "enabled_credentials_mode", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: "--use-anonymous-replies", - description: "Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. While this is going to hide its actual address information, it will make the actual communication slower and consume nearly double the bandwidth as it will require sending reply SURBs", - isRepeatable: true, - args: { - name: "use_anonymous_replies", - isOptional: true, - suggestions: [ - "true", - "false", - ], - }, - }, - { - name: "--provider", - description: "Address of the socks5 provider to send messages to", - isRepeatable: true, - args: { - name: "provider", - isOptional: true, - }, - }, - { - name: ["-p", "--port"], - description: "Port for the socket to listen on", - isRepeatable: true, - args: { - name: "port", - isOptional: true, - }, - }, - { - name: "--host", - description: "The custom host on which the socks5 client will be listening for requests", - isRepeatable: true, - args: { - name: "host", - isOptional: true, - }, - }, - { - name: "--geo-routing", - description: "Set geo-aware mixnode selection when sending mixnet traffic, for experiments only", - hidden: true, - isRepeatable: true, - args: { - name: "geo_routing", - isOptional: true, - }, - }, - { - name: "--fastmode", - description: "Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init", - }, - { - name: "--no-cover", - description: "Disable loop cover traffic and the Poisson rate limiter (for debugging only)", - }, - { - name: "--medium-toggle", - description: "Enable medium mixnet traffic, for experiments only. This includes things like disabling cover traffic, no per hop delays, etc", - }, - { - name: "--outfox", - }, - { - name: ["-h", "--help"], - description: "Print help (see more with '--help')", - }, - ], - }, - { - name: "import-credential", - description: "Import a pre-generated credential", - options: [ - { - name: "--id", - description: "Id of client that is going to import the credential", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--credential-data", - description: "Explicitly provide the encoded credential data (as base58)", - isRepeatable: true, - args: { - name: "credential_data", - isOptional: true, - }, - }, - { - name: "--credential-path", - description: "Specifies the path to file containing binary credential data", - isRepeatable: true, - args: { - name: "credential_path", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--version", - hidden: true, - isRepeatable: true, - args: { - name: "version", - isOptional: true, - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "list-gateways", - description: "List all registered with gateways", - options: [ - { - name: "--id", - description: "Id of client we want to list gateways for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "add-gateway", - description: "Add new gateway to this client", - options: [ - { - name: "--id", - description: "Id of client we want to add gateway for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway-id", - description: "Explicitly specify id of the gateway to register with. If unspecified, a random gateway will be chosen instead", - isRepeatable: true, - args: { - name: "gateway_id", - isOptional: true, - }, - }, - { - name: "--nym-apis", - description: "Comma separated list of rest endpoints of the API validators", - isRepeatable: true, - args: { - name: "nym_apis", - isOptional: true, - }, - }, - { - name: "--custom-mixnet", - description: "Path to .json file containing custom network specification", - hidden: true, - isRepeatable: true, - args: { - name: "custom_mixnet", - isOptional: true, - template: "filepaths", - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: "--force-tls-gateway", - description: "Specifies whether the client will attempt to enforce tls connection to the desired gateway", - }, - { - name: "--latency-based-selection", - description: "Specifies whether the new gateway should be determined based by latency as opposed to being chosen uniformly", - exclusiveOn: [ - "--gateway-id", - ], - }, - { - name: "--set-active", - description: "Specify whether this new gateway should be set as the active one", - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "switch-gateway", - description: "Change the currently active gateway. Note that you must have already registered with the new gateway!", - options: [ - { - name: "--id", - description: "Id of client we want to list gateways for", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: "--gateway-id", - description: "Id of the gateway we want to switch to", - isRepeatable: true, - args: { - name: "gateway_id", - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "show-ticketbooks", - description: "Display information associated with the imported ticketbooks,", - options: [ - { - name: "--id", - description: "Id of client that is going to display the ticketbook information", - isRepeatable: true, - args: { - name: "id", - }, - }, - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "build-info", - description: "Show build information of this binary", - options: [ - { - name: ["-o", "--output"], - isRepeatable: true, - args: { - name: "output", - isOptional: true, - suggestions: [ - "text", - "json", - ], - }, - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "completions", - description: "Generate shell completions", - options: [ - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - args: { - name: "shell", - suggestions: [ - "bash", - "elvish", - "fish", - "power-shell", - "zsh", - ], - }, - }, - { - name: "generate-fig-spec", - description: "Generate Fig specification", - options: [ - { - name: ["-h", "--help"], - description: "Print help", - }, - ], - }, - { - name: "help", - description: "Print this message or the help of the given subcommand(s)", - subcommands: [ - { - name: "init", - description: "Initialise a Nym client. Do this first!", - }, - { - name: "run", - description: "Run the Nym client with provided configuration client optionally overriding set parameters", - }, - { - name: "import-credential", - description: "Import a pre-generated credential", - }, - { - name: "list-gateways", - description: "List all registered with gateways", - }, - { - name: "add-gateway", - description: "Add new gateway to this client", - }, - { - name: "switch-gateway", - description: "Change the currently active gateway. Note that you must have already registered with the new gateway!", - }, - { - name: "show-ticketbooks", - description: "Display information associated with the imported ticketbooks,", - }, - { - name: "build-info", - description: "Show build information of this binary", - }, - { - name: "completions", - description: "Generate shell completions", - }, - { - name: "generate-fig-spec", - description: "Generate Fig specification", - }, - { - name: "help", - description: "Print this message or the help of the given subcommand(s)", - }, - ], - }, - ], - options: [ - { - name: ["-c", "--config-env-file"], - description: "Path pointing to an env file that configures the client", - isRepeatable: true, - args: { - name: "config_env_file", - isOptional: true, - template: "filepaths", - }, - }, - { - name: "--no-banner", - description: "Flag used for disabling the printed banner in tty", - }, - { - name: ["-h", "--help"], - description: "Print help", - }, - { - name: ["-V", "--version"], - description: "Print version", - }, - ], -}; - -export default completion; -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-import-credential-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-import-credential-help.md deleted file mode 100644 index 2f7303c2e3..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-import-credential-help.md +++ /dev/null @@ -1,15 +0,0 @@ -```sh -Import a pre-generated credential - -Usage: nym-socks5-client import-credential --id <--credential-data |--credential-path > - -Options: - --id - Id of client that is going to import the credential - --credential-data - Explicitly provide the encoded credential data (as base58) - --credential-path - Specifies the path to file containing binary credential data - -h, --help - Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-init-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-init-help.md deleted file mode 100644 index b69ef5f77c..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-init-help.md +++ /dev/null @@ -1,44 +0,0 @@ -```sh -Initialise a Nym client. Do this first! - -Usage: nym-socks5-client init [OPTIONS] --id --provider - -Options: - --id - Id of client we want to create config for - - --gateway - Id of the gateway we are going to connect to - - --force-tls-gateway - Specifies whether the client will attempt to enforce tls connection to the desired gateway - - --latency-based-selection - Specifies whether the new gateway should be determined based by latency as opposed to being chosen uniformly - - --nym-apis - Comma separated list of rest endpoints of the API validators - - --provider - Address of the socks5 provider to send messages to - - --use-reply-surbs - Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. While this is going to hide its actual address information, it will make the actual communication slower and consume nearly double the bandwidth as it will require sending reply SURBs. - - Note that some service providers might not support this. - - [possible values: true, false] - - -p, --port - Port for the socket to listen on in all subsequent runs - - --host - The custom host on which the socks5 client will be listening for requests - - -o, --output - [default: text] - [possible values: text, json] - - -h, --help - Print help (see a summary with '-h') -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-list-gateways-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-list-gateways-help.md deleted file mode 100644 index 86921bcf67..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-list-gateways-help.md +++ /dev/null @@ -1,10 +0,0 @@ -```sh -List all registered with gateways - -Usage: nym-socks5-client list-gateways [OPTIONS] --id - -Options: - --id Id of client we want to list gateways for - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-run-help.md b/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-run-help.md deleted file mode 100644 index 2ffccb39ce..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nym-socks5-client-run-help.md +++ /dev/null @@ -1,34 +0,0 @@ -```sh -Run the Nym client with provided configuration client optionally overriding set parameters - -Usage: nym-socks5-client run [OPTIONS] --id - -Options: - --id - Id of client we want to create config for - - --gateway - Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened - - --nym-apis - Comma separated list of rest endpoints of the API validators - - --use-anonymous-replies - Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. While this is going to hide its actual address information, it will make the actual communication slower and consume nearly double the bandwidth as it will require sending reply SURBs. - - Note that some service providers might not support this. - - [possible values: true, false] - - --provider - Address of the socks5 provider to send messages to - - -p, --port - Port for the socket to listen on - - --host - The custom host on which the socks5 client will be listening for requests - - -h, --help - Print help (see a summary with '-h') -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nymvisor-add-upgrade-help.md b/documentation/autodoc/autodoc-generated-markdown/nymvisor-add-upgrade-help.md deleted file mode 100644 index b88f5e1052..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nymvisor-add-upgrade-help.md +++ /dev/null @@ -1,19 +0,0 @@ -```sh -Queues up another upgrade for the associated daemon - -Usage: nymvisor add-upgrade [OPTIONS] --upgrade-name - -Arguments: - Path to the daemon's upgrade executable - -Options: - --upgrade-name Name of this upgrade - --force Overwrite existing upgrade binary / upgrade-info.json file - --add-binary Indicate that this command should only add binary to an *existing* scheduled upgrade - --now Force the upgrade to happen immediately - --publish-date Specifies the publish date metadata field of this upgrade. If unset, the current time will be used - --upgrade-time Specifies the time at which the provided upgrade will be performed (RFC3339 formatted). If left unset, the upgrade will be performed in 15min - --upgrade-delay Specifies delay until the provided upgrade is going to get performed. If let unset, the upgrade will be performed in 15min - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info-help.md b/documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info-help.md deleted file mode 100644 index 3a5ccb9b5b..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info-help.md +++ /dev/null @@ -1,9 +0,0 @@ -```sh -Show build information of this binary - -Usage: nymvisor build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info.md b/documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info.md deleted file mode 100644 index f0a0032042..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nymvisor-build-info.md +++ /dev/null @@ -1,13 +0,0 @@ -```sh - -Binary Name: nymvisor -Build Timestamp: 2024-10-09T13:56:14.428750844Z -Build Version: 0.1.5 -Commit SHA: fac373c1db4fa5389ba61de7943c77023467bccb -Commit Date: 2024-10-09T14:59:40.000000000+02:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable -cargo Profile: release - -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nymvisor-commands.md b/documentation/autodoc/autodoc-generated-markdown/nymvisor-commands.md deleted file mode 100644 index 9d0ee36f56..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nymvisor-commands.md +++ /dev/null @@ -1,154 +0,0 @@ -# `nymvisor` Binary Commands (Autogenerated) - -These docs are autogenerated by the [`autodocs`](https://github.com/nymtech/nym/tree/max/new-docs-framework/documentation/autodoc) script. -```sh -Usage: nymvisor [OPTIONS] - -Commands: - init Initialise a nymvisor instance with persistent Config.toml file - run Run the associated daemon with the preconfigured settings - build-info Show build information of this binary - daemon-build-info Show build information of the associated daemon - add-upgrade Queues up another upgrade for the associated daemon - config Show configuration options being used by this instance of nymvisor - help Print this message or the help of the given subcommand(s) - -Options: - -c, --config-env-file - Path pointing to an env file that configures the nymvisor and overrides any preconfigured values - -h, --help - Print help - -V, --version - Print version -``` - -### `init` -```sh -Initialise a nymvisor instance with persistent Config.toml file - -Usage: nymvisor init [OPTIONS] - -Arguments: - Path to the daemon's executable - -Options: - --id - ID specifies the human readable ID of this particular nymvisor instance. Can be overridden with $NYMVISOR_ID environmental variable - --upstream-base-upgrade-url - Sets the base url of the upstream source for obtaining upgrade information for the deaemon. It will be used fo constructing the full url, i.e. $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL/$DAEMON_NAME/upgrade-info.json Can be overridden with $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL environmental variable - --upstream-polling-rate - Specifies the rate of polling the upstream url for upgrade information. default: 1h Can be overridden with $NYMVISOR_UPSTREAM_POLLING_RATE - --disable-nymvisor-logs - If enabled, this will disable `nymvisor` logs (but not the underlying process) Can be overridden with $NYMVISOR_DISABLE_LOGS environmental variable - --upgrade-data-directory - Set custom directory for upgrade data - binaries and upgrade plans. If not set, the global nymvisors' data directory will be used instead. Can be overridden with $NYMVISOR_UPGRADE_DATA_DIRECTORY environmental variable - --daemon-home - The location where the `nymvisor/` directory is kept that contains the auxiliary files associated with the underlying daemon, such as any backups or current version information. (e.g. $HOME/.nym/nym-api/my-nym-api, $HOME/.nym/mixnodes/my-mixnode, etc.). Can be overridden with $DAEMON_HOME environmental variable - --daemon-absolute-upstream-upgrade-url - Override url to the upstream source for upgrade plans for this daeamon. The Url has to point to an endpoint containing a valid [`UpgradeInfo`] json. Note: if set this takes precedence over `upstream_base_upgrade_url` Can be overridden with $DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL environmental variable - --allow-download-upgrade-binaries - If set to true, this will enable auto-downloading of new binaries using the url provided in the `upgrade-info.json` Can be overridden with $DAEMON_ALLOW_BINARIES_DOWNLOAD environmental variable [possible values: true, false] - --enforce-download-checksum - If enabled nymvisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. If disabled, nymvisor will not require a checksum to be provided, but still check the checksum if one is provided. Can be overridden with $DAEMON_ENFORCE_DOWNLOAD_CHECKSUM environmental variable [possible values: true, false] - --restart-daemon-after-upgrade - If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (if disabled), nymvisor will stop running after an upgrade and will require the system administrator to manually restart it. Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs. Can be overridden with $DAEMON_RESTART_AFTER_UPGRADE environmental variable [possible values: true, false] - --restart-daemon-on-failure - If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags after it has crashed Can be overridden with $DAEMON_RESTART_ON_FAILURE environmental variable - --on-failure-daemon-restart-delay - If `restart_on_failure` is enabled, the following value defines the amount of time `nymvisor` shall wait before restarting the subprocess. Can be overridden with $DAEMON_FAILURE_RESTART_DELAY environmental variable - --max-daemon-startup-failures - Defines the maximum number of startup failures the subprocess can experience in a quick succession before no further restarts will be attempted and `nymvisor` will exit. Can be overridden with $DAEMON_MAX_STARTUP_FAILURES environmental variable - --startup-period-duration - Defines the length of time during which the subprocess is still considered to be in the startup phase when its failures are going to be considered in `max_startup_failures`. Can be overridden with $DAEMON_STARTUP_PERIOD_DURATION environmental variable - --daemon-shutdown-grace-period - Specifies the amount of time `nymvisor` is willing to wait for the subprocess to undergo graceful shutdown after receiving an interrupt (for either an upgrade or shutdown of the `nymvisor` itself) Once the time passes, a kill signal is going to be sent instead. Can be overridden with $DAEMON_SHUTDOWN_GRACE_PERIOD environmental variable - --daemon-backup-data-directory - Set custom backup directory for daemon data. If not set, the daemon's home directory will be used instead. Can be overridden with $DAEMON_BACKUP_DATA_DIRECTORY environmental variable - --unsafe-skip-backup - If enabled, `nymvisor` will perform upgrades directly without performing any backups. default: false Can be overridden with $DAEMON_UNSAFE_SKIP_BACKUP environmental variable - -o, --output - [default: text] [possible values: text, json] - -h, --help - Print help -``` - -### `run` -```sh -Run the associated daemon with the preconfigured settings - -Usage: nymvisor run [DAEMON_ARGS]... - -Arguments: - [DAEMON_ARGS]... - -Options: - -h, --help Print help -``` - -### `build-info` -```sh -Show build information of this binary - -Usage: nymvisor build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` -Example output: -```sh - -Binary Name: nymvisor -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 0.1.8 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable -cargo Profile: release - -``` - -### `daemon-build-info` -```sh -Show build information of the associated daemon - -Usage: nymvisor daemon-build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` - -### `add-upgrade` -```sh -Queues up another upgrade for the associated daemon - -Usage: nymvisor add-upgrade [OPTIONS] --upgrade-name - -Arguments: - Path to the daemon's upgrade executable - -Options: - --upgrade-name Name of this upgrade - --force Overwrite existing upgrade binary / upgrade-info.json file - --add-binary Indicate that this command should only add binary to an *existing* scheduled upgrade - --now Force the upgrade to happen immediately - --publish-date Specifies the publish date metadata field of this upgrade. If unset, the current time will be used - --upgrade-time Specifies the time at which the provided upgrade will be performed (RFC3339 formatted). If left unset, the upgrade will be performed in 15min - --upgrade-delay Specifies delay until the provided upgrade is going to get performed. If let unset, the upgrade will be performed in 15min - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` - -### `config` -```sh -Show configuration options being used by this instance of nymvisor - -Usage: nymvisor config [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nymvisor-config-help.md b/documentation/autodoc/autodoc-generated-markdown/nymvisor-config-help.md deleted file mode 100644 index 696d346802..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nymvisor-config-help.md +++ /dev/null @@ -1,9 +0,0 @@ -```sh -Show configuration options being used by this instance of nymvisor - -Usage: nymvisor config [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nymvisor-daemon-build-info-help.md b/documentation/autodoc/autodoc-generated-markdown/nymvisor-daemon-build-info-help.md deleted file mode 100644 index c89ba02c59..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nymvisor-daemon-build-info-help.md +++ /dev/null @@ -1,9 +0,0 @@ -```sh -Show build information of the associated daemon - -Usage: nymvisor daemon-build-info [OPTIONS] - -Options: - -o, --output [default: text] [possible values: text, json] - -h, --help Print help -``` diff --git a/documentation/autodoc/autodoc-generated-markdown/nymvisor-init-help.md b/documentation/autodoc/autodoc-generated-markdown/nymvisor-init-help.md deleted file mode 100644 index 8e373fc42c..0000000000 --- a/documentation/autodoc/autodoc-generated-markdown/nymvisor-init-help.md +++ /dev/null @@ -1,48 +0,0 @@ -```sh -Initialise a nymvisor instance with persistent Config.toml file - -Usage: nymvisor init [OPTIONS] - -Arguments: - Path to the daemon's executable - -Options: - --id - ID specifies the human readable ID of this particular nymvisor instance. Can be overridden with $NYMVISOR_ID environmental variable - --upstream-base-upgrade-url - Sets the base url of the upstream source for obtaining upgrade information for the deaemon. It will be used fo constructing the full url, i.e. $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL/$DAEMON_NAME/upgrade-info.json Can be overridden with $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL environmental variable - --upstream-polling-rate - Specifies the rate of polling the upstream url for upgrade information. default: 1h Can be overridden with $NYMVISOR_UPSTREAM_POLLING_RATE - --disable-nymvisor-logs - If enabled, this will disable `nymvisor` logs (but not the underlying process) Can be overridden with $NYMVISOR_DISABLE_LOGS environmental variable - --upgrade-data-directory - Set custom directory for upgrade data - binaries and upgrade plans. If not set, the global nymvisors' data directory will be used instead. Can be overridden with $NYMVISOR_UPGRADE_DATA_DIRECTORY environmental variable - --daemon-home - The location where the `nymvisor/` directory is kept that contains the auxiliary files associated with the underlying daemon, such as any backups or current version information. (e.g. $HOME/.nym/nym-api/my-nym-api, $HOME/.nym/mixnodes/my-mixnode, etc.). Can be overridden with $DAEMON_HOME environmental variable - --daemon-absolute-upstream-upgrade-url - Override url to the upstream source for upgrade plans for this daeamon. The Url has to point to an endpoint containing a valid [`UpgradeInfo`] json. Note: if set this takes precedence over `upstream_base_upgrade_url` Can be overridden with $DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL environmental variable - --allow-download-upgrade-binaries - If set to true, this will enable auto-downloading of new binaries using the url provided in the `upgrade-info.json` Can be overridden with $DAEMON_ALLOW_BINARIES_DOWNLOAD environmental variable [possible values: true, false] - --enforce-download-checksum - If enabled nymvisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. If disabled, nymvisor will not require a checksum to be provided, but still check the checksum if one is provided. Can be overridden with $DAEMON_ENFORCE_DOWNLOAD_CHECKSUM environmental variable [possible values: true, false] - --restart-daemon-after-upgrade - If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (if disabled), nymvisor will stop running after an upgrade and will require the system administrator to manually restart it. Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs. Can be overridden with $DAEMON_RESTART_AFTER_UPGRADE environmental variable [possible values: true, false] - --restart-daemon-on-failure - If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags after it has crashed Can be overridden with $DAEMON_RESTART_ON_FAILURE environmental variable - --on-failure-daemon-restart-delay - If `restart_on_failure` is enabled, the following value defines the amount of time `nymvisor` shall wait before restarting the subprocess. Can be overridden with $DAEMON_FAILURE_RESTART_DELAY environmental variable - --max-daemon-startup-failures - Defines the maximum number of startup failures the subprocess can experience in a quick succession before no further restarts will be attempted and `nymvisor` will exit. Can be overridden with $DAEMON_MAX_STARTUP_FAILURES environmental variable - --startup-period-duration - Defines the length of time during which the subprocess is still considered to be in the startup phase when its failures are going to be considered in `max_startup_failures`. Can be overridden with $DAEMON_STARTUP_PERIOD_DURATION environmental variable - --daemon-shutdown-grace-period - Specifies the amount of time `nymvisor` is willing to wait for the subprocess to undergo graceful shutdown after receiving an interrupt (for either an upgrade or shutdown of the `nymvisor` itself) Once the time passes, a kill signal is going to be sent instead. Can be overridden with $DAEMON_SHUTDOWN_GRACE_PERIOD environmental variable - --daemon-backup-data-directory - Set custom backup directory for daemon data. If not set, the daemon's home directory will be used instead. Can be overridden with $DAEMON_BACKUP_DATA_DIRECTORY environmental variable - --unsafe-skip-backup - If enabled, `nymvisor` will perform upgrades directly without performing any backups. default: false Can be overridden with $DAEMON_UNSAFE_SKIP_BACKUP environmental variable - -o, --output - [default: text] [possible values: text, json] - -h, --help - Print help -``` diff --git a/documentation/autodoc/src/main.rs b/documentation/autodoc/src/main.rs index fda48a85f7..7f9513097c 100644 --- a/documentation/autodoc/src/main.rs +++ b/documentation/autodoc/src/main.rs @@ -127,7 +127,7 @@ fn main() -> io::Result<()> { writeln!( file, "# {} Binary Commands (Autogenerated)", - format!("`{}`", last_word.unwrap()) + format_args!("`{}`", last_word.unwrap()) )?; writeln!( file, @@ -152,7 +152,7 @@ fn main() -> io::Result<()> { writeln!( file, "# {} Binary Commands (Autogenerated)", - format!("`{}`", last_word.unwrap()) + format_args!("`{}`", last_word.unwrap()) )?; writeln!( file, @@ -190,9 +190,10 @@ fn execute_command_own_file(main_command: &str, subcommand: &str) -> io::Result< // this check is basically checking for the rare commands (rn just one) that start a process with no params // perhaps if this list grows we could just add a timeout and shunt the running and writing // into a thread with a timeout or something but for right now its fine / thats overkill - if get_last_word_from_filepath(main_command).unwrap() == "nym-node" && subcommand == "run" - || get_last_word_from_filepath(main_command).unwrap() == "nym-api" && subcommand == "run" - || get_last_word_from_filepath(main_command).unwrap() == "nymvisor" && subcommand == "run" + if (get_last_word_from_filepath(main_command).unwrap() == "nymvisor" + || get_last_word_from_filepath(main_command).unwrap() == "nym-api" + || get_last_word_from_filepath(main_command).unwrap() == "nym-node") + && subcommand == "run" { info!("SKIPPING {} {}", main_command, subcommand); } else { @@ -278,7 +279,7 @@ fn execute_command( ) -> io::Result<()> { // checking for the nym-cli subsubcommands if subsubcommand.is_some() { - writeln!(file, "\n### `{} {}`", subcommand, subsubcommand.unwrap())?; + writeln!(file, "\n## `{} {}`", subcommand, subsubcommand.unwrap())?; info!("executing {} {} --help ", main_command, subcommand); let output = Command::new(main_command) @@ -293,7 +294,7 @@ fn execute_command( } // just subcommands } else { - writeln!(file, "\n### `{}`", subcommand)?; + writeln!(file, "\n## `{}`", subcommand)?; // execute help let output = Command::new(main_command) diff --git a/documentation/docs/components/api-table.tsx b/documentation/docs/components/api-table.tsx new file mode 100644 index 0000000000..236f04f828 --- /dev/null +++ b/documentation/docs/components/api-table.tsx @@ -0,0 +1,52 @@ +import React, { useState, useEffect } from "react"; + +export default function NymDealersAddresses({ + endpoint, +}: { + endpoint: string; +}) { + const [announceAddresses, setAnnounceAddresses] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch(endpoint); + + if (!response.ok) { + throw new Error("Failed to fetch data"); + } + + const jsonData = await response.json(); + + const addresses = jsonData.data.dealers.map( + (dealer: any) => dealer.announce_address + ); + + setAnnounceAddresses(addresses); + setIsLoading(false); + } catch (error) { + setError(error instanceof Error ? error.message : "Unknown error"); + setIsLoading(false); + } + }; + + fetchData(); + }, [endpoint]); + + if (isLoading) return
    Loading...
    ; + if (error) return
    Error: {error}
    ; + + return ( + + + {announceAddresses.map((address, index) => ( + + {address} + + ))} + +
    + ); +} diff --git a/documentation/docs/components/landing-page.tsx b/documentation/docs/components/landing-page.tsx index 87b9f29fff..dd6ae77db1 100644 --- a/documentation/docs/components/landing-page.tsx +++ b/documentation/docs/components/landing-page.tsx @@ -10,12 +10,6 @@ import networkDocs from "../public/images/landing/network-docs.png"; import developerDocs from "../public/images/landing/developer-docs.png"; import sdkDocs from "../public/images/landing/sdk-docs.png"; import operatorGuide from "../public/images/landing/operator-guide.png"; -import { t } from "nextra/dist/types-c8e621b7"; -// import networkDocs from "./images/network-docs.png"; -// import developerDocs from "./images/developer-docs.png"; -// import sdkDocs from "./images/sdk-docs.png"; -// import operatorGuide from "./images/operator-guide.png"; - export const LandingPage = () => { const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.up("md")); @@ -38,19 +32,25 @@ export const LandingPage = () => { }, { text: "Developer Portal", - description: - "Conceptual overview, clients, and tools for developers and integrations", + description: "Conceptual overview, clients, tools and SDKs", href: "/developers", - icon: networkDocs, - }, - { - text: "SDKs", - description: "Rust and Typescript SDK docs", - - href: "/developers/rust", icon: sdkDocs, }, + // { + // text: "SDKs", + // description: "Rust and Typescript SDK docs", + + // href: "/developers/rust", + // icon: sdkDocs, + // }, + { + text: "APIs", + description: "Interactive API specs for Nym infrastructure", + + href: "/apis/introduction", + icon: networkDocs, + }, ]; const shortenDescription = (description: string) => { diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index 2710b013a7..f3566e41d5 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Friday, December 6th 2024, 10:16:06 UTC +Monday, December 9th 2024, 19:43:12 UTC diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-build-info-help.md b/documentation/docs/components/outputs/command-outputs/nym-api-build-info-help.md index 7ece0cff3a..db52a92b4e 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-build-info-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-build-info-help.md @@ -1,5 +1,4 @@ ```sh -2024-10-29T09:51:44.050179Z  INFO nym-api/src/main.rs:41: Starting nym api... Show build information of this binary Usage: nym-api build-info [OPTIONS] diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-build-info.md b/documentation/docs/components/outputs/command-outputs/nym-api-build-info.md index 107dd29a8a..c7ef59a43c 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-build-info.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-build-info.md @@ -1,14 +1,13 @@ ```sh -2024-10-29T09:51:44.043480Z  INFO nym-api/src/main.rs:41: Starting nym api... Binary Name: nym-api -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 1.1.45 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable +Build Timestamp: 2024-12-10T11:37:07.839071360Z +Build Version: 1.1.46 +Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd +Commit Date: 2024-11-26T11:53:05.000000000+01:00 +Commit Branch: master +rustc Version: 1.84.0-nightly +rustc Channel: nightly cargo Profile: release ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-init-help.md b/documentation/docs/components/outputs/command-outputs/nym-api-init-help.md index 6427278bdf..7253758dc6 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-init-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-init-help.md @@ -1,5 +1,4 @@ ```sh -2024-10-29T09:51:44.027197Z  INFO nym-api/src/main.rs:41: Starting nym api... Initialise a Nym Api instance with persistent config.toml file Usage: nym-api init [OPTIONS] diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-init.md b/documentation/docs/components/outputs/command-outputs/nym-api-init.md index 976a4ae91d..89b8486a06 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-init.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-init.md @@ -1,3 +1,3 @@ ```sh -2024-10-29T09:51:44.019255Z  INFO nym-api/src/main.rs:41: Starting nym api... +2024-11-06T12:18:46.199371Z  INFO nym-api/src/main.rs:41: Starting nym api... ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-client-build-info.md b/documentation/docs/components/outputs/command-outputs/nym-client-build-info.md index 8bf2f6fa1c..660d02d4a2 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-client-build-info.md +++ b/documentation/docs/components/outputs/command-outputs/nym-client-build-info.md @@ -1,13 +1,13 @@ ```sh Binary Name: nym-client -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 1.1.42 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable +Build Timestamp: 2024-12-10T11:37:07.839071360Z +Build Version: 1.1.44 +Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd +Commit Date: 2024-11-26T11:53:05.000000000+01:00 +Commit Branch: master +rustc Version: 1.84.0-nightly +rustc Channel: nightly cargo Profile: release ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-bonding-information.md b/documentation/docs/components/outputs/command-outputs/nym-node-bonding-information.md index b20c9dd9fb..5cda2291e2 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-bonding-information.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-bonding-information.md @@ -1,3 +1,3 @@ ```sh -2024-10-29T09:51:44.440498Z ERROR nym-node/src/config/upgrade_helpers.rs:16: Failed to finish upgrade - failed to load config file using path '/home/m/.nym/nym-nodes/default-nym-node/config/config.toml'. detailed message: No such file or directory (os error 2) +2024-11-06T12:18:46.723546Z ERROR nym-node/src/config/upgrade_helpers.rs:17: Failed to finish upgrade - failed to load config file using path '/home/m/.nym/nym-nodes/default-nym-node/config/config.toml'. detailed message: No such file or directory (os error 2) ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-build-info.md b/documentation/docs/components/outputs/command-outputs/nym-node-build-info.md index abc2bd416f..7ed28e4de5 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-build-info.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-build-info.md @@ -1,13 +1,13 @@ ```sh Binary Name: nym-node -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 1.1.9 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable +Build Timestamp: 2024-12-10T11:37:07.839071360Z +Build Version: 1.1.11 +Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd +Commit Date: 2024-11-26T11:53:05.000000000+01:00 +Commit Branch: master +rustc Version: 1.84.0-nightly +rustc Channel: nightly cargo Profile: release ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-node-details.md b/documentation/docs/components/outputs/command-outputs/nym-node-node-details.md index 768152ed3a..37077dda8f 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-node-details.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-node-details.md @@ -1,3 +1,3 @@ ```sh -2024-10-29T09:51:44.459681Z ERROR nym-node/src/config/upgrade_helpers.rs:16: Failed to finish upgrade - failed to load config file using path '/home/m/.nym/nym-nodes/default-nym-node/config/config.toml'. detailed message: No such file or directory (os error 2) +2024-11-06T12:18:46.742880Z ERROR nym-node/src/config/upgrade_helpers.rs:17: Failed to finish upgrade - failed to load config file using path '/home/m/.nym/nym-nodes/default-nym-node/config/config.toml'. detailed message: No such file or directory (os error 2) ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-socks5-client-build-info.md b/documentation/docs/components/outputs/command-outputs/nym-socks5-client-build-info.md index 6ae9f633fe..cb9ed56157 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-socks5-client-build-info.md +++ b/documentation/docs/components/outputs/command-outputs/nym-socks5-client-build-info.md @@ -1,13 +1,13 @@ ```sh Binary Name: nym-socks5-client -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 1.1.42 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable +Build Timestamp: 2024-12-10T11:37:07.839071360Z +Build Version: 1.1.44 +Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd +Commit Date: 2024-11-26T11:53:05.000000000+01:00 +Commit Branch: master +rustc Version: 1.84.0-nightly +rustc Channel: nightly cargo Profile: release ``` diff --git a/documentation/docs/components/outputs/command-outputs/nymvisor-build-info.md b/documentation/docs/components/outputs/command-outputs/nymvisor-build-info.md index 6db90dcec6..a373c4b930 100644 --- a/documentation/docs/components/outputs/command-outputs/nymvisor-build-info.md +++ b/documentation/docs/components/outputs/command-outputs/nymvisor-build-info.md @@ -1,13 +1,13 @@ ```sh Binary Name: nymvisor -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 0.1.8 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable +Build Timestamp: 2024-12-10T11:37:07.839071360Z +Build Version: 0.1.9 +Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd +Commit Date: 2024-11-26T11:53:05.000000000+01:00 +Commit Branch: master +rustc Version: 1.84.0-nightly +rustc Channel: nightly cargo Profile: release ``` diff --git a/documentation/docs/next-env.d.ts b/documentation/docs/next-env.d.ts index a4a7b3f5cf..4f11a03dc6 100644 --- a/documentation/docs/next-env.d.ts +++ b/documentation/docs/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/documentation/docs/package.json b/documentation/docs/package.json index 938ed95093..cd0a1371c0 100644 --- a/documentation/docs/package.json +++ b/documentation/docs/package.json @@ -15,6 +15,7 @@ "start": "next start" }, "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", "@coreui/coreui": "^5.1.2", "@coreui/react": "^5.4.0", "@cosmjs/amino": "^0.32.2", @@ -39,6 +40,7 @@ "@nymproject/contract-clients": ">=1.2.4-rc.2 || ^1", "@nymproject/mix-fetch-full-fat": ">=1.2.4-rc.2 || ^1", "@nymproject/sdk-full-fat": ">=1.2.4-rc.2 || ^1", + "@redocly/cli": "^1.25.15", "@types/mdx": "^2.0.13", "chain-registry": "^1.19.0", "cosmjs-types": "^0.9.0", diff --git a/documentation/docs/pages/_meta.json b/documentation/docs/pages/_meta.json index ba7bdce511..15f1b5e394 100644 --- a/documentation/docs/pages/_meta.json +++ b/documentation/docs/pages/_meta.json @@ -27,5 +27,9 @@ "operators": { "title": "Operators", "type": "page" + }, + "apis": { + "title": "APIs", + "type": "page" } } diff --git a/documentation/docs/pages/apis/_meta.json b/documentation/docs/pages/apis/_meta.json new file mode 100644 index 0000000000..616cc14334 --- /dev/null +++ b/documentation/docs/pages/apis/_meta.json @@ -0,0 +1,12 @@ +{ + "introduction": "Introduction", + "ns-api": "Node Status API", + "nym-api": "NymAPI Validator Sidecar", + "explorer-api": "Explorer API", + "cosmos-sdk-nyx": "Validator REST API", + "---": { + "type": "separator" + }, + "licensing": "Licensing", + "coc": "Coc" +} diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx.mdx b/documentation/docs/pages/apis/cosmos-sdk-nyx.mdx new file mode 100644 index 0000000000..b43138fd3e --- /dev/null +++ b/documentation/docs/pages/apis/cosmos-sdk-nyx.mdx @@ -0,0 +1,3 @@ +# Validator REST API + +Since the [Nyx validators](../../operators/nodes/validator-setup) are built with the Cosmos SDK, they by default expose a [REST API](https://docs.cosmos.network/api) which can be used to query the state of the chain. diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx b/documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx new file mode 100644 index 0000000000..72233bd16a --- /dev/null +++ b/documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx @@ -0,0 +1,18 @@ +import { RedocStandalone } from 'redoc'; + +The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://api.nymtech.net/swagger/swagger.yaml](https://api.nymtech.net/swagger/swagger.yaml) which is also used to generate the Swagger docs deployed at [https://api.nymtech.net/swagger/](https://api.nymtech.net/swagger/). + +There is also an overview of other Validator endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx). + + diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx b/documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx new file mode 100644 index 0000000000..726ef0b51e --- /dev/null +++ b/documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx @@ -0,0 +1,16 @@ +import { RedocStandalone } from 'redoc'; + +The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at []() which is also used to generate the Swagger docs deployed at [](). + + diff --git a/documentation/docs/pages/apis/explorer-api.mdx b/documentation/docs/pages/apis/explorer-api.mdx new file mode 100644 index 0000000000..0ab207360d --- /dev/null +++ b/documentation/docs/pages/apis/explorer-api.mdx @@ -0,0 +1,8 @@ + +# Explorer API + +The Explorer API is the backend for the [Mixnet Explorer](https://explorer.nymtech.net/). + +**This will soon be deprecated in favour of the [Node Status API](../ns-api).** + +The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/explorer). diff --git a/documentation/docs/pages/apis/explorer-api/mainnet.mdx b/documentation/docs/pages/apis/explorer-api/mainnet.mdx new file mode 100644 index 0000000000..83853f6684 --- /dev/null +++ b/documentation/docs/pages/apis/explorer-api/mainnet.mdx @@ -0,0 +1,17 @@ + +import { RedocStandalone } from 'redoc'; + +The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://explorer.nymtech.net/api/v1/openapi.json](https://explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://explorer.nymtech.net/api/swagger/index.html](https://explorer.nymtech.net/api/swagger/index.html). + + diff --git a/documentation/docs/pages/apis/explorer-api/sandbox.mdx b/documentation/docs/pages/apis/explorer-api/sandbox.mdx new file mode 100644 index 0000000000..4053d7cd18 --- /dev/null +++ b/documentation/docs/pages/apis/explorer-api/sandbox.mdx @@ -0,0 +1,16 @@ +import { RedocStandalone } from 'redoc'; + +The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-explorer.nymtech.net/api/v1/openapi.json](https://sandbox-explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-explorer.nymtech.net/api/swagger/index.html](https://sandbox-explorer.nymtech.net/api/swagger/index.html). + + diff --git a/documentation/docs/pages/apis/introduction.mdx b/documentation/docs/pages/apis/introduction.mdx new file mode 100644 index 0000000000..c2c521ba8c --- /dev/null +++ b/documentation/docs/pages/apis/introduction.mdx @@ -0,0 +1,5 @@ +# Introduction + +This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. + +You can find links to the generated specs for each API on their respective pages as well as their different uses for operators and developers. diff --git a/documentation/docs/pages/apis/ns-api.mdx b/documentation/docs/pages/apis/ns-api.mdx new file mode 100644 index 0000000000..85d2ea4566 --- /dev/null +++ b/documentation/docs/pages/apis/ns-api.mdx @@ -0,0 +1,11 @@ +import { Callout } from 'nextra/components' + +# Node Status API + +The Node Status API contains information about the network, its topology, and the routing scores of all nodes within it. It offers broadly similar information to the experimental [Habourmaster frontend](https://harbourmaster.nymtech.net/) but is stable where the Harbourmaster is subject to sudden changes as we modify and experiment with how we scrape and present data about the Mixnet infrastructure. + + +People building applications or dashboards which requires information about nodes, their uptime, and their delegations should use this instead of Habourmaster. + + +The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-node-status-api). In the future we will encourage developers to run their own instance of this API in order to distribute endpoints and query load. diff --git a/documentation/docs/pages/apis/ns-api/mainnet.mdx b/documentation/docs/pages/apis/ns-api/mainnet.mdx new file mode 100644 index 0000000000..d13f994c5e --- /dev/null +++ b/documentation/docs/pages/apis/ns-api/mainnet.mdx @@ -0,0 +1,16 @@ +import { RedocStandalone } from 'redoc'; + +The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/). + + diff --git a/documentation/docs/pages/apis/ns-api/sandbox.mdx b/documentation/docs/pages/apis/ns-api/sandbox.mdx new file mode 100644 index 0000000000..aa553ea145 --- /dev/null +++ b/documentation/docs/pages/apis/ns-api/sandbox.mdx @@ -0,0 +1,16 @@ +import { RedocStandalone } from 'redoc'; + +The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json](https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-node-status-api.nymte.ch/swagger/](https://sandbox-node-status-api.nymte.ch/swagger/). + + diff --git a/documentation/docs/pages/apis/nym-api.mdx b/documentation/docs/pages/apis/nym-api.mdx new file mode 100644 index 0000000000..15be469fc4 --- /dev/null +++ b/documentation/docs/pages/apis/nym-api.mdx @@ -0,0 +1,5 @@ +# NymAPI + +The [NymAPI](../../operators/nodes/validator-setup/nym-api) is a sidecar binary operated by members of the Nym Validator set. Amongst other things, the API offers endpoints to query regarding circulating `NYM` supply, and global and ticketbook-specific [zk-nym](../../network/cryptography/zk-nym) data. This is all information contained in various smart contracts on the Nyx blockchain; the NymAPI caches this information periodically to make queries faster and more scalable. + +The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-api). diff --git a/documentation/docs/pages/apis/nym-api/mainnet.mdx b/documentation/docs/pages/apis/nym-api/mainnet.mdx new file mode 100644 index 0000000000..426dc780cb --- /dev/null +++ b/documentation/docs/pages/apis/nym-api/mainnet.mdx @@ -0,0 +1,10 @@ +import APITable from 'components/api-table.tsx'; + +You can find the OpenAPI spec found at [https://validator.nymtech.net/api-docs/openapi.json](https://validator.nymtech.net/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://validator.nymtech.net/api/swagger/index.html](https://validator.nymtech.net/api/swagger/index.html). + +You can find the Swagger endpoints of other NymAPI instances at the following endpoints: + + +There is also an overview of endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx). + +> Interactive Redoc component coming soon diff --git a/documentation/docs/pages/developers/clients.mdx b/documentation/docs/pages/developers/clients.mdx index b507ecf7be..7b6f38491d 100644 --- a/documentation/docs/pages/developers/clients.mdx +++ b/documentation/docs/pages/developers/clients.mdx @@ -1,3 +1,7 @@ +--- +toc: float +--- + # Types of Nym clients At present, there are three Nym clients. These are built as standalone binaries when building our codebase, but are most easily accessed through one of our SDKs: diff --git a/documentation/docs/pages/developers/clients/socks5.mdx b/documentation/docs/pages/developers/clients/socks5.mdx index 9a49178094..08e40b6306 100644 --- a/documentation/docs/pages/developers/clients/socks5.mdx +++ b/documentation/docs/pages/developers/clients/socks5.mdx @@ -50,144 +50,8 @@ The `nym-socks5-client` allows you to do the following from your local machine: The `nym-node` then reassembles the original TCP stream using the packets' sequence numbers, and make the intended request. It will then chop up the response into Sphinx packets and send them back through the mixnet to your `nym-socks5-client`. The application will then receive its data, without even noticing that it wasn't talking to a "normal" SOCKS5 proxy! - ## Download or compile socks5 client If you are using OSX or a Debian-based operating system, you can download the `nym-socks5-client` binary from our [Github releases page](https://github.com/nymtech/nym/releases). If you are using a different operating system, or want to build from source, simply use `cargo build --release` from the root of the Nym monorepo. - -## Client setup -### Viewing command help - -You can check that your binaries are properly compiled with: - -``` -./nym-socks5-client --help -``` - -You can check the necessary parameters for the available commands by running: - -``` -./nym-client --help -``` - -### Initialising a new client instance -Before you can use the client, you need to initalise a new instance of it, which can be done with the following command: - -``` -./nym-socks5-client init --id docs-example --use-reply-surbs true --provider Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf -``` - -The `--id` in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is **never** transmitted over the network. - -The `--use-reply-surbs` field denotes whether you wish to send [SURBs](../../network/concepts/anonymous-replies) along with your request. It defaults to `false`, we are explicitly setting it as `true`. It defaults to `false` for compatibility with versions of the pre-smoosh `nym-network-requester` binary which will soon be deprecated. - -The `--provider` field needs to be filled with the Nym address of an Exit Gateway that can make network requests on your behalf. You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). - -#### Choosing a Gateway -By default - as in the example above - your client will choose a random gateway to connect to. - -However, there are several options for choosing a gateway, if you do not want one that is randomly assigned to your client: -* If you wish to connect to a specific gateway, you can specify this with the `--gateway` flag when running `init`. -* You can also choose a gateway based on its location relative to your client. This can be done by appending the `--latency-based-selection` flag to your `init` command. This command means that to select a gateway, your client will: - * fetch a list of all availiable gateways - * send few ping messages to all of them, and measure response times. - * create a weighted distribution to randomly choose one, favouring ones with lower latency. - -You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). - -> Note this doesn't mean that your client will pick the closest gateway to you, but it will be far more likely to connect to gateway with a 20ms ping rather than 200ms - -### Configuring your client -When you initalise a client instance, a configuration directory will be generated and stored in `$HOME_DIR/.nym/socks5-clients//`. - -``` -tree $HOME//.nym/socks5-clients/docs-example -├── config -│   └── config.toml -└── data - ├── ack_key.pem - ├── credentials_database.db - ├── gateway_shared.pem - ├── persistent_reply_store.sqlite - ├── private_encryption.pem - ├── private_identity.pem - ├── public_encryption.pem - └── public_identity.pem - -``` - -The `config.toml` file contains client configuration options, while the two `pem` files contain client key information. - -The generated files contain the client name, public/private keypairs, and gateway address. The name `` in the example above is just a local identifier so that you can name your clients. - -#### Configuring your client for Docker -By default, the native client listens to host `127.0.0.1`. However this can be an issue if you wish to run a client in a Dockerized environment, where it can be convenenient to listen on a different host such as `0.0.0.0`. - -You can set this via the `--host` flag during either the `init` or `run` commands. - -Alternatively, a custom host can be set in the `config.toml` file under the `socket` section. If you do this, remember to restart your client process. - -### Running the socks5 client - -You can run the initialised client by doing this: - -``` -./nym-socks5-client run --id docs-example -``` - -## Automating your socks5 client with systemd - -Create a service file for the socks5 client at `/etc/systemd/system/nym-socks5-client.service`: - -```ini -[Unit] -Description=Nym Socks5 Client -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User=nym # replace this with whatever user you wish -LimitNOFILE=65536 -ExecStart=/home/nym/nym-socks5-client run --id -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Now enable and start your socks5 client: - -``` -systemctl enable nym-socks5-client.service -systemctl start nym-socks5-client.service -# you can always check your socks5 client has succesfully started with: -systemctl status nym-socks5-client.service -``` - -## Using your Socks5 Client - -After completing the steps above, your local Socks5 Client will be listening on `localhost:1080` ready to proxy traffic to the Network Requester set as the `--provider` when initialising. - -When trying to connect your app, generally the proxy settings are found in `settings->advanced` or `settings->connection`. - -Here is an example of setting the proxy connecting in Blockstream Green: - -![Blockstream Green settings](/images/wallet-proxy-settings/blockstream-green.gif) - -Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: **localhost**, port: **1080**). - -In some other applications, this might be written as **localhost:1080** if there's only one proxy entry field. - -## Useful Commands - -**no-banner** - -Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. - -**build-info** - -A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. diff --git a/documentation/docs/pages/developers/clients/socks5/_meta.json b/documentation/docs/pages/developers/clients/socks5/_meta.json new file mode 100644 index 0000000000..a050bc5042 --- /dev/null +++ b/documentation/docs/pages/developers/clients/socks5/_meta.json @@ -0,0 +1,6 @@ +{ + "setup": "Setup", + "usage": "Usage", + "config": "Config", + "commands": "Commands" +} diff --git a/documentation/docs/pages/developers/clients/socks5/commands.mdx b/documentation/docs/pages/developers/clients/socks5/commands.mdx index 1681dadc9b..2ecb2e7e49 100644 --- a/documentation/docs/pages/developers/clients/socks5/commands.mdx +++ b/documentation/docs/pages/developers/clients/socks5/commands.mdx @@ -25,7 +25,7 @@ Options: -V, --version Print version ``` -### `init` +## `init` ```sh Initialise a Nym client. Do this first! @@ -71,7 +71,7 @@ Options: Print help (see a summary with '-h') ``` -### `run` +## `run` ```sh Run the Nym client with provided configuration client optionally overriding set parameters @@ -107,9 +107,9 @@ Options: Print help (see a summary with '-h') ``` -### `import-credential` +## `import-credential` -### `list-gateways` +## `list-gateways` ```sh List all registered with gateways @@ -121,7 +121,7 @@ Options: -h, --help Print help ``` -### `add-gateway` +## `add-gateway` ```sh Add new gateway to this client @@ -138,7 +138,7 @@ Options: -h, --help Print help ``` -### `build-info` +## `build-info` ```sh Show build information of this binary @@ -152,18 +152,18 @@ Example output: ```sh Binary Name: nym-socks5-client -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 1.1.42 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable +Build Timestamp: 2024-12-10T11:37:07.839071360Z +Build Version: 1.1.44 +Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd +Commit Date: 2024-11-26T11:53:05.000000000+01:00 +Commit Branch: master +rustc Version: 1.84.0-nightly +rustc Channel: nightly cargo Profile: release ``` -### `completions` +## `completions` ```sh Generate shell completions @@ -176,7 +176,7 @@ Options: -h, --help Print help ``` -### `generate-fig-spec` +## `generate-fig-spec` ```sh Generate Fig specification diff --git a/documentation/docs/pages/developers/clients/socks5/config.mdx b/documentation/docs/pages/developers/clients/socks5/config.mdx new file mode 100644 index 0000000000..3c53d14da1 --- /dev/null +++ b/documentation/docs/pages/developers/clients/socks5/config.mdx @@ -0,0 +1,29 @@ +# Configuring your client +When you initalise a client instance, a configuration directory will be generated and stored in `$HOME_DIR/.nym/socks5-clients//`. + +``` +tree $HOME//.nym/socks5-clients/docs-example +├── config +│   └── config.toml +└── data + ├── ack_key.pem + ├── credentials_database.db + ├── gateway_shared.pem + ├── persistent_reply_store.sqlite + ├── private_encryption.pem + ├── private_identity.pem + ├── public_encryption.pem + └── public_identity.pem + +``` + +The `config.toml` file contains client configuration options, while the two `pem` files contain client key information. + +The generated files contain the client name, public/private keypairs, and gateway address. The name `` in the example above is just a local identifier so that you can name your clients. + +#### Configuring your client for Docker +By default, the native client listens to host `127.0.0.1`. However this can be an issue if you wish to run a client in a Dockerized environment, where it can be convenenient to listen on a different host such as `0.0.0.0`. + +You can set this via the `--host` flag during either the `init` or `run` commands. + +Alternatively, a custom host can be set in the `config.toml` file under the `socket` section. If you do this, remember to restart your client process. diff --git a/documentation/docs/pages/developers/clients/socks5/setup.mdx b/documentation/docs/pages/developers/clients/socks5/setup.mdx new file mode 100644 index 0000000000..1a02158aa0 --- /dev/null +++ b/documentation/docs/pages/developers/clients/socks5/setup.mdx @@ -0,0 +1,41 @@ +# Client setup +## Viewing command help + +You can check that your binaries are properly compiled with: + +``` +./nym-socks5-client --help +``` + +You can check the necessary parameters for the available commands by running: + +``` +./nym-client --help +``` + +## Initialising a new client instance +Before you can use the client, you need to initalise a new instance of it, which can be done with the following command: + +``` +./nym-socks5-client init --id docs-example --use-reply-surbs true --provider Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf +``` + +The `--id` in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is **never** transmitted over the network. + +The `--use-reply-surbs` field denotes whether you wish to send [SURBs](../../network/concepts/anonymous-replies) along with your request. It defaults to `false`, we are explicitly setting it as `true`. It defaults to `false` for compatibility with versions of the pre-smoosh `nym-network-requester` binary which will soon be deprecated. + +The `--provider` field needs to be filled with the Nym address of an Exit Gateway that can make network requests on your behalf. You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). + +## Choosing a Gateway +By default - as in the example above - your client will choose a random gateway to connect to. + +However, there are several options for choosing a gateway, if you do not want one that is randomly assigned to your client: +* If you wish to connect to a specific gateway, you can specify this with the `--gateway` flag when running `init`. +* You can also choose a gateway based on its location relative to your client. This can be done by appending the `--latency-based-selection` flag to your `init` command. This command means that to select a gateway, your client will: + * fetch a list of all availiable gateways + * send few ping messages to all of them, and measure response times. + * create a weighted distribution to randomly choose one, favouring ones with lower latency. + +You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). + +> Note this doesn't mean that your client will pick the closest gateway to you, but it will be far more likely to connect to gateway with a 20ms ping rather than 200ms diff --git a/documentation/docs/pages/developers/clients/socks5/usage.mdx b/documentation/docs/pages/developers/clients/socks5/usage.mdx new file mode 100644 index 0000000000..be79556af8 --- /dev/null +++ b/documentation/docs/pages/developers/clients/socks5/usage.mdx @@ -0,0 +1,62 @@ +# Running the socks5 client + +You can run the initialised client by doing this: + +``` +./nym-socks5-client run --id docs-example +``` + +## Automating your socks5 client with systemd + +Create a service file for the socks5 client at `/etc/systemd/system/nym-socks5-client.service`: + +```ini +[Unit] +Description=Nym Socks5 Client +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym # replace this with whatever user you wish +LimitNOFILE=65536 +ExecStart=/home/nym/nym-socks5-client run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Now enable and start your socks5 client: + +``` +systemctl enable nym-socks5-client.service +systemctl start nym-socks5-client.service +# you can always check your socks5 client has succesfully started with: +systemctl status nym-socks5-client.service +``` + +## Using your Socks5 Client + +After completing the steps above, your local Socks5 Client will be listening on `localhost:1080` ready to proxy traffic to the Network Requester set as the `--provider` when initialising. + +When trying to connect your app, generally the proxy settings are found in `settings->advanced` or `settings->connection`. + +Here is an example of setting the proxy connecting in Blockstream Green: + +![Blockstream Green settings](/images/wallet-proxy-settings/blockstream-green.gif) + +Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: **localhost**, port: **1080**). + +In some other applications, this might be written as **localhost:1080** if there's only one proxy entry field. + +## Useful Commands + +**no-banner** + +Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + +**build-info** + +A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. diff --git a/documentation/docs/pages/developers/clients/websocket/_meta.json b/documentation/docs/pages/developers/clients/websocket/_meta.json new file mode 100644 index 0000000000..5e4171b09e --- /dev/null +++ b/documentation/docs/pages/developers/clients/websocket/_meta.json @@ -0,0 +1,7 @@ +{ + "setup": "Setup", + "usage": "Usage", + "config": "Config", + "examples": "Examples", + "commands": "Commands" +} diff --git a/documentation/docs/pages/developers/clients/websocket/commands.mdx b/documentation/docs/pages/developers/clients/websocket/commands.mdx index f2c7c7b0e4..e7a30285cb 100644 --- a/documentation/docs/pages/developers/clients/websocket/commands.mdx +++ b/documentation/docs/pages/developers/clients/websocket/commands.mdx @@ -25,7 +25,7 @@ Options: -V, --version Print version ``` -### `init` +## `init` ```sh Initialise a Nym client. Do this first! @@ -54,7 +54,7 @@ Options: Print help ``` -### `run` +## `run` ```sh Run the Nym client with provided configuration client optionally overriding set parameters @@ -77,9 +77,9 @@ Options: Print help ``` -### `import-credential` +## `import-credential` -### `list-gateways` +## `list-gateways` ```sh List all registered with gateways @@ -91,7 +91,7 @@ Options: -h, --help Print help ``` -### `switch-gateway` +## `switch-gateway` ```sh Change the currently active gateway. Note that you must have already registered with the new gateway! @@ -103,7 +103,7 @@ Options: -h, --help Print help ``` -### `build-info` +## `build-info` ```sh Show build information of this binary @@ -117,18 +117,18 @@ Example output: ```sh Binary Name: nym-client -Build Timestamp: 2024-10-29T09:48:31.988049207Z -Build Version: 1.1.42 -Commit SHA: 299552881810511273af13eb135297a4cf7a38de -Commit Date: 2024-10-29T10:48:07.000000000+01:00 -Commit Branch: max/new-docs-framework -rustc Version: 1.80.0 -rustc Channel: stable +Build Timestamp: 2024-12-10T11:37:07.839071360Z +Build Version: 1.1.44 +Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd +Commit Date: 2024-11-26T11:53:05.000000000+01:00 +Commit Branch: master +rustc Version: 1.84.0-nightly +rustc Channel: nightly cargo Profile: release ``` -### `completions` +## `completions` ```sh Generate shell completions @@ -141,7 +141,7 @@ Options: -h, --help Print help ``` -### `generate-fig-spec` +## `generate-fig-spec` ```sh Generate Fig specification diff --git a/documentation/docs/pages/developers/tools/nym-cli/commands.mdx b/documentation/docs/pages/developers/tools/nym-cli/commands.mdx index 1117807a5a..9e15fc4eb3 100644 --- a/documentation/docs/pages/developers/tools/nym-cli/commands.mdx +++ b/documentation/docs/pages/developers/tools/nym-cli/commands.mdx @@ -67,7 +67,7 @@ Options: Print help ``` -### `account create` +## `account create` ```sh Create a new mnemonic - note, this account does not appear on the chain until the account id is used in a transaction @@ -92,7 +92,7 @@ Options: Print help ``` -### `account balance` +## `account balance` ```sh Gets the balance of an account @@ -124,7 +124,7 @@ Options: Print help ``` -### `account pub-key` +## `account pub-key` ```sh Gets the public key of an account @@ -152,7 +152,7 @@ Options: Print help ``` -### `account send` +## `account send` ```sh Sends tokens to another account @@ -183,7 +183,7 @@ Options: Print help ``` -### `account send-multiple` +## `account send-multiple` ```sh Batch multiple token sends @@ -241,7 +241,7 @@ Options: Print help ``` -### `signature sign` +## `signature sign` ```sh Sign a message @@ -267,7 +267,7 @@ Options: Print help ``` -### `signature verify` +## `signature verify` ```sh Verify a message @@ -329,7 +329,7 @@ Options: Print help ``` -### `ecash issue-ticket-book` +## `ecash issue-ticket-book` ```sh Usage: nym-cli ecash issue-ticket-book [OPTIONS] <--client-config |--output-file > @@ -366,7 +366,7 @@ Options: Print help ``` -### `ecash recover-ticket-book` +## `ecash recover-ticket-book` ```sh Usage: nym-cli ecash recover-ticket-book [OPTIONS] --client-config @@ -389,7 +389,7 @@ Options: Print help ``` -### `ecash import-ticket-book` +## `ecash import-ticket-book` ```sh Usage: nym-cli ecash import-ticket-book [OPTIONS] --credentials-store <--credential-data |--credential-path > <--standalone|--full> @@ -422,13 +422,13 @@ Options: ## `coconut` -### `coconut generate-freepass` +## `coconut generate-freepass` -### `coconut issue-credentials` +## `coconut issue-credentials` -### `coconut recover-credentials` +## `coconut recover-credentials` -### `coconut import-credential` +## `coconut import-credential` ## `block` ```sh @@ -460,7 +460,7 @@ Options: Print help ``` -### `block get` +## `block get` ```sh Gets a block's details and prints as JSON @@ -486,7 +486,7 @@ Options: Print help ``` -### `block time` +## `block time` ```sh Gets the block time at a height @@ -512,7 +512,7 @@ Options: Print help ``` -### `block current-height` +## `block current-height` ```sh Gets the current block height @@ -568,7 +568,7 @@ Options: Print help ``` -### `cosmwasm upload` +## `cosmwasm upload` ```sh Upload a smart contract WASM blob @@ -595,7 +595,7 @@ Options: Print help ``` -### `cosmwasm init` +## `cosmwasm init` ```sh Init a WASM smart contract @@ -633,7 +633,7 @@ Options: Print help ``` -### `cosmwasm generate-init-message` +## `cosmwasm generate-init-message` ```sh Generate an instantiate message @@ -665,7 +665,7 @@ Options: Print help ``` -### `cosmwasm migrate` +## `cosmwasm migrate` ```sh Migrate a WASM smart contract @@ -697,7 +697,7 @@ Options: Print help ``` -### `cosmwasm execute` +## `cosmwasm execute` ```sh Execute a WASM smart contract method @@ -758,7 +758,7 @@ Options: Print help ``` -### `tx get` +## `tx get` ```sh Get a transaction by hash or block height @@ -784,7 +784,7 @@ Options: Print help ``` -### `tx query` +## `tx query` ```sh Query for transactions @@ -841,7 +841,7 @@ Options: Print help ``` -### `vesting-schedule create` +## `vesting-schedule create` ```sh Creates a vesting schedule @@ -878,7 +878,7 @@ Options: Print help ``` -### `vesting-schedule query` +## `vesting-schedule query` ```sh Query for vesting schedule @@ -904,7 +904,7 @@ Options: Print help ``` -### `vesting-schedule vested-balance` +## `vesting-schedule vested-balance` ```sh Get the amount that has vested and is free for withdrawal, delegation or bonding @@ -930,7 +930,7 @@ Options: Print help ``` -### `vesting-schedule withdraw-vested` +## `vesting-schedule withdraw-vested` ```sh Withdraw vested tokens (note: the available amount excludes anything delegated or bonded before or after vesting) @@ -986,7 +986,7 @@ Options: Print help ``` -### `mixnet query` +## `mixnet query` ```sh Query the mixnet directory @@ -1015,7 +1015,7 @@ Options: Print help ``` -### `mixnet delegators` +## `mixnet delegators` ```sh Manage your delegations @@ -1050,7 +1050,7 @@ Options: Print help ``` -### `mixnet operators` +## `mixnet operators` ```sh Manage a mixnode or gateway you operate @@ -1104,4 +1104,4 @@ Options: Print help ``` -### `generate-fig ` +## `generate-fig ` diff --git a/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx b/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx index 05a5c86423..6da843967b 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx @@ -17,13 +17,13 @@ However, the offline approach introduces certain limitations. - Any potential repercussions against double spenders can only be implemented once the user requests a new credential for their zk-nym Generator (aka they have to 'top up' and buy more bandwidth allowance), assuming they haven't altered their identifier (the Bech32 address). An exploitable scenario arises from these limitations: -- A malicious user purchases bandwidth and aggregates a valid zk-nym credential in the standard way, worth $10 of crypto/fiat. Subsequently, the malicious user proceeds to sell the credential to 100 users for $1 each, allowing each user to generate zk-nym credits of 100MB from this **valid** credential. Under the offline approach, entry nodes forego double-spending checks; so long as the clients all used different ingress Gateways, all 100 users could access the network without obtaining a subscription. As bandwidth consumption is tracked locally between client and ingress node, and each zk-nym credit is rerandomised, there is no way that ingress Gateways would know that the zk-credential used by the client has been shared with other parties. This loophole highlights the need for stringent measures to counter such potential abuses within the system, without creating either speed bottlenecks (in the case of the Online model) or impacting the anonymity of the system. We can, however, mitigate this problem without doing either of these things. +- A malicious user purchases bandwidth and aggregates a valid zk-nym credential in the standard way, worth $10 of crypto/fiat. Subsequently, the malicious user proceeds to sell the credential to 100 users for $1 each, allowing each user to generate zk-nym tickets of 100MB from this **valid** credential. Under the offline approach, entry nodes forego double-spending checks; so long as the clients all used different ingress Gateways, all 100 users could access the network without obtaining a subscription. As bandwidth consumption is tracked locally between client and ingress node, and each zk-nym ticket is rerandomised, there is no way that ingress Gateways would know that the zk-credential used by the client has been shared with other parties. This loophole highlights the need for stringent measures to counter such potential abuses within the system, without creating either speed bottlenecks (in the case of the Online model) or impacting the anonymity of the system. We can, however, mitigate this problem without doing either of these things. ## Solution to Offline Double Spending To efficiently prevent the fraudulent use of tickets within the Nym network, a two-tiered solution is in place that combines (1) the immediate detection of double-spending attempts at the level of individuals ingress Gateways and (2) subsequent identification and blacklisting of offending clients at the Quorum level. ### Entry Node Implementation: Real-Time Ticket Unspending Validation -Each spent zk-nym credit contains as an attribute a unique serial number, which is revealed in plaintext to the respective ingress Gateway. Each Gateway has a copy of a [Bloom Filter](https://www.geeksforgeeks.org/bloom-filters-introduction-and-python-implementation/) - on receiving a credit, it will check against its copy of a local database to check whether this serial number has already been seen. If so, it rejects the credit as being double-spent and the client's connection request is rejected. If not, it will add the serial number to its local DB. +Each spent zk-nym ticket contains as an attribute a unique serial number, which is revealed in plaintext to the respective ingress Gateway. Each Gateway has a copy of a [Bloom Filter](https://www.geeksforgeeks.org/bloom-filters-introduction-and-python-implementation/) - on receiving a ticket, it will check against its copy of a local database to check whether this serial number has already been seen. If so, it rejects the ticket as being double-spent and the client's connection request is rejected. If not, it will add the serial number to its local DB. > Since each time a zk-nym credential is rerandomised its serial number is changed, the serial number being shared in no way identifies a client or user. @@ -32,6 +32,6 @@ Each Gateway will periodically share their serial numbers with the Quorum and re > Crucially, ingress Gateways refrain from extensive computations to identify the original ticket owner, and avoids broadcasting information about the double-spending attempt to other ingress Gateways. The entry node is also not involved in any global blacklisting process of the clients. The sole purpose of this check is to swiftly identify any attempts at double-spending and add the seen ticket's serial number to the local DB cache. ### Nym-API Implementation: Blacklisting and Penalties for Double-Spenders -All Gateways periodically forward the collected credits to the Quorum, enabling them to pinpoint and blacklist any clients who double spend. Upon receiving the credits, the Quorum appends all the incoming serial numbers to the global list of spend zk-nym serial numbers and proceed with the identification process for any malicious users engaging in double-spending. +All Gateways periodically forward the collected tickets to the Quorum, enabling them to pinpoint and blacklist any clients who double spend. Upon receiving the tickets, the Quorum appends all the incoming serial numbers to the global list of spend zk-nym serial numbers and proceed with the identification process for any malicious users engaging in double-spending. This identification phase involves looking for instances of double spending, identifying the id of the double-spending client, and blacklisting this client by its id. Subsequently, when this client requests a new credential, their plaintext public identifier is included in the request. The Quorum then checks if this identifier is blacklisted. If it is, a new credential is not issued. Furthermore, since the PSCs are only attainable after depositing NYM as payment, the Quorum has the authority to withhold the deposited NYMs as a punitive measure for any detected instances of double-spending. diff --git a/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx b/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx index 271fc17bf0..3b4a296eff 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx @@ -2,23 +2,20 @@ import { Callout } from 'nextra/components' # Rerandomisation & Incremental Spend -Each zk-nym credit will not be valid for the entire amount of data that the credential aggregated from the PSCs is; if the aggregated credential is worth (e.g.) 10GB of Mixnet data, each credit will be worth far less (e.g. 100MB). This amount will be globally uniform in order to avoid situations where differently sized credits allow for patterns to emerge. +Each ticket will not be valid for the entire amount of data that the ticketbook aggregated from the PSCs is; if the aggregated ticketbook is worth (e.g.) 10GB of Mixnet data, each ticket will be worth far less (e.g. 100MB). This amount will be globally uniform in order to avoid situations where differently sized tickets allow for patterns to emerge. The functionality included in the following code block examples were added to the [nym-cli tool](../tools/nym-cli.md) for illustrative purposes only: this is not necessarily how credentials will be accessed in the future. - **Furthermore, the `nym-cli` uses the words 'tickets' in place of 'credits' and 'ticketbook' in place of 'aggregated credential': this is wording that we are moving away from now in place of the simpler credential/credit.** This will be updated in the `nym-cli` tool in a future release. - - The numbers used in this high level overview are for illustration purposes only. The figures used in production will potentially vary. Note that individual zkNym sizes will be uniform across the Network. + The numbers used in this high level overview are for illustration purposes only. The figures used in production will potentially vary. Note that individual ticket sizes will be uniform across the Network. - -## Why not spend the entire credential at once? +## Why a 'ticketbook', not individual 'tickets', and why not spend them all at once? This is to account for the need for a client to change their ingress Gateway, either because the Gateway itself has gone down / is not offering the required bandwidth, or because a user might simply want to split their traffic across multiple Gateways for extra privacy. -This means that clients are not tied to particular Gateways they have 'spent' their entire subscription amount with; if the ingress Gateway goes down, or the client simply wishes to use another ingress Gateway, the user has multiple other zk-nym credits they can use that account for their remaining purchased bandwidth. +This means that clients are not tied to particular Gateways they have 'spent' their entire subscription amount with; if the ingress Gateway goes down, or the client simply wishes to use another ingress Gateway, the user has multiple other tickets they can use that account for their remaining purchased bandwidth. -Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable credits ('tickets' on this command output) from a single aggregated credential ('ticketbook' below): +Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable tickets from a single ticketbook aggregated from PSCs: ```sh ❯ ./nym-cli ecash generate-ticket --credential-storage storage.db --provider 6qidVK21zpHD298jdDa1RRpbRozP29ENVyqcSbm6hQrG --full diff --git a/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx b/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx index 90db009613..e6554299b6 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx @@ -1,14 +1,12 @@ import { Callout } from 'nextra/components' # Unlinkability -Each time a credential is requested by an ingress Gateway to prove that a client has purchased data to send through the Mixnet, instead of sending that credential, the Requester's device will produce a zk-nym credit. This is a rereandomised value that is able to be verified as being legitimate (in that it was created by a valid root credential) but **not linked to any other credits**, either previously generated or to be generated in the future. This feature also allows for a credential to be split across multiple ingress Gateways / connections and [incrementally spent](./rerandomise.md) over time. +Each time a credential is requested by an ingress Gateway to prove that a client has purchased data to send through the Mixnet the Requester's device will produce a ticket. This is a rereandomised value that is able to be verified as being legitimate (in that it was created by a valid root ticketbook) but **not linked to any other tickets**, either previously generated or to be generated in the future. This feature also allows for a single ticketbook to allow access to be split across multiple ingress Gateways / connections and [incrementally spent](./rerandomise.md) over time. The functionality included in the following code block examples were added to the [nym-cli tool](../tools/nym-cli.md) for illustrative purposes only: this is not necessarily how credentials will be accessed in the future. -**Furthermore, the `nym-cli` uses the words 'tickets' in place of 'credits' and 'ticketbook' in place of 'aggregated credential': this is wording that we are moving away from now in place of the simpler credential/credit.** This will be updated in the `nym-cli` tool in a future release. - The numbers used in this high level overview are for illustration purposes only. The figures used in production will potentially vary. Note that individual zkNym sizes will be uniform across the Network. @@ -24,7 +22,7 @@ PAYMENT FOR TICKET 3: VfZAuVRRHekQYMvFevNAZmPPuwMAfEhTBY8TXatBysbrNXAg8euEGPpJvdbhNfQSznBb9nRSeBUSVoNTToSA6Uj5dXmJ7oE2rCB439DarLMWHWYfQNhw6yhWJhcg6bt7ebBYTs3vVeQgSB5kYuifzJF4QQmK6uJyTNPvpV1J6V8M32PBkGT3JpVB3GUGZiksETf7TaF9wAhMo2QAMxw5ZvaQVve5ea7Mane6cfb2Gx69SRff5zDfEQvKqKnyyZje4SGZgWUeHWVLhRjg4KMTJ3JcsHxEqj2k5qeGeyBbgzcuEtCpYvaytsz7nuZGJsT4Z87gB5Zq4NGuDmekuN977eRJvua2dASNWeHiAzVyvnS7ARN5cdUjjYKYiWgHaYrHGsv26WTDeiu4U3sdJMrLHGFY5ihX7f8sTZqD6Wx5AWjQNbEtKaVHymDogfLcwGCC42gQ2yhKfPUaWJ8H4yMB65YBDXGjATaUzcDmJcZKx8g31j2uTVNSFUesd5CRNEEcTNW7cSFFCishCD3T4eV9SuyZyEXAZ48pazPzc1BysBNHEXQNUEtEAZTKmpghC2pihhfDub6LnMJPo9DDdhCULCbcWbGAPc1vPekPaWvk7wrUTGwp5xoNUhQLW3MeJzMvrMSsqLdursCKB4h4Tk272WCStCPQwAKMYoxjWvMzxoUTTWCkhLKHruMtsehRnai4vhu13jbui6ji1F389gfazm4ctth2s4Yw3H3SaPtRETBfZNvZ7n5UV1MD6Q3qin92gT65iqXEi4zRN3woYcK6ZehiSvgUksdEFAUSxNMgNXKtHEYDS6kA37tn5JdBa2Ex2jLudFfhg6JBM226ZKyj65o6feYPgbJAR3jMCmQRHe6DSFb4aH895EowNMjfGUhwhmnbYB1djp7iFXxPP7575NAerhxEQ1WFnxTfoX7pu1Vc9YZb5priCAVbATCaDkECJsdedM45Vx96Jc6E5NWqD98RhMsPimVJkSfYJmRxH9qugica6WonFFb2YLvXYyhoBA1VHBcRqZJ5KHitS5AegYSoYprUfubMzcYo2hGVEQkGKAsFq6jZgCsbJoGLXt3No317vcowB5f3hqT9FjASHAzW2j8uJ9RRzX7XtrPhArwx4EyPgYzrvgG7xcenoSgQt8poa7aYky56eZTKHVUZgUEt6St32MjcivMvmNdWiAHHDc2ZxzTJHgeuCckX7n19vQ3XNLuXv9oGKNNCi8kHnT4tUnnGXNAWXWuyBgZKWUL8u3y41iW6dLYK3Pw5zfpKZTrq3q3bTLJRN5LnnUuFVnWsC3SNqa6VAAvhTGR9PzxLk8C6HeLP2AsYPpqeQwbaL3Ks6tvPdob3tQPWRBGL4uiKtNZ23tRYZGZLYFWZK7psRSZg5AETejKxztVzAuYovpVUiDq71o331tjqWWV1SzWT13Rd1uwz6nHtsjgao2863YaizKARcYr1j9MKtNfDs483yho6i7tbCRR9M4CPLqdiKEaRyVC1FP4F3sejA6nZTuAA35JWUzX6BBj7wgdypMLdMmmtcCZm3bRrF3GvJJs67U8JWRc6dnoGUDaD7rUu ``` -Now lets generate another credit to spend either topping up once the previous one's data allowance has been used, or with another Gateway. Notice that the `ticket-index` is the same: this is generated from the same aggregated credential as the one above! +Now lets generate another ticket to spend either topping up once the previous one's data allowance has been used, or with another Gateway. Notice that the `ticket-index` is the same: this is generated from the same aggregated credential as the one above! ```sh ❯ ./nym-cli ecash generate-ticket --credential-storage storage.db --provider 6qidVK21zpHD298jdDa1RRpbRozP29ENVyqcSbm6hQrG --ticket-index=3 @@ -37,4 +35,4 @@ PAYMENT FOR TICKET 3: Vev3SmwWtH5vbnejX5Zzc1EcxXAgveqHpKNN8arxXaWLhFcEpdcZ6n7qr3NrQUNURWsK2AsUiX8aSiGSjMPEY3iDE3aDYnjYERVow8RKUmQiYSKvz7v9cEJxt97JAHBfu9WYNHXTnLFSJwWuFtBdzY5dzPdzGckFenGCysa1ZBHGADHChDVXKoPHXxpn5qyJxmi48coUQDptR64QgkCeQ8RRZ396Lxw2NKFSjqavCMMDVm3g1rW7cYyPanBhkoAUzPU9KXX1rtmhD6F9gV89mGZ8fm7ByDuKuYU28seLQ7GkVKkhNeRW9XxbjSiyscTnMUzJ24R5VbSdr141BaquUHezdUTzmA2EjAtcyyiVrCMV13cc96CRbMXENP2soUzckFnh1qPnrfKCvX4JYkztq7UgPT2mZEnSTDW4C6Z2NVCNBPNLqUSYrU4id8Jzcp1mBxqJjdYcQ7P5fWJbT5Q9NAq44PCgfXpsUkNoj35QVQvKXKLb5oNGqnua5YC1WBPcENcpS7ZPWpk2hwe8VK4gNgnwQtWH2RPmWbvBREAV97vS1vKNHJyry9sD2PiMJGSmBnb1bKsGxR9UQN3YvRsdGHzyJHzAMTzxbFJBqMPmxjSHJR4UdwzhB81Ludu1RAffTvecWFxmWH5bNymCQjw3wey7Uequcxgyy8KAWYDzvHGwCZQbHQXghsYREiqquZWaa8hX3iTNBFUtEk8PRVT78MoFNdeBWNjsLr8zyZ5EGnf4kqmw3a91g5p5vywf6e3LgMu19VHjPSNtKMNXiatkPEVjsCuCppmV4sB7FsdKKWcMUSWLsdmrDBg9PStHr7NaJRzLL5E91gvysmB36Nob9cHeHSZj3wM4NVVjFfZeRqQf4bi7ahfXjeeBetgDpqx7JcbU6tTN4JpcGUpp7fp4MhTq7MeVQMLweGUVLqewKgAGzCvEmrK6dzLd3U1P9vkAAVZ3cCAKUywnHGxoxDeEfexP1g1EqJLtKNZVKPf7hSMWqGhoQ36K7y5GnyZ5YhQ7jcDME9orm5w4StoxoDdCPcjbakKG7UaTHuhd7tU1mUffXcEvVerkXoQK9SEaKvGks21RBhW86aHUzJWVbkiDzdaqjJWbmzLV8FKvNxNyzucoH2rq8LiHRMZfV1H3SkVSa4j2Ktw7ZGoQfdj8DgekxXSR2nHPfhybzKYXTBqFo2ACisxkjR4rXr9Xo6eYywQhQ1MP6aYgYCAXFGHPoFf7kx7Jns5sWvHRBdaMF65zeFF2m5NDuMWETtLgFfsyNgR84vfSqTfzj2gsUykRei7q9N4LKmiDwBALTAEcTvZpLtXBjc8JaB9PUeBw7DoSiSK376sGrQ9F6ZGTngXACNz1TbvYhtau4bDa6KC2Qn7wmoyrphpn7TtM1jdwGBxLcaEEWZKQHvWVfTyL2itjqnrcAZkxYdCj56oQYwpWfKQk3zJEUA6SYHqyJjaLNVK6u25j7969EWjdpTsJ8qSsZgXi3T7dQqiwintZbUUUKRq7egN1SGVnA6Wup91uKrYUWEWMqVu4g8ipmRsLD9iXHHr3yA21Cka7pqk1FxR9BFTAnkk1 ``` -These are both generated by the _same_ underlying credential and used in a way that they cannot be tied to each other. An ingress Gateway might (for instance) get 100 connection requests from 100 Nym clients, each validated with a zk-nym credi t. It has no way of knowing whether these are all from the same single subscription, or 100 different ones. +These are both generated by the _same_ underlying ticketbook and used in a way that they cannot be tied to each other. An ingress Gateway might (for instance) get 100 connection requests from 100 Nym clients, each validated with a ticket. It has no way of knowing whether these are all from the same single subscription, or 100 different ones. diff --git a/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx b/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx index ec019a3266..14cc7488c6 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx @@ -14,7 +14,7 @@ import { Callout } from 'nextra/components' Generation of zk-nyms involves the following actors / pieces of infrastructure: -- **Requester needing a zk-nym** for example a single user using the NymVPN app, or a company purchasing zk-nyms to distribute to their app users, in the instance of an app integrating a Mixnet client via one of the SDKs. The Requester is represented by a Bech32 address on the Nyx blockchain. +- **Requester needing a zk-nym** for example a single user using the NymVPN app, or a company purchasing zk-nyms to distribute to their app users, in the instance of an app integrating a Mixnet client via one of the SDKs. The Requester is represented by a Bech32 address on the Nyx blockchain. - [NymAPI](../../architecture/nyx/nym-api) instances working together on signature generation and spent credential validation, referred to as the **NymAPI Quorum**. Members of the Quorum are a subset of the Nyx chain Validator set (other tasks they perform include a multisig used for triggering reward payouts to the Network Infrastructure Node Operators and maintaining the global Bloom Filter for double-spend protection). - **OrderAPI**: an API creating crypto/fiat to `NYM` swaps and then depositing the NYM tokens in a smart contract managed by the NymAPI Quroum for payment verification. Implementation details of the API will be released in the coming months. @@ -26,8 +26,8 @@ Generation happens in 3 distinct stages: From the perspective of the Requester most of this happens under the hood, but results in the creation and usage of an **unlinkable, rerandomisable anonymous proof-of-payment credential** - a zk-nym - with which to access the Mixnet without fear of doxxing themselves via linking app usage and payment information. The user experience is further enhanced by the fact that a single credential can be split into multiple small zk-nyms, meaning that a Requester may buy a large chunk of bandwidth but 'spend' this in the form of multiple zk-nyms with different ingress Gateways. Whilst this happens under the hood, what it affords the Requester is an ease of experience in that they have to 'top up' their bandwidth less and are able to chop and change ingress points to the Nym Network as they see fit, akin to the UX of most modern day VPNs and dVPNs. ## Key Generation & Payment -- First, a Cosmos [Bech32 address](https://docs.cosmos.network/main/build/spec/addresses/bech32) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. This is the only identity that the OrderAPI is able to see, and is not able to link this to the zk-nyms that will be generated. This identity never leaves the Requester’s device and there is no email or any personal details needed for signup. If a Requester is simply 'topping up' their subscription, the creation of the address is skipped as it already exists. -- The Requester also generates an ed25519 keypair: this is used to identify and authenticate them in the case of using zk-nyms across several devices as an individual user. However, this is never used in the clear: these keys are used as private attribute values within generated credentials which are verified via zero-knowledge. +- First, a Cosmos [Bech32 address](https://docs.cosmos.network/main/build/spec/addresses/bech32) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. **This is the only identity that the OrderAPI is able to see, and is not able to link this to the zk-nyms that will be generated.** This identity never leaves the Requester’s device and there is no email or any personal details needed for signup. If a Requester is simply 'topping up' their subscription, the creation of the address is skipped as it already exists. +- The Requester also generates an ed25519 keypair: this is used to identify and authenticate them in the case of using zk-nyms across several devices as an individual user. However, **this is never used in the clear**: these keys are used as private attribute values within generated credentials which are verified via zero-knowledge and not publicly exposed. - The Requester can then interact with various payment backends to pay for their zk-nyms with crypto, fiat options, or natively with NYM tokens. - Payment options will trigger the OrderAPI. This will: - Create a swap for `` to `NYM` tokens. @@ -35,24 +35,24 @@ From the perspective of the Requester most of this happens under the hood, but r - The Requester sends a request to each member of the Quorum requesting a zk-nym credential. This request is signed with their private key and includes the transaction hash of the NYM deposit into the deposit contract, performed either by themselves or the OrderAPI. ## Issue zk-nym -At this point, NYM tokens have been deposited into the smart contract controlled by the Quorum's multisig and a zk-nym credential has been requested. Next, each member of the Quorum who responds to the Requester's request for a zk-nym checks the validity and returns a PSC signed with part of the master key (since this is a threshold cryptsystem, not all members of the Quroum must respond to create a zk-nym, only enough to pass the threshold). The process looks like this: +At this point, NYM tokens have been deposited into the smart contract controlled by the Quorum's multisig and a zk-nym has been requested. Next, each member of the Quorum who responds to the Requester's request for a zk-nym checks the validity and returns a partial blinded signature - a 'partial signed credential' ('PSC') - signed with part of the master key (since this is a threshold cryptsystem, not all members of the Quroum must respond to create a zk-nym, only enough to pass the threshold). The process looks like this: - Members of the Quroum performs several checks to verify the request is valid: - They verify the signature sent as part of the request is valid and that the request was made in the last 48 hours. - They verify that the amount requested matches the amount deposited in the transation, the hash of which was signed by the Requester's ed25519 key and sent as part of the request. -- Members then create a partial blinded signature - a 'partial signed credential' ('PSC') - from their fragment of the master key generated and split amongst them at the beginning of the Quroum in the initial DKG ceremony. +- Members then create a PSC from their fragment of the master key generated and split amongst them at the beginning of the Quroum in the initial DKG ceremony. - The member also creates a `key:value` entry in their local cache with the transaction hash as the key, and the PSC + encrypted signature as the value. This is used later for zk-nym validation and is cleaned after a predefined timeout. - These PSCs are given back to the Requester after setting up a secure channel via DH key exchange, with each replying Quorum member also sending their public key for verification that the returned PSC was signed by them. -Once the Requester has received over the threshold number of PSCs they can assemble them into a credential signed by the master key. The Requester never learns this master key (it is a private attribute) but the credential can be verified by the Quroum as being valid by checking for a proof that the credential's private attribute - the value of the master key - is valid. +Once the Requester has received over the threshold number of PSCs they can assemble them into a 'ticketbook' of 'tickets' - spendable credentials - signed by the master key. The Requester never learns this master key (it is a private attribute) but the credential can be verified by the Quroum as being valid by checking for a proof that the credential's private attribute - the value of the master key - is valid. ![](/images/network/deposit-generate.png) ## Spend zk-nym to Access Mixnet -- Once the credential has been aggregated from the PSCs returned from > threshold of Quorum members, smaller 'zk-nym credits' can be generated from it, accounting for smaller chunks of bandwidth which can be 'spent' with ingress Gateways. This occurs entirely offline, on the device of the zk-nym Requester. See pages on the scheme's [unlinkability](unlinkability.md) and [rerandomisation and incremental spending](./rerandomise.md) features for further information on this. -- This zk-nym credit is later presented to the Quorum by the Gateway that collected it, which is used to calculate reward percentages given to Nym Network infrastructure operators by the Quorum, with payouts triggered by their multisig wallet. Both ingress Gateways and the Quorum use spent zk-nym credits when engaging in [double spending protection](./double-spend-prot.md). +- Once the ticketbook has been aggregated from the PSCs returned from > threshold of Quorum members, smaller 'ticket' credentials can be generated from it, accounting for smaller chunks of bandwidth which can be 'spent' with ingress Gateways. This occurs entirely offline, on the device of the zk-nym Requester. See pages on the scheme's [unlinkability](unlinkability.md) and [rerandomisation and incremental spending](./rerandomise.md) features for further information on this. +- This ticket is later presented to the Quorum by the Gateway that collected it, which is used to calculate reward percentages given to Nym Network infrastructure operators by the Quorum, with payouts triggered by their multisig wallet. Both ingress Gateways and the Quorum use spent tickets when engaging in [double spending protection](./double-spend-prot.md). ![](/images/network/use-zknym.png) diff --git a/documentation/docs/pages/styles.css b/documentation/docs/pages/styles.css index 87255c71af..aa26004976 100644 --- a/documentation/docs/pages/styles.css +++ b/documentation/docs/pages/styles.css @@ -1,12 +1,3 @@ -body { - --colorPrimary: #fc8469; - /* --textPrimary: #121726; */ -} - -div.nextra-code-block > div { - background: var(--colorPrimary) !important; -} - footer { text-align: center; } @@ -20,21 +11,23 @@ footer { /* set width entire div */ .nx-mx-auto.nx-flex.nx-max-w-\[90rem\] { max-width: 110rem; - padding: 0%; + left-padding: 1%; } .nextra-toc { - width: 300px !important; + width: 10px !important; padding-right: 0px; padding-left: 0px; - text-align: right; + /* text-align: right; */ border-left: 1px solid #262626; } -/* Code blocks*/ -.nx-absolute.nx-top-0 { - background: var(--colorPrimary) !important; - color: var(--textPrimary) !important; +.nextra-content { + padding-top: 0 !important; + padding-right: 0 !important; + padding-bottom: 0 !important; + padding-left: 0 !important; + margin: 0 !important; } :is(html[class~="dark"] .dark\:nx-bg-primary-300\/10) { @@ -56,14 +49,6 @@ footer { column-width: 500px; } -/* aside ul > li > a:hover{ - background: #fb6e4ea9 !important; -} */ - -/* aside ul > li > a:active{ - background: #fb6e4e !important; -} */ - /* Links*/ .nx-text-primary-600.nx-underline.nx-decoration-from-font { color: var(--colorPrimary) !important; @@ -73,31 +58,6 @@ footer { color: var(--colorPrimary) !important; } -/* Callouts */ -/* -.nextra-callout { - background-color: #fb6e4e21 !important; -} - -.nx-mt-6 .nx-leading-7 > p { - color: var(--colorPrimary) !important; -} -*/ - -/* Callout Secondary */ -/* -:is(html .dark\:nx-border-yellow-200\/30) { - border-color: transparent !important; -} -*/ - -/* Callout Primary */ -/* -:is(html .dark\:nx-border-blue-200\/30) { - border-color: var(--colorPrimary) !important; -} -*/ - /* Chips*/ .chipContained { background-color: var(--colorPrimary) !important; @@ -138,7 +98,6 @@ a.MuiLink-root { color: var(--colorPrimary) !important; } - .MuiPaper-root.MuiAccordion-root { border-radius: 8px; } diff --git a/documentation/docs/pnpm-lock.yaml b/documentation/docs/pnpm-lock.yaml index 3d10a3f4a9..48f2a523b0 100644 --- a/documentation/docs/pnpm-lock.yaml +++ b/documentation/docs/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@apidevtools/swagger-parser': + specifier: ^10.1.0 + version: 10.1.0(openapi-types@12.1.3) '@coreui/coreui': specifier: ^5.1.2 version: 5.1.2(@popperjs/core@2.11.8) @@ -80,6 +83,9 @@ importers: '@nymproject/sdk-full-fat': specifier: '>=1.2.4-rc.2 || ^1' version: 1.2.3 + '@redocly/cli': + specifier: ^1.25.15 + version: 1.25.15(enzyme@3.11.0) '@types/mdx': specifier: ^2.0.13 version: 2.0.13 @@ -139,6 +145,21 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@apidevtools/json-schema-ref-parser@9.0.6': + resolution: {integrity: sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==} + + '@apidevtools/openapi-schemas@2.1.0': + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + + '@apidevtools/swagger-methods@3.0.2': + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + + '@apidevtools/swagger-parser@10.1.0': + resolution: {integrity: sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==} + peerDependencies: + openapi-types: '>=7' + '@babel/code-frame@7.25.9': resolution: {integrity: sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==} engines: {node: '>=6.9.0'} @@ -187,6 +208,13 @@ packages: '@braintree/sanitize-url@6.0.4': resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} + '@cfaester/enzyme-adapter-react-18@0.8.0': + resolution: {integrity: sha512-3Z3ThTUouHwz8oIyhTYQljEMNRFtlVyc3VOOHCbxs47U6cnXs8K9ygi/c1tv49s7MBlTXeIcuN+Ttd9aPtILFQ==} + peerDependencies: + enzyme: ^3.11.0 + react: '>=18' + react-dom: '>=18' + '@chain-registry/client@1.53.1': resolution: {integrity: sha512-fNhNzElizS84XnUX8DejLcCPbJE5Fiw/8pVNE+d3hxB+qSXxFR//W+np4WKhCbjxmNt8ON1DVFzbu5HJQ+TJvA==} @@ -335,9 +363,15 @@ packages: '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + '@emotion/is-prop-valid@1.3.1': resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + '@emotion/memoize@0.9.0': resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} @@ -369,6 +403,9 @@ packages: '@emotion/unitless@0.10.0': resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + '@emotion/use-insertion-effect-with-fallbacks@1.1.0': resolution: {integrity: sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==} peerDependencies: @@ -449,6 +486,9 @@ packages: '@ethersproject/web@5.7.1': resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} + '@exodus/schemasafe@1.3.0': + resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@floating-ui/core@1.6.8': resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} @@ -551,6 +591,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@keplr-wallet/common@0.12.28': resolution: {integrity: sha512-ESQorPZw8PRiUXhsrxED+E1FEWkAdc6Kwi3Az7ce204gMBQDI2j0XJtTd4uCUp+C24Em9fk0samdHzdoB4caIg==} @@ -2356,6 +2399,21 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/cli@1.25.15': + resolution: {integrity: sha512-ZD743CJX4FpMJvGNE9Cm3gNn8LNRzRjyrYNVPi1C4iIEtrFkr5Zq791qv6gUFehWns71svbVyzWD9ftVTdfqYg==} + engines: {node: '>=14.19.0', npm: '>=7.0.0'} + hasBin: true + + '@redocly/config@0.17.1': + resolution: {integrity: sha512-CEmvaJuG7pm2ylQg53emPmtgm4nW2nxBgwXzbVEHpGas/lGnMyN8Zlkgiz6rPw0unASg6VW3wlz27SOL5XFHYQ==} + + '@redocly/openapi-core@1.25.15': + resolution: {integrity: sha512-/dpr5zpGj2t1Bf7EIXEboRZm1hsJZBQfv3Q1pkivtdAEg3if2khv+b9gY68aquC6cM/2aQY2kMLy8LlY2tn+Og==} + engines: {node: '>=14.19.0', npm: '>=7.0.0'} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -2529,6 +2587,9 @@ packages: '@types/react@18.3.12': resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + '@types/stylis@4.2.5': + resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2713,6 +2774,10 @@ packages: resolution: {integrity: sha512-JlqiAl9CPvTm5kKG0QXmVCWNWoC/XyRMOeT77cQlbxXWllgjf6SqUmaNqFon72C2o5OSZids+5FvLdsw6dvWaw==} hasBin: true + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -2728,6 +2793,18 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -2819,6 +2896,10 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + array.prototype.filter@1.0.4: + resolution: {integrity: sha512-r+mCJ7zXgXElgR4IRC+fkvNCeoaavWBs6EdCso5Tbcf+iEMKzBU/His60lt34WEZ9vlb8wDkZvQGcVI5GwkfoQ==} + engines: {node: '>= 0.4'} + array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -2933,6 +3014,9 @@ packages: bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} @@ -2974,6 +3058,9 @@ packages: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -2982,6 +3069,9 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + caniuse-lite@1.0.30001669: resolution: {integrity: sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==} @@ -3019,6 +3109,13 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.0.0: + resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} + engines: {node: '>=18.17'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -3033,6 +3130,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -3044,6 +3144,9 @@ packages: resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} engines: {node: '>=18'} + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -3079,6 +3182,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -3129,6 +3235,9 @@ packages: peerDependencies: webpack: ^5.1.0 + core-js@3.39.0: + resolution: {integrity: sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -3161,6 +3270,16 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -3359,6 +3478,9 @@ packages: supports-color: optional: true + decko@1.2.0: + resolution: {integrity: sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==} + decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} @@ -3438,6 +3560,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + discontinuous-range@1.0.0: + resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -3452,9 +3577,22 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dompurify@3.1.6: resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==} + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -3486,6 +3624,9 @@ packages: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} + encoding-sniffer@0.2.0: + resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -3497,6 +3638,12 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + enzyme-shallow-equal@1.0.7: + resolution: {integrity: sha512-/um0GFqUXnpM9SvKtje+9Tjoz3f1fpBC3eXRFrNs8kpYn69JljciYP7KZTqM/YQbUY9KUjvKB4jo/q+L6WGGvg==} + + enzyme@3.11.0: + resolution: {integrity: sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -3504,6 +3651,9 @@ packages: resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} + es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -3534,6 +3684,9 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} + es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -3696,6 +3849,13 @@ packages: event-stream@4.0.1: resolution: {integrity: sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -3732,9 +3892,16 @@ packages: resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} engines: {node: '>=6'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.0.3: resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} + fast-xml-parser@4.5.0: + resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==} + hasBin: true + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -3792,6 +3959,9 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + foreach@2.0.6: + resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -3940,6 +4110,11 @@ packages: h3@1.13.0: resolution: {integrity: sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==} + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -3970,6 +4145,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + has@1.0.4: + resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} + engines: {node: '>= 0.4.0'} + hash-base@3.1.0: resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} engines: {node: '>=4'} @@ -4027,13 +4206,26 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + html-element-map@1.3.1: + resolution: {integrity: sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + http2-client@1.3.5: + resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==} + + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -4251,6 +4443,9 @@ packages: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} + is-subset@0.1.1: + resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==} + is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} @@ -4311,6 +4506,10 @@ packages: resolution: {integrity: sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==} hasBin: true + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} @@ -4336,6 +4535,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-pointer@0.6.2: + resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -4450,6 +4652,12 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.escape@4.0.1: + resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==} + + lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + lodash.foreach@4.5.0: resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} @@ -4501,9 +4709,15 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + map-stream@0.0.7: resolution: {integrity: sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==} + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + markdown-extensions@1.1.1: resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} engines: {node: '>=0.10.0'} @@ -4511,6 +4725,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true + match-sorter@6.3.4: resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==} @@ -4743,6 +4962,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} @@ -4761,12 +4984,41 @@ packages: mlly@1.7.2: resolution: {integrity: sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==} + mobx-react-lite@4.0.7: + resolution: {integrity: sha512-RjwdseshK9Mg8On5tyJZHtGD+J78ZnCnRaxeQDSiciKVQDUbfZcXhmld0VMxAwvcTnPEHZySGGewm467Fcpreg==} + peerDependencies: + mobx: ^6.9.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + mobx-react@9.1.1: + resolution: {integrity: sha512-gVV7AdSrAAxqXOJ2bAbGa5TkPqvITSzaPiiEkzpW4rRsMhSec7C2NBCJYILADHKp2tzOAIETGRsIY0UaCV5aEw==} + peerDependencies: + mobx: ^6.9.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + mobx@6.13.5: resolution: {integrity: sha512-/HTWzW2s8J1Gqt+WmUj5Y0mddZk+LInejADc79NJadrWla3rHzmRHki/mnEUH1AvOmbNTZ1BRbKxr8DSgfdjMA==} modern-ahocorasick@1.0.1: resolution: {integrity: sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA==} + moo@0.5.2: + resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -4791,6 +5043,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + nearley@2.20.1: + resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} + hasBin: true + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -4853,6 +5109,10 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch-h2@2.3.0: + resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==} + engines: {node: 4.x || >=6.0.0} + node-fetch-native@1.6.4: resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} @@ -4869,6 +5129,9 @@ packages: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} + node-readfiles@0.2.0: + resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} + node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -4891,6 +5154,25 @@ packages: resolution: {integrity: sha512-O/j/ROyX0KGLG7O6Ieut/seQ0oiTpHF2tXAcFbpdTLQFiaNtkyTXXocM1fwpaa60dg1qpWj0nHlbNhx6qwuENQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + oas-kit-common@1.0.8: + resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==} + + oas-linter@3.2.2: + resolution: {integrity: sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==} + + oas-resolver@2.5.6: + resolution: {integrity: sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==} + hasBin: true + + oas-schema-walker@1.1.5: + resolution: {integrity: sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==} + + oas-validator@5.0.8: + resolution: {integrity: sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4903,6 +5185,10 @@ packages: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -4943,6 +5229,12 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + openapi-sampler@1.6.0: + resolution: {integrity: sha512-0PKhql1Ms38xSngEztcNQ7EXgssR2jAyVX7RckEln4reynIr/HHwuwM29cDEpiNkk4OkrHoc+7Li9V7WTAPYmw==} + + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -4985,9 +5277,18 @@ packages: parse-url@8.1.0: resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + parse5@7.2.0: resolution: {integrity: sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -5025,6 +5326,12 @@ packages: pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + perfect-scrollbar@1.5.6: + resolution: {integrity: sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==} + + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} @@ -5056,6 +5363,14 @@ packages: pkg-types@1.2.1: resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + polished@4.3.1: + resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} + engines: {node: '>=10'} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -5101,6 +5416,10 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.4.47: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} @@ -5109,6 +5428,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + process-warning@1.0.0: resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} @@ -5158,12 +5481,22 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + + railroad-diagrams@1.0.0: + resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} + rainbow-sprinkles@0.17.3: resolution: {integrity: sha512-tu00bbDozceDz/ajL8YzDn5L0mf0uVHR93v2tfGKKbemKeuPk8R4mXUokleKsA24vzrPiw26iMK53HdRy6HLag==} peerDependencies: '@vanilla-extract/css': ^1 '@vanilla-extract/dynamic': ^2 + randexp@0.4.6: + resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} + engines: {node: '>=0.12'} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -5210,6 +5543,11 @@ packages: '@types/react': optional: true + react-shallow-renderer@16.15.0: + resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-stately@3.33.0: resolution: {integrity: sha512-DNPOxYAPuhuXwSuE1s1K7iSgqG2QOBUZq3bsLAd4gUUZje6Qepkhe7TzK2LWarQYAZ3gC9Xhmnz8ie1fdCo0GA==} peerDependencies: @@ -5225,6 +5563,11 @@ packages: '@types/react': optional: true + react-tabs@6.0.2: + resolution: {integrity: sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==} + peerDependencies: + react: ^18.0.0 + react-textarea-autosize@8.5.4: resolution: {integrity: sha512-eSSjVtRLcLfFwFcariT77t9hcbVJHQV76b51QjQGarQIHml2+gM2lms0n3XrhnDmgK5B+/Z7TmQk5OHNzqYm/A==} engines: {node: '>=10'} @@ -5265,10 +5608,23 @@ packages: redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + redoc@2.2.0: + resolution: {integrity: sha512-52rz/xJtpUBc3Y/GAkaX03czKhQXTxoU7WnkXNzRLuGwiGb/iEO4OgwcgQqtwHWrYNaZXTyqZ4MAVXpi/e1gAg==} + engines: {node: '>=6.9', npm: '>=3.0.0'} + peerDependencies: + core-js: ^3.1.4 + mobx: ^6.0.4 + react: ^16.8.4 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.4 || ^17.0.0 || ^18.0.0 + styled-components: ^4.1.1 || ^5.1.1 || ^6.0.5 + reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} + reftools@1.1.9: + resolution: {integrity: sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -5335,6 +5691,10 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true + ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -5350,6 +5710,9 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rst-selector-parser@2.2.3: + resolution: {integrity: sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -5432,6 +5795,9 @@ packages: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} hasBin: true + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -5451,6 +5817,24 @@ packages: shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} + should-equal@2.0.0: + resolution: {integrity: sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==} + + should-format@3.0.3: + resolution: {integrity: sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==} + + should-type-adaptors@1.1.0: + resolution: {integrity: sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==} + + should-type@1.4.0: + resolution: {integrity: sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==} + + should-util@1.0.1: + resolution: {integrity: sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==} + + should@13.2.3: + resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} + side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -5465,6 +5849,9 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-websocket@9.1.0: + resolution: {integrity: sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -5473,6 +5860,10 @@ packages: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} + slugify@1.4.7: + resolution: {integrity: sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==} + engines: {node: '>=8.0.0'} + sonic-boom@2.8.0: resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} @@ -5522,6 +5913,9 @@ packages: std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + stickyfill@1.1.1: + resolution: {integrity: sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==} + stream-combiner@0.2.2: resolution: {integrity: sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==} @@ -5600,9 +5994,19 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + styled-components@6.1.13: + resolution: {integrity: sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==} + engines: {node: '>= 16'} + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -5619,6 +6023,9 @@ packages: stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + stylis@4.3.4: resolution: {integrity: sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==} @@ -5647,6 +6054,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swagger2openapi@7.0.8: + resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} + hasBin: true + symbol-observable@2.0.3: resolution: {integrity: sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==} engines: {node: '>=0.10'} @@ -5767,6 +6178,9 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.0: resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} @@ -5809,6 +6223,11 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + uint8arrays@3.1.0: resolution: {integrity: sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog==} @@ -5818,6 +6237,10 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici@6.21.0: + resolution: {integrity: sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==} + engines: {node: '>=18.17'} + unenv@1.10.0: resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==} @@ -5943,6 +6366,9 @@ packages: uqr@0.1.2: resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -5952,6 +6378,9 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + url-template@2.0.8: + resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} + use-callback-ref@1.3.2: resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} @@ -6071,9 +6500,17 @@ packages: webpack-cli: optional: true + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -6108,6 +6545,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6141,6 +6581,9 @@ packages: yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -6150,10 +6593,18 @@ packages: engines: {node: '>= 14'} hasBin: true + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@17.0.1: + resolution: {integrity: sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==} + engines: {node: '>=12'} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -6187,6 +6638,27 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@apidevtools/json-schema-ref-parser@9.0.6': + dependencies: + '@jsdevtools/ono': 7.1.3 + call-me-maybe: 1.0.2 + js-yaml: 3.14.1 + + '@apidevtools/openapi-schemas@2.1.0': {} + + '@apidevtools/swagger-methods@3.0.2': {} + + '@apidevtools/swagger-parser@10.1.0(openapi-types@12.1.3)': + dependencies: + '@apidevtools/json-schema-ref-parser': 9.0.6 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + '@babel/code-frame@7.25.9': dependencies: '@babel/highlight': 7.25.9 @@ -6250,6 +6722,17 @@ snapshots: '@braintree/sanitize-url@6.0.4': {} + '@cfaester/enzyme-adapter-react-18@0.8.0(enzyme@3.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + enzyme: 3.11.0 + enzyme-shallow-equal: 1.0.7 + function.prototype.name: 1.1.6 + has: 1.0.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-shallow-renderer: 16.15.0(react@18.3.1) + '@chain-registry/client@1.53.1': dependencies: '@chain-registry/types': 0.50.1 @@ -6736,10 +7219,16 @@ snapshots: '@emotion/hash@0.9.2': {} + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + '@emotion/is-prop-valid@1.3.1': dependencies: '@emotion/memoize': 0.9.0 + '@emotion/memoize@0.8.1': {} + '@emotion/memoize@0.9.0': {} '@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1)': @@ -6785,6 +7274,8 @@ snapshots: '@emotion/unitless@0.10.0': {} + '@emotion/unitless@0.8.1': {} + '@emotion/use-insertion-effect-with-fallbacks@1.1.0(react@18.3.1)': dependencies: react: 18.3.1 @@ -6927,6 +7418,8 @@ snapshots: '@ethersproject/properties': 5.7.0 '@ethersproject/strings': 5.7.0 + '@exodus/schemasafe@1.3.0': {} + '@floating-ui/core@1.6.8': dependencies: '@floating-ui/utils': 0.2.8 @@ -7080,6 +7573,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jsdevtools/ono@7.1.3': {} + '@keplr-wallet/common@0.12.28': dependencies: '@keplr-wallet/crypto': 0.12.28 @@ -9999,6 +10494,61 @@ snapshots: '@react-types/shared': 3.25.0(react@18.3.1) react: 18.3.1 + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/cli@1.25.15(enzyme@3.11.0)': + dependencies: + '@redocly/openapi-core': 1.25.15 + abort-controller: 3.0.0 + chokidar: 3.6.0 + colorette: 1.4.0 + core-js: 3.39.0 + form-data: 4.0.1 + get-port-please: 3.1.2 + glob: 7.2.3 + handlebars: 4.7.8 + mobx: 6.13.5 + node-fetch: 2.7.0 + pluralize: 8.0.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + redoc: 2.2.0(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + semver: 7.6.3 + simple-websocket: 9.1.0 + styled-components: 6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + yargs: 17.0.1 + transitivePeerDependencies: + - bufferutil + - encoding + - enzyme + - react-native + - supports-color + - utf-8-validate + + '@redocly/config@0.17.1': {} + + '@redocly/openapi-core@1.25.15': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.17.1 + colorette: 1.4.0 + https-proxy-agent: 7.0.5 + js-levenshtein: 1.1.6 + js-yaml: 4.1.0 + lodash.isequal: 4.5.0 + minimatch: 5.1.6 + node-fetch: 2.7.0 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - encoding + - supports-color + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.10.4': {} @@ -10200,6 +10750,8 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 + '@types/stylis@4.2.5': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -10616,6 +11168,10 @@ snapshots: fs-extra: 10.1.0 yargs: 17.7.2 + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-import-attributes@1.9.5(acorn@8.13.0): dependencies: acorn: 8.13.0 @@ -10626,6 +11182,16 @@ snapshots: acorn@8.13.0: {} + agent-base@7.1.1: + dependencies: + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -10710,6 +11276,15 @@ snapshots: array-union@2.1.0: {} + array.prototype.filter@1.0.4: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-array-method-boxes-properly: 1.0.0 + es-object-atoms: 1.0.0 + is-string: 1.0.7 + array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.7 @@ -10845,6 +11420,8 @@ snapshots: bn.js@5.2.1: {} + boolbase@1.0.0: {} + bowser@2.11.0: {} brace-expansion@1.1.11: @@ -10898,10 +11475,14 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-me-maybe@1.0.2: {} + callsites@3.1.0: {} camelcase-css@2.0.1: {} + camelize@1.0.1: {} + caniuse-lite@1.0.30001669: {} cardinal@2.1.1: @@ -10940,6 +11521,29 @@ snapshots: character-reference-invalid@2.0.1: {} + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + + cheerio@1.0.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + encoding-sniffer: 0.2.0 + htmlparser2: 9.1.0 + parse5: 7.2.0 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 6.21.0 + whatwg-mimetype: 4.0.0 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -10963,6 +11567,8 @@ snapshots: dependencies: consola: 3.2.3 + classnames@2.5.1: {} + client-only@0.0.1: {} clipboardy@1.2.2: @@ -10976,6 +11582,12 @@ snapshots: is-wsl: 3.1.0 is64bit: 2.0.0 + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -11010,6 +11622,8 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 + colorette@1.4.0: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -11050,6 +11664,8 @@ snapshots: serialize-javascript: 6.0.2 webpack: 5.95.0 + core-js@3.39.0: {} + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -11105,6 +11721,22 @@ snapshots: crypto-js@4.2.0: {} + css-color-keywords@1.0.0: {} + + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + css-what@6.1.0: {} cssesc@3.0.0: {} @@ -11320,6 +11952,8 @@ snapshots: dependencies: ms: 2.1.3 + decko@1.2.0: {} + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 @@ -11380,6 +12014,8 @@ snapshots: dependencies: path-type: 4.0.0 + discontinuous-range@1.0.0: {} + dlv@1.1.3: {} doctrine@2.1.0: @@ -11395,8 +12031,26 @@ snapshots: '@babel/runtime': 7.25.9 csstype: 3.1.3 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + dompurify@3.1.6: {} + domutils@3.1.0: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + duplexer@0.1.2: {} duplexify@4.1.3: @@ -11438,6 +12092,11 @@ snapshots: emojis-list@3.0.0: {} + encoding-sniffer@0.2.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -11449,6 +12108,36 @@ snapshots: entities@4.5.0: {} + enzyme-shallow-equal@1.0.7: + dependencies: + hasown: 2.0.2 + object-is: 1.1.6 + + enzyme@3.11.0: + dependencies: + array.prototype.flat: 1.3.2 + cheerio: 1.0.0 + enzyme-shallow-equal: 1.0.7 + function.prototype.name: 1.1.6 + has: 1.0.4 + html-element-map: 1.3.1 + is-boolean-object: 1.1.2 + is-callable: 1.2.7 + is-number-object: 1.0.7 + is-regex: 1.1.4 + is-string: 1.0.7 + is-subset: 0.1.1 + lodash.escape: 4.0.1 + lodash.isequal: 4.5.0 + object-inspect: 1.13.2 + object-is: 1.1.6 + object.assign: 4.1.5 + object.entries: 1.1.8 + object.values: 1.2.0 + raf: 3.4.1 + rst-selector-parser: 2.2.3 + string.prototype.trim: 1.2.9 + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -11502,6 +12191,8 @@ snapshots: unbox-primitive: 1.0.2 which-typed-array: 1.1.15 + es-array-method-boxes-properly@1.0.0: {} + es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 @@ -11547,6 +12238,8 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 + es6-promise@3.3.1: {} + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -11804,6 +12497,10 @@ snapshots: stream-combiner: 0.2.2 through: 2.3.8 + event-target-shim@5.0.1: {} + + eventemitter3@5.0.1: {} + events@3.3.0: {} execa@0.8.0: @@ -11850,8 +12547,14 @@ snapshots: fast-redact@3.5.0: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.0.3: {} + fast-xml-parser@4.5.0: + dependencies: + strnum: 1.0.5 + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -11900,6 +12603,8 @@ snapshots: dependencies: is-callable: 1.2.7 + foreach@2.0.6: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 @@ -12079,6 +12784,15 @@ snapshots: uncrypto: 0.1.3 unenv: 1.10.0 + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + has-bigints@1.0.2: {} has-flag@2.0.0: {} @@ -12099,6 +12813,8 @@ snapshots: dependencies: has-symbols: 1.0.3 + has@1.0.4: {} + hash-base@3.1.0: dependencies: inherits: 2.0.4 @@ -12234,10 +12950,31 @@ snapshots: dependencies: react-is: 16.13.1 + html-element-map@1.3.1: + dependencies: + array.prototype.filter: 1.0.4 + call-bind: 1.0.7 + html-void-elements@3.0.0: {} + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + http-shutdown@1.2.2: {} + http2-client@1.3.5: {} + + https-proxy-agent@7.0.5: + dependencies: + agent-base: 7.1.1 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + human-signals@5.0.0: {} iconv-lite@0.6.3: @@ -12419,6 +13156,8 @@ snapshots: dependencies: has-tostringtag: 1.0.2 + is-subset@0.1.1: {} + is-symbol@1.0.4: dependencies: has-symbols: 1.0.3 @@ -12485,6 +13224,8 @@ snapshots: jiti@2.3.3: {} + js-levenshtein@1.1.6: {} + js-sha3@0.8.0: {} js-tokens@4.0.0: {} @@ -12504,6 +13245,10 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-pointer@0.6.2: + dependencies: + foreach: 2.0.6 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -12619,6 +13364,10 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.escape@4.0.1: {} + + lodash.flattendeep@4.4.0: {} + lodash.foreach@4.5.0: {} lodash.get@4.4.2: {} @@ -12658,12 +13407,18 @@ snapshots: dependencies: react: 18.3.1 + lunr@2.3.9: {} + map-stream@0.0.7: {} + mark.js@8.11.1: {} + markdown-extensions@1.1.1: {} markdown-table@3.0.4: {} + marked@4.3.0: {} + match-sorter@6.3.4: dependencies: '@babel/runtime': 7.25.9 @@ -13194,6 +13949,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 @@ -13213,10 +13972,28 @@ snapshots: pkg-types: 1.2.1 ufo: 1.5.4 + mobx-react-lite@4.0.7(mobx@6.13.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + mobx: 6.13.5 + react: 18.3.1 + use-sync-external-store: 1.2.2(react@18.3.1) + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + mobx-react@9.1.1(mobx@6.13.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + mobx: 6.13.5 + mobx-react-lite: 4.0.7(mobx@6.13.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + mobx@6.13.5: {} modern-ahocorasick@1.0.1: {} + moo@0.5.2: {} + mri@1.2.0: {} ms@2.1.3: {} @@ -13235,6 +14012,13 @@ snapshots: natural-compare@1.4.0: {} + nearley@2.20.1: + dependencies: + commander: 2.20.3 + moo: 0.5.2 + railroad-diagrams: 1.0.0 + randexp: 0.4.6 + neo-async@2.6.2: {} next-mdx-remote@4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -13349,6 +14133,10 @@ snapshots: node-addon-api@7.1.1: {} + node-fetch-h2@2.3.0: + dependencies: + http2-client: 1.3.5 + node-fetch-native@1.6.4: {} node-fetch@2.7.0: @@ -13357,6 +14145,10 @@ snapshots: node-forge@1.3.1: {} + node-readfiles@0.2.0: + dependencies: + es6-promise: 3.3.1 + node-releases@2.0.18: {} non-layered-tidy-tree-layout@2.0.2: {} @@ -13373,12 +14165,52 @@ snapshots: npm-to-yarn@2.2.1: {} + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + oas-kit-common@1.0.8: + dependencies: + fast-safe-stringify: 2.1.1 + + oas-linter@3.2.2: + dependencies: + '@exodus/schemasafe': 1.3.0 + should: 13.2.3 + yaml: 1.10.2 + + oas-resolver@2.5.6: + dependencies: + node-fetch-h2: 2.3.0 + oas-kit-common: 1.0.8 + reftools: 1.1.9 + yaml: 1.10.2 + yargs: 17.7.2 + + oas-schema-walker@1.1.5: {} + + oas-validator@5.0.8: + dependencies: + call-me-maybe: 1.0.2 + oas-kit-common: 1.0.8 + oas-linter: 3.2.2 + oas-resolver: 2.5.6 + oas-schema-walker: 1.1.5 + reftools: 1.1.9 + should: 13.2.3 + yaml: 1.10.2 + object-assign@4.1.1: {} object-hash@3.0.0: {} object-inspect@1.13.2: {} + object-is@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + object-keys@1.1.1: {} object.assign@4.1.5: @@ -13431,6 +14263,14 @@ snapshots: dependencies: mimic-fn: 4.0.0 + openapi-sampler@1.6.0: + dependencies: + '@types/json-schema': 7.0.15 + fast-xml-parser: 4.5.0 + json-pointer: 0.6.2 + + openapi-types@12.1.3: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -13486,10 +14326,21 @@ snapshots: dependencies: parse-path: 7.0.0 + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.2.0 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.2.0 + parse5@7.2.0: dependencies: entities: 4.5.0 + path-browserify@1.0.1: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -13515,6 +14366,10 @@ snapshots: dependencies: through: 2.3.8 + perfect-scrollbar@1.5.6: {} + + performance-now@2.1.0: {} + periscopic@3.1.0: dependencies: '@types/estree': 1.0.6 @@ -13556,6 +14411,12 @@ snapshots: mlly: 1.7.2 pathe: 1.1.2 + pluralize@8.0.0: {} + + polished@4.3.1: + dependencies: + '@babel/runtime': 7.25.9 + possible-typed-array-names@1.0.0: {} postcss-import@15.1.0(postcss@8.4.47): @@ -13595,6 +14456,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.4.38: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.4.47: dependencies: nanoid: 3.3.7 @@ -13603,6 +14470,8 @@ snapshots: prelude-ls@1.2.1: {} + prismjs@1.29.0: {} + process-warning@1.0.0: {} prop-types@15.8.1: @@ -13656,11 +14525,22 @@ snapshots: radix3@1.1.2: {} + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + + railroad-diagrams@1.0.0: {} + rainbow-sprinkles@0.17.3(@vanilla-extract/css@1.16.0(babel-plugin-macros@3.1.0))(@vanilla-extract/dynamic@2.1.2): dependencies: '@vanilla-extract/css': 1.16.0(babel-plugin-macros@3.1.0) '@vanilla-extract/dynamic': 2.1.2 + randexp@0.4.6: + dependencies: + discontinuous-range: 1.0.0 + ret: 0.1.15 + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -13743,6 +14623,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + react-shallow-renderer@16.15.0(react@18.3.1): + dependencies: + object-assign: 4.1.1 + react: 18.3.1 + react-is: 18.3.1 + react-stately@3.33.0(react@18.3.1): dependencies: '@react-stately/calendar': 3.5.5(react@18.3.1) @@ -13780,6 +14666,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + react-tabs@6.0.2(react@18.3.1): + dependencies: + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-textarea-autosize@8.5.4(@types/react@18.3.12)(react@18.3.1): dependencies: '@babel/runtime': 7.25.9 @@ -13826,6 +14718,41 @@ snapshots: dependencies: esprima: 4.0.1 + redoc@2.2.0(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + dependencies: + '@cfaester/enzyme-adapter-react-18': 0.8.0(enzyme@3.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@redocly/openapi-core': 1.25.15 + classnames: 2.5.1 + core-js: 3.39.0 + decko: 1.2.0 + dompurify: 3.1.6 + eventemitter3: 5.0.1 + json-pointer: 0.6.2 + lunr: 2.3.9 + mark.js: 8.11.1 + marked: 4.3.0 + mobx: 6.13.5 + mobx-react: 9.1.1(mobx@6.13.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + openapi-sampler: 1.6.0 + path-browserify: 1.0.1 + perfect-scrollbar: 1.5.6 + polished: 4.3.1 + prismjs: 1.29.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-tabs: 6.0.2(react@18.3.1) + slugify: 1.4.7 + stickyfill: 1.1.1 + styled-components: 6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + swagger2openapi: 7.0.8 + url-template: 2.0.8 + transitivePeerDependencies: + - encoding + - enzyme + - react-native + - supports-color + reflect.getprototypeof@1.0.6: dependencies: call-bind: 1.0.7 @@ -13836,6 +14763,8 @@ snapshots: globalthis: 1.0.4 which-builtin-type: 1.1.4 + reftools@1.1.9: {} + regenerator-runtime@0.14.1: {} regexp.prototype.flags@1.5.3: @@ -13937,6 +14866,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + ret@0.1.15: {} + reusify@1.0.4: {} rimraf@3.0.2: @@ -13950,6 +14881,11 @@ snapshots: robust-predicates@3.0.2: {} + rst-selector-parser@2.2.3: + dependencies: + lodash.flattendeep: 4.4.0 + nearley: 2.20.1 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -14051,6 +14987,8 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 + shallowequal@1.1.0: {} + shebang-command@1.2.0: dependencies: shebang-regex: 1.0.0 @@ -14070,6 +15008,32 @@ snapshots: vscode-oniguruma: 1.7.0 vscode-textmate: 8.0.0 + should-equal@2.0.0: + dependencies: + should-type: 1.4.0 + + should-format@3.0.3: + dependencies: + should-type: 1.4.0 + should-type-adaptors: 1.1.0 + + should-type-adaptors@1.1.0: + dependencies: + should-type: 1.4.0 + should-util: 1.0.1 + + should-type@1.4.0: {} + + should-util@1.0.1: {} + + should@13.2.3: + dependencies: + should-equal: 2.0.0 + should-format: 3.0.3 + should-type: 1.4.0 + should-type-adaptors: 1.1.0 + should-util: 1.0.1 + side-channel@1.0.6: dependencies: call-bind: 1.0.7 @@ -14085,10 +15049,24 @@ snapshots: dependencies: is-arrayish: 0.3.2 + simple-websocket@9.1.0: + dependencies: + debug: 4.3.7 + queue-microtask: 1.2.3 + randombytes: 2.1.0 + readable-stream: 3.6.2 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + slash@3.0.0: {} slash@4.0.0: {} + slugify@1.4.7: {} + sonic-boom@2.8.0: dependencies: atomic-sleep: 1.0.0 @@ -14142,6 +15120,8 @@ snapshots: std-env@3.7.0: {} + stickyfill@1.1.1: {} + stream-combiner@0.2.2: dependencies: duplexer: 0.1.2 @@ -14237,10 +15217,26 @@ snapshots: strip-json-comments@3.1.1: {} + strnum@1.0.5: {} + style-to-object@0.4.4: dependencies: inline-style-parser: 0.1.1 + styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@emotion/is-prop-valid': 1.2.2 + '@emotion/unitless': 0.8.1 + '@types/stylis': 4.2.5 + css-to-react-native: 3.2.0 + csstype: 3.1.3 + postcss: 8.4.38 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + shallowequal: 1.1.0 + stylis: 4.3.2 + tslib: 2.6.2 + styled-jsx@5.1.1(babel-plugin-macros@3.1.0)(react@18.3.1): dependencies: client-only: 0.0.1 @@ -14250,6 +15246,8 @@ snapshots: stylis@4.2.0: {} + stylis@4.3.2: {} + stylis@4.3.4: {} sucrase@3.35.0: @@ -14280,6 +15278,22 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swagger2openapi@7.0.8: + dependencies: + call-me-maybe: 1.0.2 + node-fetch: 2.7.0 + node-fetch-h2: 2.3.0 + node-readfiles: 0.2.0 + oas-kit-common: 1.0.8 + oas-resolver: 2.5.6 + oas-schema-walker: 1.1.5 + oas-validator: 5.0.8 + reftools: 1.1.9 + yaml: 1.10.2 + yargs: 17.7.2 + transitivePeerDependencies: + - encoding + symbol-observable@2.0.3: {} system-architecture@0.1.0: {} @@ -14409,6 +15423,8 @@ snapshots: tslib@1.14.1: {} + tslib@2.6.2: {} + tslib@2.8.0: {} type-check@0.4.0: @@ -14457,6 +15473,9 @@ snapshots: ufo@1.5.4: {} + uglify-js@3.19.3: + optional: true + uint8arrays@3.1.0: dependencies: multiformats: 9.9.0 @@ -14470,6 +15489,8 @@ snapshots: uncrypto@0.1.3: {} + undici@6.21.0: {} + unenv@1.10.0: dependencies: consola: 3.2.3 @@ -14605,6 +15626,8 @@ snapshots: uqr@0.1.2: {} + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -14616,6 +15639,8 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + url-template@2.0.8: {} + use-callback-ref@1.3.2(@types/react@18.3.12)(react@18.3.1): dependencies: react: 18.3.1 @@ -14750,8 +15775,14 @@ snapshots: - esbuild - uglify-js + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-fetch@3.6.20: {} + whatwg-mimetype@4.0.0: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -14809,6 +15840,8 @@ snapshots: word-wrap@1.2.5: {} + wordwrap@1.0.0: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -14834,12 +15867,26 @@ snapshots: yallist@2.1.2: {} + yaml-ast-parser@0.0.43: {} + yaml@1.10.2: {} yaml@2.6.0: {} + yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} + yargs@17.0.1: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + yargs@17.7.2: dependencies: cliui: 8.0.1 diff --git a/documentation/docs/public/favicon.ico b/documentation/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c0e5719e27e1a6238751eda44e5fa66c62053d6c GIT binary patch literal 1793 zcmbu8Ur5tY6vwYtFN3z9?T>RyU9&Kp|EU8>Zl&l z#2U6J|Bzv2kuO0KWDf?3G&B&5Tt4&={c*L5w4HwEH-7VHZ54Fk{_ef!en01)bI<(| z5<(V{B})iBDo86KB#G`ko^Sm;gpe2XnxcH`eTxZs9ZEwE`eL>P^T?wCpN1|hn=E>&Xo zU2=v9Zyw!~GP!?JZZ=-+u>eHXB&2)&)AVrSF*Pgg_t+ev&+nwdsxjhFiP8>ab z@A!r5U!n*B`h5jySWx=aF{S)-K^HnOANrnPta5jx6+E0wHgwNw>5g->;od^^{3@ps zc!vES9GZ+KuSgv2PYf>zJKEIgjb7BCcAg{z*Vz>>pU4#{zi}+jj;KK`*9ZFi7nbV- z8H@pQ!noKyEcC{V42WF5oQXxFQ8R0_;U25QJlVVA=8HOPg#`sbQG9T@T;TC|7#r@f zJTmM(vo;*LykXl`hQo)4jmx7J>!LMw&j#Z=JFkP=?S}HQvVc6Y>3KZ?d1Pj0W}vI9 zi^(O>`N*1f4^9R;zqj`WbEeT?2+EuG3{F;``P30EoqsQE{vOX~n4X?yej>cfU(ZLT z`i!UJSPLX%=QaOUKlHPPt{ujPwZt{Iep~XIyvX7kIMwEMtd2|d*M^%!6gWvU z$YQhGfp@xf>Pf8X@9#HOl?tdu#z*_U_WQtV>vlT|EzMq;eEvSz^JTJVD6-hRs6j24 j<<5HsAmQrWuN~HNk0uY--I&FWbF}q5@a4, }, toc: { - float: true, // TODO would be nice to set this to false so the TOC is in the left sidebar but this doesn't seem to work with pages that are also the top of directories: fix + float: false, // TODO would be nice to set this to false so the TOC is in the left sidebar but this doesn't seem to work with pages that are also the top of directories: fix // if we do this then we also have to uncomment the editLink and feedback objects below + component: null, // try remove right-hand column + }, + editLink: { + component: null, // remove element + }, + feedback: { + content: null, // remove element }, - // editLink: { - // component: null, - // }, - // feedback: { - // content: null, - // }, - // gitTimestamp: TODO }; diff --git a/documentation/scripts/next-scripts/autodoc.sh b/documentation/scripts/next-scripts/autodoc.sh index b04fff82cf..bbc2bfda93 100755 --- a/documentation/scripts/next-scripts/autodoc.sh +++ b/documentation/scripts/next-scripts/autodoc.sh @@ -6,9 +6,9 @@ set -o errexit set -o nounset set -o pipefail -# make sure we have all the binaries built -cd ../../ && cargo build --release && cd tools/nym-cli && cargo build --release && cd ../../ && - +# make sure we have all the binaries built from master +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) && git checkout master && +cd ../../ && cargo build --release && cd tools/nym-cli && cargo build --release && cd ../../ && git checkout $CURRENT_BRANCH && # run autodoc script cd documentation/autodoc/ && cargo run --release && From d9e5c62b5cd347d2be9cb0d49d2110d3b392eecd Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:00:36 +0000 Subject: [PATCH 41/80] Update changelog.mdx (#5248) --- documentation/docs/pages/operators/changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 6c156b5f22..a649d8de4f 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -43,7 +43,7 @@ This page displays a full list of all the changes during our release cycle from - [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.14-crunch) - [`nym-node`](nodes/nym-node.mdx) version `1.2.0` -- [Releae operators updates and tools](changelog#operators-updates--tools) +- [Operators updates and tools](changelog#operators-updates--tools) - [Github CHANGELOG.md](https://github.com/nymtech/nym/blob/nym-binaries-v2024.14-crunch/CHANGELOG.md) ```sh From 9b5e14c78ec77b8ea43e93d99503cb7b06647cf2 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 12 Dec 2024 09:52:04 +0000 Subject: [PATCH 42/80] tweak fix (#5250) * tweak fix * added default config directories --- documentation/docs/pages/developers/nymvpncli.mdx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/documentation/docs/pages/developers/nymvpncli.mdx b/documentation/docs/pages/developers/nymvpncli.mdx index d8687cc5b9..140f21549c 100644 --- a/documentation/docs/pages/developers/nymvpncli.mdx +++ b/documentation/docs/pages/developers/nymvpncli.mdx @@ -81,7 +81,7 @@ Ignore the `NoAccountStored` errors: these will disappear after the next step. * ## Run VPN We have to first store the account we have created online: ```sh -./PATH/TO/nym-vpn-cli store-account --mnemonic "" +./PATH/TO/nym-vpnc store-account --mnemonic "" ``` You will see this registration in the daemon logs: @@ -108,7 +108,7 @@ You will see this registration in the daemon logs: You can then connect `nym-vpnc` (in this case, with 2 hop wireguard mode enabled): ```sh -./PATH/TO/nym-vpn-cli nym-vpnc connect --enable-two-hop +./PATH/TO/nym-vpnc connect --enable-two-hop ``` Which shows as such in the daemon logs: @@ -168,7 +168,7 @@ You should see the `Remaining wireguard bandwidth` decrease as you use your allo There are a lot of configuration options available to you regarding how to connect: ```sh -❯ ./nym-vpn-core/target/debug/nym-vpnc connect --help +❯ ./PATH/TO/nym-vpnc connect --help Connect to the Nym network Usage: nym-vpnc connect [OPTIONS] @@ -217,7 +217,7 @@ Options: ## Command Reference ```sh -❯ ./nym-vpn-core/target/debug/nym-vpnc --help +❯ ./PATH/TO/nym-vpnc --help NymVPN commandline client Usage: nym-vpnc [OPTIONS] @@ -257,7 +257,7 @@ Options: ``` ```sh -❯ ./nym-vpn-core/target/debug/nym-vpnd --help +❯ ./PATH/TO/nym-vpnd --help NymVPN daemon Usage: nym-vpnd [OPTIONS] @@ -276,3 +276,6 @@ Options: -V, --version Print version ``` + +## Default Config Directories +Configurations are stored in `/etc/nym`. State stored between runs (keys, mnemonic, etc) are stored in `/var/lib/nym-vpnd`. From 2ab172146a4f15fa920deaea6247a36e979c4772 Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:24:38 +0000 Subject: [PATCH 43/80] [DOCs/operators]: Edit tokenomics definitions (#5252 ) --- .../docs/pages/operators/tokenomics/mixnet-rewards.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx index 569cd12e08..a5fa6a20a4 100644 --- a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx +++ b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx @@ -206,8 +206,7 @@ For example `nym-node` on version `1.2.0` is on 1st major version, 2nd minor and ![](/images/operators/tokenomics/reward_version_graph.png) -As you can see on above, the algorithm is designed to give maximum probability (`1`) to the latest version and exponentialy dicrease the probability to non-upgraded nodes where the more important version the node is behind, the faster the cliff. This eliminates any older nodes despite their saturation and performance to take place in the Rewarded set and gives a priority to the operators running up-to-date nodes, ensuring as strong network as possible. - +As you can see above, the algorithm is designed to give maximum selection score (`1`) to the latest version, while non-upgraded nodes receive a lower score. The score decreases faster when the node has failed to make a major version upgrade, and slower when the node is behind only with minor updates. This scoring de-prioritizes the selection of outdated nodes, even if their saturation and performance are high. Nodes are selected probabilistically in each epoch (60 min), according to their scores, to be part of the Rewarded set. This scoring mechanism gives priority to the operators running up-to-date nodes, ensuring that the network is as updated as possible. #### Performance Calculation @@ -218,7 +217,7 @@ Good performance is much more essential than [stake saturation](../tokenomics.md For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1):
    - + > node_1 = 1.00 ^ 20 \* 1.0 = 1
    > node_2 = 1.00 ^ 20 \* 0.5 = 0.5
    > node_3 = 0.99 ^ 20 \* 1.0 = 0.818
    @@ -226,8 +225,9 @@ For a comparison we made an example with 5 nodes, where first number is node per > node_5 = 0.90 ^ 20 \* 1.0 = 0.122
    -As you can see the performance (also known as *Routing score*) is much more important during the active set selection. A node with 100% performance but only 50% stake saturation has much bigger chance to be chosen than a node with 95% performance but full stake saturation and incomparably bigger chance than 90% performing node with 100% stake saturation. +As you can see the performance (also known as *Routing score*) is much more important during the Rewarded set selection. A node with 100% performance but only 50% stake saturation has much bigger chance to be chosen than a node with 95% performance but full stake saturation and incomparably bigger chance than 90% performing node with 100% stake saturation. +The nodes are chosen probababilistically in each epoch (60 min), so even nodes with lower performance will eventually be chosen, just much less often, as their chances decrease. ### Layer Distribution From c31d1f63e653470016395d646eb79d061bca02ab Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 12 Dec 2024 13:11:25 +0000 Subject: [PATCH 44/80] updated readme links + bit of general clean (#5253) --- README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8ebd3b590f..a29b215f0b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr * `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications. * `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer. * `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework. +* `nym-cli` - a tool for interacting with the network from the CLI. @@ -35,24 +36,20 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───► ### Building * Platform build instructions are available on Nym [Operators Guide documentation](https://nymtech.net/operators/binaries/building-nym.html). -* Wallet build instructions are available on Nym [Technical docs](https://nymtech.net/docs/wallet/desktop-wallet.html). +* Wallet build instructions are available [here](https://github.com/nymtech/nym/tree/master/nym-wallet#installation-prerequisites---linux--mac). ### Developing -There's a [`sandbox.env`](https://github.com/nymtech/nym/envs/sandbox.env) file provided which you can rename to `.env` if you want convenient testing environment. Read more about sandbox environment in our [Operators Guide page](https://nymtech.net/operators/sandbox.html). - References for developers: -* [Developers Portal](https://nymtech.net/developers) -* [Typescript SDKs](https://sdk.nymtech.net/) -* [Technical Documentation - Nym network overview](https://nymtech.net/docs/) -* [Release Cycle - git flow](https://nymtech.net/operators/release-cycle.html) +* [Dev Docs](https://nymtech.net/docs/developers) +* [SDKs](https://nymtech.net/docs/developers/rust) +* [Network Docs](https://nymtech.net/docs/network) +* [Release Cycle - git flow](https://nymtech.net/docs/operators/release-cycle) ### Developer chat -You can chat to us in two places: -* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat) -* The various developer channels on [Discord](https://nymtech.net/go/discord) +You can chat to us in the #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat) or on the [Nym Forum](https://forum.nymtech.net). ### Tokenomics & Rewards From ccd66f8a513aa83684d9625f0233f7fcf9f75f1d Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:34:03 +0000 Subject: [PATCH 45/80] tokenomics edits (#5254) --- .../operators/tokenomics/mixnet-rewards.mdx | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx index a5fa6a20a4..fb1225af15 100644 --- a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx +++ b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx @@ -133,29 +133,33 @@ This is a quick summary, to understand the full picture, please see detailed [*R ### Rewarded Set Selection -For a node to be rewarded, the node must be part of a [Rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params) (which currently = active set) in the first place. The active set is selected in the beginning of each epoch (every 60min) where total of 240 Nym nodes - represented by 120 mixnodes and 120 gateways, are randomly allocated across the layers. +For a node to be rewarded, the node must be part of a [Rewarded set](https://validator.nymtech.net/api/v1/epoch/reward_params) (which currently = active set) in the first place. The Rewarded set is freshly selected at the start of each epoch (every 60 min), and it consists of 240 Nym nodes that are probabilistically chosen from all the available nodes. These 240 nodes include 120 gateways and 120 mixnodes (40 for each of 3 mixnet layers). -The algorithm choosing nodes into the active set takes into account these parameters: +Rewarded set nodes are randomly selected, and their selection chances increase with a node score that includes three parameters: -1. [Config score](#config-score-calculation) -2. [Performance](#performance-calculation) -3. [Stake saturation](../tokenomics.mdx#stake-saturation) +1. [Config score](#config-score-calculation): highest (`1`) when the node is running the latest version of the software with [T&C's accepted](../nodes/nym-node/setup.mdx#terms--conditions) +2. [Performance](#performance-calculation): highest (`1`) when the node is consistently online and correctly processes all the received traffic +3. [Stake saturation](../tokenomics.mdx#stake-saturation): including bond and delegated stake -Besides these values, the API is also looking whther the node is bonded in Mixnet smart contract as a Nym Node or legacy node (Mixnode or Gateway). **Only nodes bonded as Nym Node in Mixnet smart contract can be selected to the Rewrded set, if you haven't migrated your node yet, please [follow these steps](../nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract)!** +Besides these values, the API is also looking whether the node is bonded in Mixnet smart contract as a Nym Node or legacy node (Mixnode or Gateway). **Only nodes bonded as Nym Node in Mixnet smart contract can be selected to the Rewrded set, if you haven't migrated your node yet, please [follow these steps](../nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract)!** -**The Rewarded set selection probablity formula:** +**The node score is calculated with this formula:** -> **active_set_selection_probability = config_score \* ( node_performance ^ 20 ) \* stake_saturation** +> **active_set_selection_probability = total_stake \* ( config_score \* node_performance ) ^ 20 )** +Note that the score helps prioritize some nodes over others. If all available nodes have the same score, then the selection is done uniformly at random. By raising the config and performance components to 20, values of these parameters that are below one incur a heavy penalization for the node’s selection chances. + +Besides these values, the API is also checks whether the node is bonded in Mixnet smart contract as a Nym Node or legacy node (Mixnode or Gateway). **Only nodes bonded as Nym Node in Mixnet smart contract can be selected to the Rewrded set. Thus, if you haven't migrated your node yet, please [follow these steps](../nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract)!** + #### Config Score Calculation The nodes selection to the active set has a new parameter - `config_score`. Config score currently looks into three paramteres: -1. If the node binary is `nym-node` (not legacy `nym-mixnode` or `nym-gateway`) -2. If [Terms & Conditions](../nodes/nym-node/setup.mdx#terms--conditions) are accepted. -3. Version of `nym-node` binary +1. If the node binary is `nym-node` (not legacy `nym-mixnode` or `nym-gateway`): `1` if `True`, `0` if `False` +2. If [Terms & Conditions](../nodes/nym-node/setup.mdx#terms--conditions) are accepted: `1` if `True`, `0` if `False` +3. Version of `nym-node` binary: decreasing weight for outdated versions, as explained below **The `config_score` parameter calculation formula:** @@ -174,10 +178,9 @@ First two points have binary values of either 0 or 1, with a following logic: Only if both conditions above are `True` the node can have any chance to be selected, as otherwise the probability will always be 0. -**The `version_behind` parameter in `config_score` calculation** +**The `versions_behind` parameter in `config_score` calculation** -From release `2024.14-crunch` (`nym-node v1.2.0`), the `config_score` parameter takes into account also nodes version. Current version is the -one marked as `Latest` in our repository. From that one we count the parameter `version_behind`, where every version back the number of `versions_behind` increases by 1 in this formula: +From release `2024.14-crunch` (`nym-node v1.2.0`), the `config_score` parameter takes into account also nodes version. The "current version" is the one marked as `Latest` in our repository. The parameter `versions_behind` indicates the number of versions between the `Latest` version and the version run by the node, and it is factored into the config score with the formula: > **0.995 ^ ( ( X * versions_behind ) ^ 1.65 )** @@ -192,7 +195,9 @@ one marked as `Latest` in our repository. From that one we count the parameter ` Our versioning convention is: `major_version . minor_version . patch` -For example `nym-node` on version `1.2.0` is on 1st major version, 2nd minor and 0 patches. See the the table and graph below: +For example `nym-node` on version `1.2.0` is on 1st major version, 2nd minor and 0 patches. + +Note that the `X` multiplier heavily lowers the `config_score` when nodes are outdated with respect to more significant updates. See the the table and graph below: | **Version behind** | **Patches (X = 1)** | **Minor versions (X = 10)** | **Major versions (X = 100)** | | :-- | --: | --: | --: | @@ -212,12 +217,12 @@ As you can see above, the algorithm is designed to give maximum selection score Performance is measured by Nym Network Monitor which sends thousands of packages through different routes every 15 minutes and measures how many were dropped on the way. Test result represents percentage of packets succesfully returned (can be anything between 0 and 1). Performance value is nodes average of these tests in last 24h. -Good performance is much more essential than [stake saturation](../tokenomics.mdx#stake-saturation), because it's lifted to 20th power in the selection formula. +Good performance is much more essential than [total stake](../tokenomics.mdx#stake-saturation), because it's lifted to 20th power in the selection formula. -For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1): +For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1 for simplification):
    - + > node_1 = 1.00 ^ 20 \* 1.0 = 1
    > node_2 = 1.00 ^ 20 \* 0.5 = 0.5
    > node_3 = 0.99 ^ 20 \* 1.0 = 0.818
    From ee7b3f1415447e0ce640e5c7bb0b89e2173b07d4 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 12 Dec 2024 15:21:57 +0100 Subject: [PATCH 46/80] Update TS bindings (#5255) --- .../types/src/types/rust/ActiveSetUpdate.ts | 18 +++++++ .../src/types/rust/GatewayConfigUpdate.ts | 3 ++ .../types/src/types/rust/Interval.ts | 30 +++++++++++ .../src/types/rust/IntervalRewardParams.ts | 51 +++++++++++++++++++ .../rust/IntervalRewardingParamsUpdate.ts | 35 +++++++++++++ .../src/types/rust/MixNodeConfigUpdate.ts | 3 ++ .../types/src/types/rust/Mixnode.ts | 34 +++++++++++++ .../types/src/types/rust/NodeConfigUpdate.ts | 3 ++ .../src/types/rust/NodeRewardingParameters.ts | 15 ++++++ .../types/src/types/rust/NymNode.ts | 20 ++++++++ .../src/types/rust/PendingMixnodeChanges.ts | 3 ++ .../src/types/rust/PendingNodeChanges.ts | 3 ++ .../types/src/types/rust/RewardEstimate.ts | 20 ++++++++ .../types/src/types/rust/RewardedSetParams.ts | 19 +++++++ .../types/src/types/rust/RewardingParams.ts | 12 +++++ .../ts-packages/types/src/types/rust/Role.ts | 3 ++ .../types/src/types/rust/UnbondedMixnode.ts | 23 +++++++++ .../types/src/types/rust/Period.ts | 6 +++ .../types/src/types/rust/Account.ts | 4 ++ .../types/src/types/rust/AccountEntry.ts | 3 ++ .../src/types/rust/AccountWithMnemonic.ts | 4 ++ .../types/src/types/rust/ActiveSetUpdate.ts | 18 +++++++ .../types/src/types/rust/Balance.ts | 4 ++ .../ts-packages/types/src/types/rust/Coin.ts | 3 ++ .../types/src/types/rust/CosmosFee.ts | 4 ++ .../types/src/types/rust/CurrencyDenom.ts | 3 ++ .../types/src/types/rust/DecCoin.ts | 4 ++ .../types/src/types/rust/Delegation.ts | 4 ++ .../types/src/types/rust/DelegationEvent.ts | 5 ++ .../src/types/rust/DelegationEventKind.ts | 3 ++ .../types/src/types/rust/DelegationResult.ts | 4 ++ .../types/rust/DelegationSummaryResponse.ts | 5 ++ .../types/rust/DelegationWithEverything.ts | 6 +++ .../ts-packages/types/src/types/rust/Fee.ts | 4 ++ .../types/src/types/rust/FeeDetails.ts | 5 ++ .../ts-packages/types/src/types/rust/Gas.ts | 7 +++ .../types/src/types/rust/GasInfo.ts | 12 +++++ .../types/src/types/rust/Gateway.ts | 7 +++ .../types/src/types/rust/GatewayBond.ts | 5 ++ .../rust/IntervalRewardingParamsUpdate.ts | 35 +++++++++++++ .../types/src/types/rust/MixNodeBond.ts | 5 ++ .../types/src/types/rust/MixNodeCostParams.ts | 4 ++ .../types/src/types/rust/MixNodeDetails.ts | 5 ++ .../types/src/types/rust/Mixnode.ts | 34 +++++++++++++ .../types/src/types/rust/NodeRewarding.ts | 4 ++ .../types/src/types/rust/NymNodeBond.ts | 39 ++++++++++++++ .../types/src/types/rust/NymNodeDetails.ts | 21 ++++++++ .../src/types/rust/OriginalVestingResponse.ts | 4 ++ .../types/src/types/rust/PendingEpochEvent.ts | 4 ++ .../src/types/rust/PendingEpochEventData.ts | 5 ++ .../src/types/rust/PendingIntervalEvent.ts | 4 ++ .../types/rust/PendingIntervalEventData.ts | 5 ++ .../src/types/rust/PendingNodeChanges.ts | 3 ++ .../types/src/types/rust/PledgeData.ts | 4 ++ .../types/src/types/rust/RewardedSetParams.ts | 19 +++++++ .../src/types/rust/RpcTransactionResponse.ts | 5 ++ .../types/src/types/rust/SendTxResult.ts | 6 +++ .../src/types/rust/TransactionDetails.ts | 4 ++ .../types/rust/TransactionExecuteResult.ts | 5 ++ .../src/types/rust/VestingAccountInfo.ts | 5 ++ .../types/src/types/rust/VestingPeriod.ts | 3 ++ .../src/types/rust/WrappedDelegationEvent.ts | 4 ++ .../src/types/rust/AnnotationResponse.ts | 4 ++ .../types/src/types/rust/ConfigScore.ts | 7 +++ .../types/src/types/rust/DeclaredRoles.ts | 3 ++ .../types/src/types/rust/DescribedNodeType.ts | 3 ++ .../src/types/rust/DetailedNodePerformance.ts | 9 ++++ .../types/src/types/rust/DisplayRole.ts | 3 ++ .../types/rust/GatewayCoreStatusResponse.ts | 3 ++ .../rust/HistoricalPerformanceResponse.ts | 3 ++ .../types/rust/HistoricalUptimeResponse.ts | 3 ++ .../rust/InclusionProbabilityResponse.ts | 4 ++ .../types/src/types/rust/Interval.ts | 30 +++++++++++ .../src/types/rust/IntervalRewardParams.ts | 51 +++++++++++++++++++ .../types/rust/MixnodeCoreStatusResponse.ts | 3 ++ .../types/src/types/rust/MixnodeStatus.ts | 3 ++ .../src/types/rust/MixnodeStatusResponse.ts | 4 ++ .../types/src/types/rust/NodeAnnotation.ts | 5 ++ .../types/rust/NodeDatePerformanceResponse.ts | 3 ++ .../src/types/rust/NodePerformanceResponse.ts | 3 ++ .../types/src/types/rust/PaginatedResponse.ts | 4 ++ .../types/src/types/rust/Pagination.ts | 3 ++ .../types/rust/PerformanceHistoryResponse.ts | 5 ++ .../types/src/types/rust/RewardEstimate.ts | 20 ++++++++ .../types/rust/RewardEstimationResponse.ts | 6 +++ .../types/src/types/rust/RewardedSetParams.ts | 19 +++++++ .../types/src/types/rust/RewardingParams.ts | 12 +++++ .../types/src/types/rust/RoutingScore.ts | 7 +++ .../types/src/types/rust/SelectionChance.ts | 3 ++ .../src/types/rust/StakeSaturationResponse.ts | 3 ++ .../src/types/rust/UptimeHistoryResponse.ts | 5 ++ 91 files changed, 873 insertions(+) create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/GatewayConfigUpdate.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Interval.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/MixNodeConfigUpdate.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Mixnode.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeConfigUpdate.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeRewardingParameters.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NymNode.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingMixnodeChanges.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardingParams.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Role.ts create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/UnbondedMixnode.ts create mode 100644 common/cosmwasm-smart-contracts/vesting-contract/bindings/ts-packages/types/src/types/rust/Period.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Account.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/AccountEntry.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/AccountWithMnemonic.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Balance.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Coin.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/CosmosFee.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/CurrencyDenom.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/DecCoin.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Delegation.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/DelegationEvent.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/DelegationEventKind.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/DelegationResult.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/DelegationWithEverything.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Fee.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/FeeDetails.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Gas.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/GasInfo.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Gateway.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/GatewayBond.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/MixNodeBond.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/MixNodeCostParams.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/MixNodeDetails.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/Mixnode.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/NodeRewarding.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/NymNodeBond.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/NymNodeDetails.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/OriginalVestingResponse.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEvent.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEventData.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEvent.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEventData.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/PledgeData.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/RpcTransactionResponse.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/SendTxResult.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/TransactionDetails.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/TransactionExecuteResult.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/VestingAccountInfo.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/VestingPeriod.ts create mode 100644 common/types/bindings/ts-packages/types/src/types/rust/WrappedDelegationEvent.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/AnnotationResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/ConfigScore.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DeclaredRoles.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DescribedNodeType.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DetailedNodePerformance.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DisplayRole.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalPerformanceResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalUptimeResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Interval.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatus.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeAnnotation.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeDatePerformanceResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodePerformanceResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PaginatedResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Pagination.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PerformanceHistoryResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimationResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardingParams.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RoutingScore.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/SelectionChance.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/StakeSaturationResponse.ts create mode 100644 nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/UptimeHistoryResponse.ts diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts new file mode 100644 index 0000000000..20e5640581 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts @@ -0,0 +1,18 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Specification on how the active set should be updated. + */ +export type ActiveSetUpdate = { +/** + * The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`]) + */ +entry_gateways: number, +/** + * The expected number of nodes assigned exit gateway role (i.e. [`Role::ExitGateway`]) + */ +exit_gateways: number, +/** + * The expected number of nodes assigned the 'mixnode' role, i.e. total of [`Role::Layer1`], [`Role::Layer2`] and [`Role::Layer3`]. + */ +mixnodes: number, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/GatewayConfigUpdate.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/GatewayConfigUpdate.ts new file mode 100644 index 0000000000..44738d7f32 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/GatewayConfigUpdate.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GatewayConfigUpdate = { host: string, mix_port: number, clients_port: number, location: string, version: string, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Interval.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Interval.ts new file mode 100644 index 0000000000..5efd2d2dc5 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Interval.ts @@ -0,0 +1,30 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Specification of a rewarding interval. + */ +export type Interval = { +/** + * Monotonously increasing id of this interval. + */ +id: number, +/** + * Number of epochs in this interval. + */ +epochs_in_interval: number, +/** + * The timestamp indicating the start of the current rewarding epoch. + */ +current_epoch_start: string, +/** + * Monotonously increasing id of the current epoch in this interval. + */ +current_epoch_id: number, +/** + * The duration of all epochs in this interval. + */ +epoch_length: { secs: number; nanos: number; }, +/** + * The total amount of elapsed epochs since the first epoch of the first interval. + */ +total_elapsed_epochs: number, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts new file mode 100644 index 0000000000..a582bf138b --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts @@ -0,0 +1,51 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Parameters required by the mix-mining reward distribution that do not change during an interval. + */ +export type IntervalRewardParams = { +/** + * Current value of the rewarding pool. + * It is expected to be constant throughout the interval. + */ +reward_pool: string, +/** + * Current value of the staking supply. + * It is expected to be constant throughout the interval. + */ +staking_supply: string, +/** + * Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set. + * Also known as `beta`. + */ +staking_supply_scale_factor: string, +/** + * Current value of the computed reward budget per epoch, per node. + * It is expected to be constant throughout the interval. + */ +epoch_reward_budget: string, +/** + * Current value of the stake saturation point. + * It is expected to be constant throughout the interval. + */ +stake_saturation_point: string, +/** + * Current value of the sybil resistance percent (`alpha`). + * It is not really expected to be changing very often. + * As a matter of fact, unless there's a very specific reason, it should remain constant. + */ +sybil_resistance: string, +/** + * Current active set work factor. + * It is not really expected to be changing very often. + * As a matter of fact, unless there's a very specific reason, it should remain constant. + */ +active_set_work_factor: string, +/** + * Current maximum interval pool emission. + * Assuming all nodes in the rewarded set are fully saturated and have 100% performance, + * this % of the reward pool would get distributed in rewards to all operators and its delegators. + * It is not really expected to be changing very often. + * As a matter of fact, unless there's a very specific reason, it should remain constant. + */ +interval_pool_emission: string, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts new file mode 100644 index 0000000000..7ed7f7907a --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts @@ -0,0 +1,35 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RewardedSetParams } from "./RewardedSetParams"; + +/** + * Specification on how the rewarding params should be updated. + */ +export type IntervalRewardingParamsUpdate = { +/** + * Defines the new value of the reward pool. + */ +reward_pool: string | null, +/** + * Defines the new value of the staking supply. + */ +staking_supply: string | null, +/** + * Defines the new value of the staking supply scale factor. + */ +staking_supply_scale_factor: string | null, +/** + * Defines the new value of the sybil resistance percent. + */ +sybil_resistance_percent: string | null, +/** + * Defines the new value of the active set work factor. + */ +active_set_work_factor: string | null, +/** + * Defines the new value of the interval pool emission rate. + */ +interval_pool_emission: string | null, +/** + * Defines the parameters of the rewarded set. + */ +rewarded_set_params: RewardedSetParams | null, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/MixNodeConfigUpdate.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/MixNodeConfigUpdate.ts new file mode 100644 index 0000000000..96bd132f52 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/MixNodeConfigUpdate.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type MixNodeConfigUpdate = { host: string, mix_port: number, verloc_port: number, http_api_port: number, version: string, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Mixnode.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Mixnode.ts new file mode 100644 index 0000000000..ada169d2b4 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Mixnode.ts @@ -0,0 +1,34 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Information provided by the node operator during bonding that are used to allow other entities to use the services of this node. + */ +export type MixNode = { +/** + * Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com + */ +host: string, +/** + * Port used by this mixnode for listening for mix packets. + */ +mix_port: number, +/** + * Port used by this mixnode for listening for verloc requests. + */ +verloc_port: number, +/** + * Port used by this mixnode for its http(s) API + */ +http_api_port: number, +/** + * Base58-encoded x25519 public key used for sphinx key derivation. + */ +sphinx_key: string, +/** + * Base58-encoded ed25519 EdDSA public key. + */ +identity_key: string, +/** + * The self-reported semver version of this mixnode. + */ +version: string, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeConfigUpdate.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeConfigUpdate.ts new file mode 100644 index 0000000000..b39d3997e4 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeConfigUpdate.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NodeConfigUpdate = { host: string | null, custom_http_port: number | null, restore_default_http_port: boolean, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeRewardingParameters.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeRewardingParameters.ts new file mode 100644 index 0000000000..8354619ff9 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NodeRewardingParameters.ts @@ -0,0 +1,15 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Parameters used for rewarding particular node. + */ +export type NodeRewardingParameters = { +/** + * Performance of the particular node in the current epoch. + */ +performance: string, +/** + * Amount of work performed by this node in the current epoch + * also known as 'omega' in the paper + */ +work_factor: string, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NymNode.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NymNode.ts new file mode 100644 index 0000000000..a113827957 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/NymNode.ts @@ -0,0 +1,20 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Information provided by the node operator during bonding that are used to allow other entities to use the services of this node. + */ +export type NymNode = { +/** + * Network address of this nym-node, for example 1.1.1.1 or foo.mixnode.com + * that is used to discover other capabilities of this node. + */ +host: string, +/** + * Allow specifying custom port for accessing the http, and thus self-described, api + * of this node for the capabilities discovery. + */ +custom_http_port: number | null, +/** + * Base58-encoded ed25519 EdDSA public key. + */ +identity_key: string, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingMixnodeChanges.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingMixnodeChanges.ts new file mode 100644 index 0000000000..cddbfdba80 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingMixnodeChanges.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PendingMixNodeChanges = { pledge_change: number | null, cost_params_change: number | null, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts new file mode 100644 index 0000000000..b1667a190e --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PendingNodeChanges = { pledge_change: number | null, cost_params_change: number | null, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts new file mode 100644 index 0000000000..9eea44b06e --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts @@ -0,0 +1,20 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RewardEstimate = { +/** + * The amount of **decimal** coins that are going to get distributed to the node, + * i.e. the operator and all its delegators. + */ +total_node_reward: string, +/** + * The share of the reward that is going to get distributed to the node operator. + */ +operator: string, +/** + * The share of the reward that is going to get distributed among the node delegators. + */ +delegates: string, +/** + * The operating cost of this node. Note: it's already included in the operator reward. + */ +operating_cost: string, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts new file mode 100644 index 0000000000..e1d09ac0e3 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts @@ -0,0 +1,19 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RewardedSetParams = { +/** + * The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`]) + */ +entry_gateways: number, +/** + * The expected number of nodes assigned exit gateway role (i.e. [`Role::ExitGateway`]) + */ +exit_gateways: number, +/** + * The expected number of nodes assigned the 'mixnode' role, i.e. total of [`Role::Layer1`], [`Role::Layer2`] and [`Role::Layer3`]. + */ +mixnodes: number, +/** + * Number of nodes in the 'standby' set. (i.e. [`Role::Standby`]) + */ +standby: number, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardingParams.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardingParams.ts new file mode 100644 index 0000000000..5c8263bb6d --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/RewardingParams.ts @@ -0,0 +1,12 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { IntervalRewardParams } from "./IntervalRewardParams"; +import type { RewardedSetParams } from "./RewardedSetParams"; + +/** + * Parameters used for reward calculation. + */ +export type RewardingParams = { +/** + * Parameters that should remain unchanged throughout an interval. + */ +interval: IntervalRewardParams, rewarded_set: RewardedSetParams, }; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Role.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Role.ts new file mode 100644 index 0000000000..3bac0196bd --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/Role.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Role = "EntryGateway" | "Layer1" | "Layer2" | "Layer3" | "ExitGateway" | "Standby"; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/UnbondedMixnode.ts b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/UnbondedMixnode.ts new file mode 100644 index 0000000000..adde1f385a --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/bindings/ts-packages/types/src/types/rust/UnbondedMixnode.ts @@ -0,0 +1,23 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Basic information of a node that used to be part of the mix network but has already unbonded. + */ +export type UnbondedMixnode = { +/** + * Base58-encoded ed25519 EdDSA public key. + */ +identity_key: string, +/** + * Address of the owner of this mixnode. + */ +owner: string, +/** + * Entity who bonded this mixnode on behalf of the owner. + * If exists, it's most likely the address of the vesting contract. + */ +proxy: string | null, +/** + * Block height at which this mixnode has unbonded. + */ +unbonding_height: number, }; diff --git a/common/cosmwasm-smart-contracts/vesting-contract/bindings/ts-packages/types/src/types/rust/Period.ts b/common/cosmwasm-smart-contracts/vesting-contract/bindings/ts-packages/types/src/types/rust/Period.ts new file mode 100644 index 0000000000..8dfc1576da --- /dev/null +++ b/common/cosmwasm-smart-contracts/vesting-contract/bindings/ts-packages/types/src/types/rust/Period.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * The vesting period. + */ +export type Period = "Before" | { "In": number } | "After"; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Account.ts b/common/types/bindings/ts-packages/types/src/types/rust/Account.ts new file mode 100644 index 0000000000..4980e9b69e --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Account.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CurrencyDenom } from "./CurrencyDenom"; + +export type Account = { client_address: string, base_mix_denom: string, display_mix_denom: CurrencyDenom, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/AccountEntry.ts b/common/types/bindings/ts-packages/types/src/types/rust/AccountEntry.ts new file mode 100644 index 0000000000..39db6db6cc --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/AccountEntry.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AccountEntry = { id: string, address: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/AccountWithMnemonic.ts b/common/types/bindings/ts-packages/types/src/types/rust/AccountWithMnemonic.ts new file mode 100644 index 0000000000..82d73ac020 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/AccountWithMnemonic.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Account } from "./Account"; + +export type AccountWithMnemonic = { account: Account, mnemonic: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts b/common/types/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts new file mode 100644 index 0000000000..20e5640581 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/ActiveSetUpdate.ts @@ -0,0 +1,18 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Specification on how the active set should be updated. + */ +export type ActiveSetUpdate = { +/** + * The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`]) + */ +entry_gateways: number, +/** + * The expected number of nodes assigned exit gateway role (i.e. [`Role::ExitGateway`]) + */ +exit_gateways: number, +/** + * The expected number of nodes assigned the 'mixnode' role, i.e. total of [`Role::Layer1`], [`Role::Layer2`] and [`Role::Layer3`]. + */ +mixnodes: number, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Balance.ts b/common/types/bindings/ts-packages/types/src/types/rust/Balance.ts new file mode 100644 index 0000000000..b2632511e3 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Balance.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type Balance = { amount: DecCoin, printable_balance: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Coin.ts b/common/types/bindings/ts-packages/types/src/types/rust/Coin.ts new file mode 100644 index 0000000000..4dc9188584 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Coin.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Coin = { denom: string, amount: bigint, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/CosmosFee.ts b/common/types/bindings/ts-packages/types/src/types/rust/CosmosFee.ts new file mode 100644 index 0000000000..bcb0edc1aa --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/CosmosFee.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Coin } from "./Coin"; + +export type CosmosFee = { amount: Array, gas_limit: bigint, payer: string | null, granter: string | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/CurrencyDenom.ts b/common/types/bindings/ts-packages/types/src/types/rust/CurrencyDenom.ts new file mode 100644 index 0000000000..939585e625 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/CurrencyDenom.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CurrencyDenom = "unknown" | "nym" | "nymt" | "nyx" | "nyxt"; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/DecCoin.ts b/common/types/bindings/ts-packages/types/src/types/rust/DecCoin.ts new file mode 100644 index 0000000000..5b0a446ba8 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/DecCoin.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CurrencyDenom } from "./CurrencyDenom"; + +export type DecCoin = { denom: CurrencyDenom, amount: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Delegation.ts b/common/types/bindings/ts-packages/types/src/types/rust/Delegation.ts new file mode 100644 index 0000000000..44112fa3cf --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Delegation.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type Delegation = { owner: string, mix_id: number, amount: DecCoin, height: bigint, proxy: string | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/DelegationEvent.ts b/common/types/bindings/ts-packages/types/src/types/rust/DelegationEvent.ts new file mode 100644 index 0000000000..86a62f4c26 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/DelegationEvent.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { DelegationEventKind } from "./DelegationEventKind"; + +export type DelegationEvent = { kind: DelegationEventKind, mix_id: number, address: string, amount: DecCoin | null, proxy: string | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/DelegationEventKind.ts b/common/types/bindings/ts-packages/types/src/types/rust/DelegationEventKind.ts new file mode 100644 index 0000000000..ad43ee8282 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/DelegationEventKind.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DelegationEventKind = "Delegate" | "Undelegate"; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/DelegationResult.ts b/common/types/bindings/ts-packages/types/src/types/rust/DelegationResult.ts new file mode 100644 index 0000000000..66cb790c18 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/DelegationResult.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type DelegationResult = { source_address: string, target_address: string, amount: DecCoin | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts b/common/types/bindings/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts new file mode 100644 index 0000000000..113f6ac944 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { DelegationWithEverything } from "./DelegationWithEverything"; + +export type DelegationsSummaryResponse = { delegations: Array, total_delegations: DecCoin, total_rewards: DecCoin, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/DelegationWithEverything.ts b/common/types/bindings/ts-packages/types/src/types/rust/DelegationWithEverything.ts new file mode 100644 index 0000000000..ed8a84b8d7 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/DelegationWithEverything.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { DelegationEvent } from "./DelegationEvent"; +import type { NodeCostParams } from "./MixNodeCostParams"; + +export type DelegationWithEverything = { owner: string, mix_id: number, node_identity: string, amount: DecCoin, accumulated_by_delegates: DecCoin | null, accumulated_by_operator: DecCoin | null, block_height: bigint, delegated_on_iso_datetime: string | null, cost_params: NodeCostParams | null, avg_uptime_percent: number | null, stake_saturation: string | null, uses_vesting_contract_tokens: boolean, unclaimed_rewards: DecCoin | null, errors: string | null, pending_events: Array, mixnode_is_unbonding: boolean | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Fee.ts b/common/types/bindings/ts-packages/types/src/types/rust/Fee.ts new file mode 100644 index 0000000000..d88be5b4c4 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Fee.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CosmosFee } from "./CosmosFee"; + +export type Fee = { "Manual": CosmosFee } | { "Auto": number | null }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/FeeDetails.ts b/common/types/bindings/ts-packages/types/src/types/rust/FeeDetails.ts new file mode 100644 index 0000000000..7383077844 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/FeeDetails.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { Fee } from "./Fee"; + +export type FeeDetails = { amount: DecCoin | null, fee: Fee, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Gas.ts b/common/types/bindings/ts-packages/types/src/types/rust/Gas.ts new file mode 100644 index 0000000000..a76fdfecd9 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Gas.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Gas = { +/** + * units of gas used + */ +gas_units: bigint, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/GasInfo.ts b/common/types/bindings/ts-packages/types/src/types/rust/GasInfo.ts new file mode 100644 index 0000000000..b51eb3c02d --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/GasInfo.ts @@ -0,0 +1,12 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Gas } from "./Gas"; + +export type GasInfo = { +/** + * GasWanted is the maximum units of work we allow this tx to perform. + */ +gas_wanted: Gas, +/** + * GasUsed is the amount of gas actually consumed. + */ +gas_used: Gas, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Gateway.ts b/common/types/bindings/ts-packages/types/src/types/rust/Gateway.ts new file mode 100644 index 0000000000..7ecd1e6789 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Gateway.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Gateway = { host: string, mix_port: number, clients_port: number, location: string, sphinx_key: string, +/** + * Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients + */ +identity_key: string, version: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/GatewayBond.ts b/common/types/bindings/ts-packages/types/src/types/rust/GatewayBond.ts new file mode 100644 index 0000000000..1bbdf83099 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/GatewayBond.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { Gateway } from "./Gateway"; + +export type GatewayBond = { pledge_amount: DecCoin, owner: string, block_height: bigint, gateway: Gateway, proxy: string | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts b/common/types/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts new file mode 100644 index 0000000000..7ed7f7907a --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts @@ -0,0 +1,35 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RewardedSetParams } from "./RewardedSetParams"; + +/** + * Specification on how the rewarding params should be updated. + */ +export type IntervalRewardingParamsUpdate = { +/** + * Defines the new value of the reward pool. + */ +reward_pool: string | null, +/** + * Defines the new value of the staking supply. + */ +staking_supply: string | null, +/** + * Defines the new value of the staking supply scale factor. + */ +staking_supply_scale_factor: string | null, +/** + * Defines the new value of the sybil resistance percent. + */ +sybil_resistance_percent: string | null, +/** + * Defines the new value of the active set work factor. + */ +active_set_work_factor: string | null, +/** + * Defines the new value of the interval pool emission rate. + */ +interval_pool_emission: string | null, +/** + * Defines the parameters of the rewarded set. + */ +rewarded_set_params: RewardedSetParams | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/MixNodeBond.ts b/common/types/bindings/ts-packages/types/src/types/rust/MixNodeBond.ts new file mode 100644 index 0000000000..730582750b --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/MixNodeBond.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { MixNode } from "./Mixnode"; + +export type MixNodeBond = { mix_id: number, owner: string, original_pledge: DecCoin, mix_node: MixNode, proxy: string | null, bonding_height: bigint, is_unbonding: boolean, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/MixNodeCostParams.ts b/common/types/bindings/ts-packages/types/src/types/rust/MixNodeCostParams.ts new file mode 100644 index 0000000000..8658f9c91e --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/MixNodeCostParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type NodeCostParams = { profit_margin_percent: string, interval_operating_cost: DecCoin, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/MixNodeDetails.ts b/common/types/bindings/ts-packages/types/src/types/rust/MixNodeDetails.ts new file mode 100644 index 0000000000..7103354d40 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/MixNodeDetails.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { MixNodeBond } from "./MixNodeBond"; +import type { NodeRewarding } from "./NodeRewarding"; + +export type MixNodeDetails = { bond_information: MixNodeBond, rewarding_details: NodeRewarding, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/Mixnode.ts b/common/types/bindings/ts-packages/types/src/types/rust/Mixnode.ts new file mode 100644 index 0000000000..ada169d2b4 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/Mixnode.ts @@ -0,0 +1,34 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Information provided by the node operator during bonding that are used to allow other entities to use the services of this node. + */ +export type MixNode = { +/** + * Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com + */ +host: string, +/** + * Port used by this mixnode for listening for mix packets. + */ +mix_port: number, +/** + * Port used by this mixnode for listening for verloc requests. + */ +verloc_port: number, +/** + * Port used by this mixnode for its http(s) API + */ +http_api_port: number, +/** + * Base58-encoded x25519 public key used for sphinx key derivation. + */ +sphinx_key: string, +/** + * Base58-encoded ed25519 EdDSA public key. + */ +identity_key: string, +/** + * The self-reported semver version of this mixnode. + */ +version: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/NodeRewarding.ts b/common/types/bindings/ts-packages/types/src/types/rust/NodeRewarding.ts new file mode 100644 index 0000000000..1d472fbfcb --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/NodeRewarding.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NodeCostParams } from "./MixNodeCostParams"; + +export type NodeRewarding = { cost_params: NodeCostParams, operator: string, delegates: string, total_unit_reward: string, unit_delegation: string, last_rewarded_epoch: number, unique_delegations: number, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/NymNodeBond.ts b/common/types/bindings/ts-packages/types/src/types/rust/NymNodeBond.ts new file mode 100644 index 0000000000..0d50d01f55 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/NymNodeBond.ts @@ -0,0 +1,39 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type NymNodeBond = { +/** + * Unique id assigned to the bonded node. + */ +node_id: number, +/** + * Address of the owner of this nym-node. + */ +owner: string, +/** + * Original amount pledged by the operator of this node. + */ +original_pledge: DecCoin, +/** + * Block height at which this nym-node has been bonded. + */ +bonding_height: bigint, +/** + * Flag to indicate whether this node is in the process of unbonding, + * that will conclude upon the epoch finishing. + */ +is_unbonding: boolean, +/** + * Network address of this nym-node, for example 1.1.1.1 or foo.mixnode.com + * that is used to discover other capabilities of this node. + */ +host: string, +/** + * Allow specifying custom port for accessing the http, and thus self-described, api + * of this node for the capabilities discovery. + */ +custom_http_port: number | null, +/** + * Base58-encoded ed25519 EdDSA public key. + */ +identity_key: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/NymNodeDetails.ts b/common/types/bindings/ts-packages/types/src/types/rust/NymNodeDetails.ts new file mode 100644 index 0000000000..3baee09908 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/NymNodeDetails.ts @@ -0,0 +1,21 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NodeRewarding } from "./NodeRewarding"; +import type { NymNodeBond } from "./NymNodeBond"; +import type { PendingNodeChanges } from "./PendingNodeChanges"; + +/** + * Full details associated with given node. + */ +export type NymNodeDetails = { +/** + * Basic bond information of this node, such as owner address, original pledge, etc. + */ +bond_information: NymNodeBond, +/** + * Details used for computation of rewarding related data. + */ +rewarding_details: NodeRewarding, +/** + * Adjustments to the node that are scheduled to happen during future epoch/interval transitions. + */ +pending_changes: PendingNodeChanges, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/OriginalVestingResponse.ts b/common/types/bindings/ts-packages/types/src/types/rust/OriginalVestingResponse.ts new file mode 100644 index 0000000000..33d307ba18 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/OriginalVestingResponse.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type OriginalVestingResponse = { amount: DecCoin, number_of_periods: number, period_duration: bigint, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEvent.ts b/common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEvent.ts new file mode 100644 index 0000000000..9df4241249 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEvent.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PendingEpochEventData } from "./PendingEpochEventData"; + +export type PendingEpochEvent = { id: number, created_at: bigint, event: PendingEpochEventData, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEventData.ts b/common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEventData.ts new file mode 100644 index 0000000000..8ce22bacf9 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/PendingEpochEventData.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActiveSetUpdate } from "./ActiveSetUpdate"; +import type { DecCoin } from "./DecCoin"; + +export type PendingEpochEventData = { "Delegate": { owner: string, mix_id: number, amount: DecCoin, proxy: string | null, } } | { "Undelegate": { owner: string, mix_id: number, proxy: string | null, } } | { "PledgeMore": { mix_id: number, amount: DecCoin, } } | { "DecreasePledge": { mix_id: number, decrease_by: DecCoin, } } | { "UnbondMixnode": { mix_id: number, } } | { "UpdateActiveSetSize": { new_size: number, } } | { "NymNodePledgeMore": { node_id: number, amount: DecCoin, } } | { "NymNodeDecreasePledge": { node_id: number, decrease_by: DecCoin, } } | { "UnbondNymNode": { node_id: number, } } | { "UpdateActiveSet": { update: ActiveSetUpdate, } }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEvent.ts b/common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEvent.ts new file mode 100644 index 0000000000..5358a2a823 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEvent.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PendingIntervalEventData } from "./PendingIntervalEventData"; + +export type PendingIntervalEvent = { id: number, created_at: bigint, event: PendingIntervalEventData, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEventData.ts b/common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEventData.ts new file mode 100644 index 0000000000..788ea37be8 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/PendingIntervalEventData.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { IntervalRewardingParamsUpdate } from "./IntervalRewardingParamsUpdate"; +import type { NodeCostParams } from "./MixNodeCostParams"; + +export type PendingIntervalEventData = { "ChangeMixCostParams": { mix_id: number, new_costs: NodeCostParams, } } | { "ChangeNymNodeCostParams": { node_id: number, new_costs: NodeCostParams, } } | { "UpdateRewardingParams": { update: IntervalRewardingParamsUpdate, } } | { "UpdateIntervalConfig": { epochs_in_interval: number, epoch_duration_secs: bigint, } }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts b/common/types/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts new file mode 100644 index 0000000000..b1667a190e --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/PendingNodeChanges.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PendingNodeChanges = { pledge_change: number | null, cost_params_change: number | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/PledgeData.ts b/common/types/bindings/ts-packages/types/src/types/rust/PledgeData.ts new file mode 100644 index 0000000000..3accba8d63 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/PledgeData.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type PledgeData = { amount: DecCoin, block_time: bigint, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts b/common/types/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts new file mode 100644 index 0000000000..e1d09ac0e3 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts @@ -0,0 +1,19 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RewardedSetParams = { +/** + * The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`]) + */ +entry_gateways: number, +/** + * The expected number of nodes assigned exit gateway role (i.e. [`Role::ExitGateway`]) + */ +exit_gateways: number, +/** + * The expected number of nodes assigned the 'mixnode' role, i.e. total of [`Role::Layer1`], [`Role::Layer2`] and [`Role::Layer3`]. + */ +mixnodes: number, +/** + * Number of nodes in the 'standby' set. (i.e. [`Role::Standby`]) + */ +standby: number, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/RpcTransactionResponse.ts b/common/types/bindings/ts-packages/types/src/types/rust/RpcTransactionResponse.ts new file mode 100644 index 0000000000..6067e083ca --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/RpcTransactionResponse.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { Gas } from "./Gas"; + +export type RpcTransactionResponse = { index: number, tx_result_json: string, block_height: bigint, transaction_hash: string, gas_used: Gas, gas_wanted: Gas, fee: DecCoin | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/SendTxResult.ts b/common/types/bindings/ts-packages/types/src/types/rust/SendTxResult.ts new file mode 100644 index 0000000000..cb39f47184 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/SendTxResult.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { Gas } from "./Gas"; +import type { TransactionDetails } from "./TransactionDetails"; + +export type SendTxResult = { block_height: bigint, code: number, details: TransactionDetails, gas_used: Gas, gas_wanted: Gas, tx_hash: string, fee: DecCoin | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/TransactionDetails.ts b/common/types/bindings/ts-packages/types/src/types/rust/TransactionDetails.ts new file mode 100644 index 0000000000..2bcef1a14d --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/TransactionDetails.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; + +export type TransactionDetails = { amount: DecCoin, from_address: string, to_address: string, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/TransactionExecuteResult.ts b/common/types/bindings/ts-packages/types/src/types/rust/TransactionExecuteResult.ts new file mode 100644 index 0000000000..5e5e902f90 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/TransactionExecuteResult.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { GasInfo } from "./GasInfo"; + +export type TransactionExecuteResult = { logs_json: string, msg_responses_json: string, transaction_hash: string, gas_info: GasInfo, fee: DecCoin | null, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/VestingAccountInfo.ts b/common/types/bindings/ts-packages/types/src/types/rust/VestingAccountInfo.ts new file mode 100644 index 0000000000..5602f9a381 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/VestingAccountInfo.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DecCoin } from "./DecCoin"; +import type { VestingPeriod } from "./VestingPeriod"; + +export type VestingAccountInfo = { owner_address: string, staking_address: string | null, start_time: bigint, periods: Array, amount: DecCoin, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/VestingPeriod.ts b/common/types/bindings/ts-packages/types/src/types/rust/VestingPeriod.ts new file mode 100644 index 0000000000..242962750f --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/VestingPeriod.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type VestingPeriod = { start_time: bigint, period_seconds: bigint, }; diff --git a/common/types/bindings/ts-packages/types/src/types/rust/WrappedDelegationEvent.ts b/common/types/bindings/ts-packages/types/src/types/rust/WrappedDelegationEvent.ts new file mode 100644 index 0000000000..854982c359 --- /dev/null +++ b/common/types/bindings/ts-packages/types/src/types/rust/WrappedDelegationEvent.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DelegationEvent } from "./DelegationEvent"; + +export type WrappedDelegationEvent = { event: DelegationEvent, node_identity: string, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/AnnotationResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/AnnotationResponse.ts new file mode 100644 index 0000000000..d7d74a3c8f --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/AnnotationResponse.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NodeAnnotation } from "./NodeAnnotation"; + +export type AnnotationResponse = { node_id: number, annotation: NodeAnnotation | null, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/ConfigScore.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/ConfigScore.ts new file mode 100644 index 0000000000..82930c798b --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/ConfigScore.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ConfigScore = { +/** + * Total score after taking all the criteria into consideration + */ +score: number, versions_behind: number | null, self_described_api_available: boolean, accepted_terms_and_conditions: boolean, runs_nym_node_binary: boolean, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DeclaredRoles.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DeclaredRoles.ts new file mode 100644 index 0000000000..1ddb236ff5 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DeclaredRoles.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DeclaredRoles = { mixnode: boolean, entry: boolean, exit_nr: boolean, exit_ipr: boolean, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DescribedNodeType.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DescribedNodeType.ts new file mode 100644 index 0000000000..bc27071fea --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DescribedNodeType.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DescribedNodeType = "legacy_mixnode" | "legacy_gateway" | "nym_node"; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DetailedNodePerformance.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DetailedNodePerformance.ts new file mode 100644 index 0000000000..814619b026 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DetailedNodePerformance.ts @@ -0,0 +1,9 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConfigScore } from "./ConfigScore"; +import type { RoutingScore } from "./RoutingScore"; + +export type DetailedNodePerformance = { +/** + * routing_score * config_score + */ +performance_score: number, routing_score: RoutingScore, config_score: ConfigScore, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DisplayRole.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DisplayRole.ts new file mode 100644 index 0000000000..621f5ce685 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/DisplayRole.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DisplayRole = "entryGateway" | "layer1" | "layer2" | "layer3" | "exitGateway" | "standby"; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts new file mode 100644 index 0000000000..7a7d1ed793 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type GatewayCoreStatusResponse = { identity: string, count: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalPerformanceResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalPerformanceResponse.ts new file mode 100644 index 0000000000..c0ba1e59c5 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalPerformanceResponse.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type HistoricalPerformanceResponse = { date: string, performance: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalUptimeResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalUptimeResponse.ts new file mode 100644 index 0000000000..ba46e77889 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/HistoricalUptimeResponse.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type HistoricalUptimeResponse = { date: string, uptime: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts new file mode 100644 index 0000000000..8295c20aba --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SelectionChance } from "./SelectionChance"; + +export type InclusionProbabilityResponse = { in_active: SelectionChance, in_reserve: SelectionChance, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Interval.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Interval.ts new file mode 100644 index 0000000000..5efd2d2dc5 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Interval.ts @@ -0,0 +1,30 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Specification of a rewarding interval. + */ +export type Interval = { +/** + * Monotonously increasing id of this interval. + */ +id: number, +/** + * Number of epochs in this interval. + */ +epochs_in_interval: number, +/** + * The timestamp indicating the start of the current rewarding epoch. + */ +current_epoch_start: string, +/** + * Monotonously increasing id of the current epoch in this interval. + */ +current_epoch_id: number, +/** + * The duration of all epochs in this interval. + */ +epoch_length: { secs: number; nanos: number; }, +/** + * The total amount of elapsed epochs since the first epoch of the first interval. + */ +total_elapsed_epochs: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts new file mode 100644 index 0000000000..a582bf138b --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/IntervalRewardParams.ts @@ -0,0 +1,51 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Parameters required by the mix-mining reward distribution that do not change during an interval. + */ +export type IntervalRewardParams = { +/** + * Current value of the rewarding pool. + * It is expected to be constant throughout the interval. + */ +reward_pool: string, +/** + * Current value of the staking supply. + * It is expected to be constant throughout the interval. + */ +staking_supply: string, +/** + * Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set. + * Also known as `beta`. + */ +staking_supply_scale_factor: string, +/** + * Current value of the computed reward budget per epoch, per node. + * It is expected to be constant throughout the interval. + */ +epoch_reward_budget: string, +/** + * Current value of the stake saturation point. + * It is expected to be constant throughout the interval. + */ +stake_saturation_point: string, +/** + * Current value of the sybil resistance percent (`alpha`). + * It is not really expected to be changing very often. + * As a matter of fact, unless there's a very specific reason, it should remain constant. + */ +sybil_resistance: string, +/** + * Current active set work factor. + * It is not really expected to be changing very often. + * As a matter of fact, unless there's a very specific reason, it should remain constant. + */ +active_set_work_factor: string, +/** + * Current maximum interval pool emission. + * Assuming all nodes in the rewarded set are fully saturated and have 100% performance, + * this % of the reward pool would get distributed in rewards to all operators and its delegators. + * It is not really expected to be changing very often. + * As a matter of fact, unless there's a very specific reason, it should remain constant. + */ +interval_pool_emission: string, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts new file mode 100644 index 0000000000..8883216403 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type MixnodeCoreStatusResponse = { mix_id: number, count: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatus.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatus.ts new file mode 100644 index 0000000000..4047d50af3 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatus.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type MixnodeStatus = "active" | "standby" | "inactive" | "not_found"; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts new file mode 100644 index 0000000000..eb9221fc37 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { MixnodeStatus } from "./MixnodeStatus"; + +export type MixnodeStatusResponse = { status: MixnodeStatus, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeAnnotation.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeAnnotation.ts new file mode 100644 index 0000000000..a9af2dac87 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeAnnotation.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DetailedNodePerformance } from "./DetailedNodePerformance"; +import type { DisplayRole } from "./DisplayRole"; + +export type NodeAnnotation = { last_24h_performance: string, current_role: DisplayRole | null, detailed_performance: DetailedNodePerformance, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeDatePerformanceResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeDatePerformanceResponse.ts new file mode 100644 index 0000000000..720f52e078 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodeDatePerformanceResponse.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NodeDatePerformanceResponse = { node_id: number, date: string, performance: number | null, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodePerformanceResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodePerformanceResponse.ts new file mode 100644 index 0000000000..698ea013f8 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/NodePerformanceResponse.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NodePerformanceResponse = { node_id: number, performance: number | null, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PaginatedResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PaginatedResponse.ts new file mode 100644 index 0000000000..26ed36c828 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PaginatedResponse.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Pagination } from "./Pagination"; + +export type PaginatedResponse = { pagination: Pagination, data: Array, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Pagination.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Pagination.ts new file mode 100644 index 0000000000..b0a0d46b8a --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/Pagination.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Pagination = { total: number, page: number, size: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PerformanceHistoryResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PerformanceHistoryResponse.ts new file mode 100644 index 0000000000..9b1e93ebc9 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/PerformanceHistoryResponse.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { HistoricalPerformanceResponse } from "./HistoricalPerformanceResponse"; +import type { PaginatedResponse } from "./PaginatedResponse"; + +export type PerformanceHistoryResponse = { node_id: number, history: PaginatedResponse, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts new file mode 100644 index 0000000000..9eea44b06e --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimate.ts @@ -0,0 +1,20 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RewardEstimate = { +/** + * The amount of **decimal** coins that are going to get distributed to the node, + * i.e. the operator and all its delegators. + */ +total_node_reward: string, +/** + * The share of the reward that is going to get distributed to the node operator. + */ +operator: string, +/** + * The share of the reward that is going to get distributed among the node delegators. + */ +delegates: string, +/** + * The operating cost of this node. Note: it's already included in the operator reward. + */ +operating_cost: string, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimationResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimationResponse.ts new file mode 100644 index 0000000000..257edccfb9 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardEstimationResponse.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Interval } from "./Interval"; +import type { RewardEstimate } from "./RewardEstimate"; +import type { RewardingParams } from "./RewardingParams"; + +export type RewardEstimationResponse = { estimation: RewardEstimate, reward_params: RewardingParams, epoch: Interval, as_at: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts new file mode 100644 index 0000000000..e1d09ac0e3 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardedSetParams.ts @@ -0,0 +1,19 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RewardedSetParams = { +/** + * The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`]) + */ +entry_gateways: number, +/** + * The expected number of nodes assigned exit gateway role (i.e. [`Role::ExitGateway`]) + */ +exit_gateways: number, +/** + * The expected number of nodes assigned the 'mixnode' role, i.e. total of [`Role::Layer1`], [`Role::Layer2`] and [`Role::Layer3`]. + */ +mixnodes: number, +/** + * Number of nodes in the 'standby' set. (i.e. [`Role::Standby`]) + */ +standby: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardingParams.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardingParams.ts new file mode 100644 index 0000000000..5c8263bb6d --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RewardingParams.ts @@ -0,0 +1,12 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { IntervalRewardParams } from "./IntervalRewardParams"; +import type { RewardedSetParams } from "./RewardedSetParams"; + +/** + * Parameters used for reward calculation. + */ +export type RewardingParams = { +/** + * Parameters that should remain unchanged throughout an interval. + */ +interval: IntervalRewardParams, rewarded_set: RewardedSetParams, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RoutingScore.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RoutingScore.ts new file mode 100644 index 0000000000..f00098ef60 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/RoutingScore.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RoutingScore = { +/** + * Total score after taking all the criteria into consideration + */ +score: number, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/SelectionChance.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/SelectionChance.ts new file mode 100644 index 0000000000..41c7333f96 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/SelectionChance.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SelectionChance = "High" | "Good" | "Low"; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/StakeSaturationResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/StakeSaturationResponse.ts new file mode 100644 index 0000000000..58ebc65d53 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/StakeSaturationResponse.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type StakeSaturationResponse = { saturation: string, uncapped_saturation: string, as_at: bigint, }; diff --git a/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/UptimeHistoryResponse.ts b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/UptimeHistoryResponse.ts new file mode 100644 index 0000000000..1370fdb206 --- /dev/null +++ b/nym-api/nym-api-requests/bindings/ts-packages/types/src/types/rust/UptimeHistoryResponse.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { HistoricalUptimeResponse } from "./HistoricalUptimeResponse"; +import type { PaginatedResponse } from "./PaginatedResponse"; + +export type UptimeHistoryResponse = { node_id: number, history: PaginatedResponse, }; From c26d4f24fc367779e30a2ab9cdaf028b73cc536d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 13 Dec 2024 10:38:25 +0200 Subject: [PATCH 47/80] Add conversion unit tests for auth msg (#5251) * Add conversion unit tests for auth msg * Fix remaining bad mac conversions --- common/authenticator-requests/src/lib.rs | 1 + common/authenticator-requests/src/util.rs | 71 ++ .../src/v2/registration.rs | 14 +- .../authenticator-requests/src/v2/request.rs | 2 +- .../authenticator-requests/src/v2/response.rs | 8 +- .../src/v3/conversion.rs | 576 +++++++++++++- .../src/v3/registration.rs | 14 +- .../authenticator-requests/src/v3/request.rs | 2 +- .../authenticator-requests/src/v3/response.rs | 10 +- common/authenticator-requests/src/v3/topup.rs | 2 +- .../src/v4/conversion.rs | 701 +++++++++++++++--- .../src/v4/registration.rs | 14 +- .../authenticator-requests/src/v4/request.rs | 4 +- .../authenticator-requests/src/v4/response.rs | 12 +- common/authenticator-requests/src/v4/topup.rs | 2 +- .../src/lib.rs | 2 +- .../authenticator/src/mixnet_listener.rs | 14 +- 17 files changed, 1304 insertions(+), 145 deletions(-) create mode 100644 common/authenticator-requests/src/util.rs diff --git a/common/authenticator-requests/src/lib.rs b/common/authenticator-requests/src/lib.rs index 27164417e8..ed987a9a50 100644 --- a/common/authenticator-requests/src/lib.rs +++ b/common/authenticator-requests/src/lib.rs @@ -8,6 +8,7 @@ pub mod v3; pub mod v4; mod error; +mod util; pub use error::Error; pub use v4 as latest; diff --git a/common/authenticator-requests/src/util.rs b/common/authenticator-requests/src/util.rs new file mode 100644 index 0000000000..cb1269f10c --- /dev/null +++ b/common/authenticator-requests/src/util.rs @@ -0,0 +1,71 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +pub(crate) mod tests { + pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [ + 0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254, + 16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139, + 154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123, + 35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1, + 151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43, + 90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193, + 39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81, + 184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195, + 196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48, + 92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48, + 166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186, + 19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38, + 228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85, + 88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241, + 85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112, + 48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31, + 97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203, + 71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107, + 213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180, + 178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1, + 96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166, + 30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212, + 207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71, + 223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22, + 119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131, + 59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119, + 240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202, + 58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144, + 189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33, + 30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150, + 74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6, + 227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85, + 15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38, + 161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20, + 47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48, + 135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170, + 150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109, + 55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249, + 87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144, + 178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32, + 203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184, + 96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169, + 24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61, + 130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82, + 108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1, + 32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234, + 150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241, + 203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217, + 160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52, + 147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81, + 70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233, + 178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98, + 110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69, + 131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251, + 197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233, + 162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106, + 83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145, + 192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124, + 55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0, + 0, 0, 0, 0, 0, 1, + ]; + pub(crate) const RECIPIENT: &str = "CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f"; +} diff --git a/common/authenticator-requests/src/v2/registration.rs b/common/authenticator-requests/src/v2/registration.rs index b7bf2d8430..f3aa22d749 100644 --- a/common/authenticator-requests/src/v2/registration.rs +++ b/common/authenticator-requests/src/v2/registration.rs @@ -29,7 +29,7 @@ pub type Taken = Option; pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct InitMessage { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, @@ -41,7 +41,7 @@ impl InitMessage { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct FinalMessage { /// Gateway client data pub gateway_client: GatewayClient, @@ -50,28 +50,28 @@ pub struct FinalMessage { pub credential: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RegistrationData { pub nonce: u64, pub gateway_data: GatewayClient, pub wg_port: u16, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RegistredData { pub pub_key: PeerPublicKey, pub private_ip: IpAddr, pub wg_port: u16, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RemainingBandwidthData { pub available_bandwidth: i64, } /// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret. /// Gateway/Nym node can then verify pub_key payload using the same process -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct GatewayClient { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, @@ -147,7 +147,7 @@ impl GatewayClient { // TODO: change the inner type into generic array of size HmacSha256::OutputSize // TODO2: rely on our internal crypto/hmac -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ClientMac(Vec); impl fmt::Display for ClientMac { diff --git a/common/authenticator-requests/src/v2/request.rs b/common/authenticator-requests/src/v2/request.rs index 6943085af7..abd1e5ebff 100644 --- a/common/authenticator-requests/src/v2/request.rs +++ b/common/authenticator-requests/src/v2/request.rs @@ -87,7 +87,7 @@ impl AuthenticatorRequest { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum AuthenticatorRequestData { Initial(InitMessage), Final(Box), diff --git a/common/authenticator-requests/src/v2/response.rs b/common/authenticator-requests/src/v2/response.rs index ab05dfcd35..1b389de43f 100644 --- a/common/authenticator-requests/src/v2/response.rs +++ b/common/authenticator-requests/src/v2/response.rs @@ -100,28 +100,28 @@ impl AuthenticatorResponse { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum AuthenticatorResponseData { PendingRegistration(PendingRegistrationResponse), Registered(RegisteredResponse), RemainingBandwidth(RemainingBandwidthResponse), } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct PendingRegistrationResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: RegistrationData, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct RegisteredResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: RegistredData, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct RemainingBandwidthResponse { pub request_id: u64, pub reply_to: Recipient, diff --git a/common/authenticator-requests/src/v3/conversion.rs b/common/authenticator-requests/src/v3/conversion.rs index fe0699ab8b..1a55576a0d 100644 --- a/common/authenticator-requests/src/v3/conversion.rs +++ b/common/authenticator-requests/src/v3/conversion.rs @@ -19,6 +19,24 @@ impl From for v3::request::AuthenticatorReque } } +impl TryFrom for v2::request::AuthenticatorRequest { + type Error = crate::Error; + + fn try_from( + authenticator_request: v3::request::AuthenticatorRequest, + ) -> Result { + Ok(Self { + protocol: Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator, + }, + data: authenticator_request.data.try_into()?, + reply_to: authenticator_request.reply_to, + request_id: authenticator_request.request_id, + }) + } +} + impl From for v3::request::AuthenticatorRequestData { fn from(authenticator_request_data: v2::request::AuthenticatorRequestData) -> Self { match authenticator_request_data { @@ -35,6 +53,29 @@ impl From for v3::request::AuthenticatorR } } +impl TryFrom for v2::request::AuthenticatorRequestData { + type Error = crate::Error; + + fn try_from( + authenticator_request_data: v3::request::AuthenticatorRequestData, + ) -> Result { + match authenticator_request_data { + v3::request::AuthenticatorRequestData::Initial(init_msg) => Ok( + v2::request::AuthenticatorRequestData::Initial(init_msg.into()), + ), + v3::request::AuthenticatorRequestData::Final(gw_client) => Ok( + v2::request::AuthenticatorRequestData::Final(gw_client.into()), + ), + v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => Ok( + v2::request::AuthenticatorRequestData::QueryBandwidth(pub_key), + ), + v3::request::AuthenticatorRequestData::TopUpBandwidth(_) => Err( + Self::Error::Conversion("no top up bandwidth variant in v2".to_string()), + ), + } + } +} + impl From for v3::registration::InitMessage { fn from(init_msg: v2::registration::InitMessage) -> Self { Self { @@ -43,6 +84,14 @@ impl From for v3::registration::InitMessage { } } +impl From for v2::registration::InitMessage { + fn from(init_msg: v3::registration::InitMessage) -> Self { + Self { + pub_key: init_msg.pub_key, + } + } +} + impl From> for Box { fn from(gw_client: Box) -> Self { Box::new(v3::registration::FinalMessage { @@ -52,6 +101,15 @@ impl From> for Box> for Box { + fn from(gw_client: Box) -> Self { + Box::new(v2::registration::FinalMessage { + gateway_client: gw_client.gateway_client.into(), + credential: gw_client.credential, + }) + } +} + impl From for v3::registration::GatewayClient { fn from(gw_client: v2::registration::GatewayClient) -> Self { Self { @@ -93,7 +151,10 @@ impl TryFrom for v2::response::Authenticato Ok(Self { data: authenticator_response.data.try_into()?, reply_to: authenticator_response.reply_to, - protocol: authenticator_response.protocol, + protocol: Protocol { + version: 2, + service_provider_type: authenticator_response.protocol.service_provider_type, + }, }) } } @@ -101,7 +162,10 @@ impl TryFrom for v2::response::Authenticato impl From for v3::response::AuthenticatorResponse { fn from(value: v2::response::AuthenticatorResponse) -> Self { Self { - protocol: value.protocol, + protocol: Protocol { + version: 3, + service_provider_type: value.protocol.service_provider_type, + }, data: value.data.into(), reply_to: value.reply_to, } @@ -270,3 +334,511 @@ impl From for v3::registration::Remain } } } + +#[cfg(test)] +mod tests { + use std::{net::IpAddr, str::FromStr}; + + use nym_credentials_interface::CredentialSpendingData; + use nym_crypto::asymmetric::encryption::PrivateKey; + use nym_sphinx::addressing::Recipient; + use nym_wireguard_types::PeerPublicKey; + use x25519_dalek::PublicKey; + + use super::*; + use crate::util::tests::{CREDENTIAL_BYTES, RECIPIENT}; + + #[test] + fn upgrade_initial_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v2::request::AuthenticatorRequest::new_initial_request( + v2::registration::InitMessage::new(pub_key), + reply_to, + ); + let upgraded_msg = v3::request::AuthenticatorRequest::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v3::request::AuthenticatorRequestData::Initial(v3::registration::InitMessage { + pub_key + }) + ); + } + + #[test] + fn downgrade_initial_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v3::request::AuthenticatorRequest::new_initial_request( + v3::registration::InitMessage::new(pub_key), + reply_to, + ); + let downgraded_msg = v2::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v2::request::AuthenticatorRequestData::Initial(v2::registration::InitMessage { + pub_key + }) + ); + } + + #[test] + fn upgrade_final_req() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let private_ip = IpAddr::from_str("10.10.10.10").unwrap(); + let nonce = 42; + let gateway_client = v2::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ); + let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()); + let final_message = v2::registration::FinalMessage { + gateway_client, + credential: credential.clone(), + }; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = + v2::request::AuthenticatorRequest::new_final_request(final_message, reply_to); + let upgraded_msg = v3::request::AuthenticatorRequest::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v3::request::AuthenticatorRequestData::Final(Box::new( + v3::registration::FinalMessage { + gateway_client: v3::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ), + credential + } + )) + ); + } + + #[test] + fn downgrade_final_req() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let private_ip = IpAddr::from_str("10.10.10.10").unwrap(); + let nonce = 42; + let gateway_client = v3::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ); + let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()); + let final_message = v3::registration::FinalMessage { + gateway_client, + credential: credential.clone(), + }; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = + v3::request::AuthenticatorRequest::new_final_request(final_message, reply_to); + let upgraded_msg = v2::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v2::request::AuthenticatorRequestData::Final(Box::new( + v2::registration::FinalMessage { + gateway_client: v2::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ), + credential + } + )) + ); + } + + #[test] + fn upgrade_query_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v2::request::AuthenticatorRequest::new_query_request(pub_key, reply_to); + let upgraded_msg = v3::request::AuthenticatorRequest::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + ); + } + + #[test] + fn downgrade_query_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v3::request::AuthenticatorRequest::new_query_request(pub_key, reply_to); + let downgraded_msg = v2::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v2::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + ); + } + + #[test] + fn downgrade_topup_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); + let top_up_message = v3::topup::TopUpMessage { + pub_key, + credential, + }; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = + v3::request::AuthenticatorRequest::new_topup_request(top_up_message, reply_to); + assert!(v2::request::AuthenticatorRequest::try_from(msg).is_err()); + } + + #[test] + fn upgrade_pending_reg_resp() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let private_ip = IpAddr::from_str("10.10.10.10").unwrap(); + let nonce = 42; + let wg_port = 51822; + let gateway_data = v2::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ); + let registration_data = v2::registration::RegistrationData { + nonce, + gateway_data, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v2::response::AuthenticatorResponse::new_pending_registration_success( + registration_data, + request_id, + reply_to, + ); + let upgraded_msg = v3::response::AuthenticatorResponse::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v3::response::AuthenticatorResponseData::PendingRegistration( + v3::response::PendingRegistrationResponse { + request_id, + reply_to, + reply: v3::registration::RegistrationData { + nonce, + gateway_data: v3::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ), + wg_port, + } + } + ) + ); + } + + #[test] + fn downgrade_pending_reg_resp() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let private_ip = IpAddr::from_str("10.10.10.10").unwrap(); + let nonce = 42; + let wg_port = 51822; + let gateway_data = v3::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ); + let registration_data = v3::registration::RegistrationData { + nonce, + gateway_data, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v3::response::AuthenticatorResponse::new_pending_registration_success( + registration_data, + request_id, + reply_to, + ); + let downgraded_msg = v2::response::AuthenticatorResponse::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v2::response::AuthenticatorResponseData::PendingRegistration( + v2::response::PendingRegistrationResponse { + request_id, + reply_to, + reply: v2::registration::RegistrationData { + nonce, + gateway_data: v2::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ip, + nonce, + ), + wg_port, + } + } + ) + ); + } + + #[test] + fn upgrade_registered_resp() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let private_ip = IpAddr::from_str("10.10.10.10").unwrap(); + let wg_port = 51822; + let registred_data = v2::registration::RegistredData { + pub_key, + private_ip, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v2::response::AuthenticatorResponse::new_registered( + registred_data, + reply_to, + request_id, + ); + let upgraded_msg = v3::response::AuthenticatorResponse::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse { + request_id, + reply_to, + reply: v3::registration::RegistredData { + wg_port, + pub_key, + private_ip + } + }) + ); + } + + #[test] + fn downgrade_registered_resp() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let private_ip = IpAddr::from_str("10.10.10.10").unwrap(); + let wg_port = 51822; + let registred_data = v3::registration::RegistredData { + pub_key, + private_ip, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v3::response::AuthenticatorResponse::new_registered( + registred_data, + reply_to, + request_id, + ); + let downgraded_msg = v2::response::AuthenticatorResponse::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v2::response::AuthenticatorResponseData::Registered(v2::response::RegisteredResponse { + request_id, + reply_to, + reply: v2::registration::RegistredData { + wg_port, + pub_key, + private_ip + } + }) + ); + } + + #[test] + fn upgrade_remaining_bandwidth_resp() { + let available_bandwidth = 42; + let remaining_bandwidth_data = Some(v2::registration::RemainingBandwidthData { + available_bandwidth, + }); + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v2::response::AuthenticatorResponse::new_remaining_bandwidth( + remaining_bandwidth_data, + reply_to, + request_id, + ); + let upgraded_msg = v3::response::AuthenticatorResponse::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v3::response::AuthenticatorResponseData::RemainingBandwidth( + v3::response::RemainingBandwidthResponse { + request_id, + reply_to, + reply: Some(v3::registration::RemainingBandwidthData { + available_bandwidth, + }) + } + ) + ); + } + + #[test] + fn downgrade_remaining_bandwidth_resp() { + let available_bandwidth = 42; + let remaining_bandwidth_data = Some(v3::registration::RemainingBandwidthData { + available_bandwidth, + }); + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v3::response::AuthenticatorResponse::new_remaining_bandwidth( + remaining_bandwidth_data, + reply_to, + request_id, + ); + let downgraded_msg = v2::response::AuthenticatorResponse::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v2::response::AuthenticatorResponseData::RemainingBandwidth( + v2::response::RemainingBandwidthResponse { + request_id, + reply_to, + reply: Some(v2::registration::RemainingBandwidthData { + available_bandwidth, + }) + } + ) + ); + } + + #[test] + fn downgrade_topup_resp() { + let available_bandwidth = 42; + let remaining_bandwidth_data = v3::registration::RemainingBandwidthData { + available_bandwidth, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v3::response::AuthenticatorResponse::new_topup_bandwidth( + remaining_bandwidth_data, + reply_to, + request_id, + ); + assert!(v2::response::AuthenticatorResponse::try_from(msg).is_err()); + } +} diff --git a/common/authenticator-requests/src/v3/registration.rs b/common/authenticator-requests/src/v3/registration.rs index 37234f7e1f..d9fac785a8 100644 --- a/common/authenticator-requests/src/v3/registration.rs +++ b/common/authenticator-requests/src/v3/registration.rs @@ -29,7 +29,7 @@ pub type Taken = Option; pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct InitMessage { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, @@ -41,7 +41,7 @@ impl InitMessage { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct FinalMessage { /// Gateway client data pub gateway_client: GatewayClient, @@ -50,28 +50,28 @@ pub struct FinalMessage { pub credential: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RegistrationData { pub nonce: u64, pub gateway_data: GatewayClient, pub wg_port: u16, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RegistredData { pub pub_key: PeerPublicKey, pub private_ip: IpAddr, pub wg_port: u16, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RemainingBandwidthData { pub available_bandwidth: i64, } /// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret. /// Gateway/Nym node can then verify pub_key payload using the same process -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct GatewayClient { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, @@ -147,7 +147,7 @@ impl GatewayClient { // TODO: change the inner type into generic array of size HmacSha256::OutputSize // TODO2: rely on our internal crypto/hmac -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ClientMac(Vec); impl fmt::Display for ClientMac { diff --git a/common/authenticator-requests/src/v3/request.rs b/common/authenticator-requests/src/v3/request.rs index 32db17aed2..9a7940e1cc 100644 --- a/common/authenticator-requests/src/v3/request.rs +++ b/common/authenticator-requests/src/v3/request.rs @@ -106,7 +106,7 @@ impl AuthenticatorRequest { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum AuthenticatorRequestData { Initial(InitMessage), Final(Box), diff --git a/common/authenticator-requests/src/v3/response.rs b/common/authenticator-requests/src/v3/response.rs index 370fc64671..ca44fb19f6 100644 --- a/common/authenticator-requests/src/v3/response.rs +++ b/common/authenticator-requests/src/v3/response.rs @@ -120,7 +120,7 @@ impl AuthenticatorResponse { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum AuthenticatorResponseData { PendingRegistration(PendingRegistrationResponse), Registered(RegisteredResponse), @@ -128,28 +128,28 @@ pub enum AuthenticatorResponseData { TopUpBandwidth(TopUpBandwidthResponse), } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct PendingRegistrationResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: RegistrationData, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct RegisteredResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: RegistredData, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct RemainingBandwidthResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TopUpBandwidthResponse { pub request_id: u64, pub reply_to: Recipient, diff --git a/common/authenticator-requests/src/v3/topup.rs b/common/authenticator-requests/src/v3/topup.rs index 31a61a0659..1163d07f12 100644 --- a/common/authenticator-requests/src/v3/topup.rs +++ b/common/authenticator-requests/src/v3/topup.rs @@ -5,7 +5,7 @@ use nym_credentials_interface::CredentialSpendingData; use nym_wireguard_types::PeerPublicKey; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct TopUpMessage { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, diff --git a/common/authenticator-requests/src/v4/conversion.rs b/common/authenticator-requests/src/v4/conversion.rs index 7b79b5f087..86a1c8791b 100644 --- a/common/authenticator-requests/src/v4/conversion.rs +++ b/common/authenticator-requests/src/v4/conversion.rs @@ -3,37 +3,82 @@ use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; -use crate::{v2, v3, v4}; +use crate::{v3, v4}; -impl From for v4::request::AuthenticatorRequest { - fn from(authenticator_request: v3::request::AuthenticatorRequest) -> Self { - Self { +impl TryFrom for v4::request::AuthenticatorRequest { + type Error = crate::Error; + fn try_from( + authenticator_request: v3::request::AuthenticatorRequest, + ) -> Result { + Ok(Self { protocol: Protocol { version: 4, service_provider_type: ServiceProviderType::Authenticator, }, - data: authenticator_request.data.into(), + data: authenticator_request.data.try_into()?, reply_to: authenticator_request.reply_to, request_id: authenticator_request.request_id, + }) + } +} + +impl TryFrom for v3::request::AuthenticatorRequest { + type Error = crate::Error; + fn try_from( + authenticator_request: v4::request::AuthenticatorRequest, + ) -> Result { + Ok(Self { + protocol: Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator, + }, + data: authenticator_request.data.try_into()?, + reply_to: authenticator_request.reply_to, + request_id: authenticator_request.request_id, + }) + } +} + +impl TryFrom for v4::request::AuthenticatorRequestData { + type Error = crate::Error; + fn try_from( + authenticator_request_data: v3::request::AuthenticatorRequestData, + ) -> Result { + match authenticator_request_data { + v3::request::AuthenticatorRequestData::Initial(init_msg) => Ok( + v4::request::AuthenticatorRequestData::Initial(init_msg.into()), + ), + v3::request::AuthenticatorRequestData::Final(_) => Err(Self::Error::Conversion( + "mac hash breaking change".to_string(), + )), + v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => Ok( + v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key), + ), + v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => Ok( + v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()), + ), } } } -impl From for v4::request::AuthenticatorRequestData { - fn from(authenticator_request_data: v3::request::AuthenticatorRequestData) -> Self { +impl TryFrom for v3::request::AuthenticatorRequestData { + type Error = crate::Error; + fn try_from( + authenticator_request_data: v4::request::AuthenticatorRequestData, + ) -> Result { match authenticator_request_data { - v3::request::AuthenticatorRequestData::Initial(init_msg) => { - v4::request::AuthenticatorRequestData::Initial(init_msg.into()) - } - v3::request::AuthenticatorRequestData::Final(gw_client) => { - v4::request::AuthenticatorRequestData::Final(gw_client.into()) - } - v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => { - v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) - } - v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => { - v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()) - } + v4::request::AuthenticatorRequestData::Initial(init_msg) => Ok( + v3::request::AuthenticatorRequestData::Initial(init_msg.into()), + ), + v4::request::AuthenticatorRequestData::Final(_) => Err(Self::Error::Conversion( + "mac hash breaking change".to_string(), + )), + v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => Ok( + v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key), + ), + v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => Ok( + v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()), + ), } } } @@ -46,12 +91,11 @@ impl From for v4::registration::InitMessage { } } -impl From> for Box { - fn from(gw_client: Box) -> Self { - Box::new(v4::registration::FinalMessage { - gateway_client: gw_client.gateway_client.into(), - credential: gw_client.credential, - }) +impl From for v3::registration::InitMessage { + fn from(init_msg: v4::registration::InitMessage) -> Self { + Self { + pub_key: init_msg.pub_key, + } } } @@ -64,67 +108,26 @@ impl From> for Box { } } -impl From for v4::registration::GatewayClient { - fn from(gw_client: v2::registration::GatewayClient) -> Self { - Self { - pub_key: gw_client.pub_key, - private_ips: gw_client.private_ip.into(), - mac: gw_client.mac.into(), - } +impl From> for Box { + fn from(top_up_message: Box) -> Self { + Box::new(v3::topup::TopUpMessage { + pub_key: top_up_message.pub_key, + credential: top_up_message.credential, + }) } } -impl From for v4::registration::GatewayClient { - fn from(gw_client: v3::registration::GatewayClient) -> Self { - Self { - pub_key: gw_client.pub_key, - private_ips: gw_client.private_ip.into(), - mac: gw_client.mac.into(), - } - } -} - -impl From for v3::registration::GatewayClient { - fn from(gw_client: v4::registration::GatewayClient) -> Self { - Self { - pub_key: gw_client.pub_key, - private_ip: gw_client.private_ips.ipv4.into(), - mac: gw_client.mac.into(), - } - } -} - -impl From for v2::registration::GatewayClient { - fn from(gw_client: v4::registration::GatewayClient) -> Self { - Self { - pub_key: gw_client.pub_key, - private_ip: gw_client.private_ips.ipv4.into(), - mac: gw_client.mac.into(), - } - } -} - -impl From for v4::registration::ClientMac { - fn from(mac: v2::registration::ClientMac) -> Self { - Self::new(mac.to_vec()) - } -} - -impl From for v4::registration::ClientMac { - fn from(mac: v3::registration::ClientMac) -> Self { - Self::new(mac.to_vec()) - } -} - -impl From for v3::registration::ClientMac { - fn from(mac: v4::registration::ClientMac) -> Self { - Self::new(mac.to_vec()) - } -} - -impl From for v2::registration::ClientMac { - fn from(mac: v4::registration::ClientMac) -> Self { - Self::new(mac.to_vec()) +impl TryFrom for v4::response::AuthenticatorResponse { + type Error = crate::Error; + fn try_from(value: v3::response::AuthenticatorResponse) -> Result { + Ok(Self { + protocol: Protocol { + version: 4, + service_provider_type: value.protocol.service_provider_type, + }, + data: value.data.try_into()?, + reply_to: value.reply_to, + }) } } @@ -137,11 +140,40 @@ impl TryFrom for v3::response::Authenticato Ok(Self { data: authenticator_response.data.try_into()?, reply_to: authenticator_response.reply_to, - protocol: authenticator_response.protocol, + protocol: Protocol { + version: 3, + service_provider_type: authenticator_response.protocol.service_provider_type, + }, }) } } +impl TryFrom for v4::response::AuthenticatorResponseData { + type Error = crate::Error; + fn try_from( + authenticator_response_data: v3::response::AuthenticatorResponseData, + ) -> Result { + match authenticator_response_data { + v3::response::AuthenticatorResponseData::PendingRegistration(_) => Err( + Self::Error::Conversion("mac hash breaking change".to_string()), + ), + + v3::response::AuthenticatorResponseData::Registered(registered_response) => Ok( + v4::response::AuthenticatorResponseData::Registered(registered_response.into()), + ), + + v3::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response, + ) => Ok(v4::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response.into(), + )), + v3::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => Ok( + v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into()), + ), + } + } +} + impl TryFrom for v3::response::AuthenticatorResponseData { type Error = crate::Error; @@ -149,13 +181,10 @@ impl TryFrom for v3::response::Authenti authenticator_response_data: v4::response::AuthenticatorResponseData, ) -> Result { match authenticator_response_data { - v4::response::AuthenticatorResponseData::PendingRegistration( - pending_registration_response, - ) => Ok( - v3::response::AuthenticatorResponseData::PendingRegistration( - pending_registration_response.into(), - ), + v4::response::AuthenticatorResponseData::PendingRegistration(_) => Err( + Self::Error::Conversion("mac hash breaking change".to_string()), ), + v4::response::AuthenticatorResponseData::Registered(registered_response) => Ok( v3::response::AuthenticatorResponseData::Registered(registered_response.into()), ), @@ -173,8 +202,8 @@ impl TryFrom for v3::response::Authenti } } -impl From for v3::response::PendingRegistrationResponse { - fn from(value: v4::response::PendingRegistrationResponse) -> Self { +impl From for v3::response::RegisteredResponse { + fn from(value: v4::response::RegisteredResponse) -> Self { Self { request_id: value.request_id, reply_to: value.reply_to, @@ -183,8 +212,8 @@ impl From for v3::response::PendingRe } } -impl From for v3::response::RegisteredResponse { - fn from(value: v4::response::RegisteredResponse) -> Self { +impl From for v4::response::RegisteredResponse { + fn from(value: v3::response::RegisteredResponse) -> Self { Self { request_id: value.request_id, reply_to: value.reply_to, @@ -193,6 +222,16 @@ impl From for v3::response::RegisteredResponse } } +impl From for v4::response::RemainingBandwidthResponse { + fn from(value: v3::response::RemainingBandwidthResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.map(Into::into), + } + } +} + impl From for v3::response::RemainingBandwidthResponse { fn from(value: v4::response::RemainingBandwidthResponse) -> Self { Self { @@ -203,11 +242,31 @@ impl From for v3::response::RemainingB } } -impl From for v3::registration::RegistrationData { - fn from(value: v4::registration::RegistrationData) -> Self { +impl From for v4::response::TopUpBandwidthResponse { + fn from(value: v3::response::TopUpBandwidthResponse) -> Self { Self { - nonce: value.nonce, - gateway_data: value.gateway_data.into(), + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.into(), + } + } +} + +impl From for v3::response::TopUpBandwidthResponse { + fn from(value: v4::response::TopUpBandwidthResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.into(), + } + } +} + +impl From for v4::registration::RegistredData { + fn from(value: v3::registration::RegistredData) -> Self { + Self { + pub_key: value.pub_key, + private_ips: value.private_ip.into(), wg_port: value.wg_port, } } @@ -223,6 +282,14 @@ impl From for v3::registration::RegistredData { } } +impl From for v4::registration::RemainingBandwidthData { + fn from(value: v3::registration::RemainingBandwidthData) -> Self { + Self { + available_bandwidth: value.available_bandwidth, + } + } +} + impl From for v3::registration::RemainingBandwidthData { fn from(value: v4::registration::RemainingBandwidthData) -> Self { Self { @@ -230,3 +297,441 @@ impl From for v3::registration::Remain } } } + +#[cfg(test)] +mod tests { + use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, + }; + + use nym_credentials_interface::CredentialSpendingData; + use nym_crypto::asymmetric::encryption::PrivateKey; + use nym_sphinx::addressing::Recipient; + use nym_wireguard_types::PeerPublicKey; + use x25519_dalek::PublicKey; + + use super::*; + use crate::util::tests::{CREDENTIAL_BYTES, RECIPIENT}; + + #[test] + fn upgrade_initial_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v3::request::AuthenticatorRequest::new_initial_request( + v3::registration::InitMessage::new(pub_key), + reply_to, + ); + let upgraded_msg = v4::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 4, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v4::request::AuthenticatorRequestData::Initial(v4::registration::InitMessage { + pub_key + }) + ); + } + + #[test] + fn downgrade_initial_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v4::request::AuthenticatorRequest::new_initial_request( + v4::registration::InitMessage::new(pub_key), + reply_to, + ); + let downgraded_msg = v3::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v3::request::AuthenticatorRequestData::Initial(v3::registration::InitMessage { + pub_key + }) + ); + } + + #[test] + fn upgrade_final_req() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap(); + let nonce = 42; + let gateway_client = v3::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + ipv4.into(), + nonce, + ); + let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()); + let final_message = v3::registration::FinalMessage { + gateway_client, + credential: credential.clone(), + }; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = + v3::request::AuthenticatorRequest::new_final_request(final_message, reply_to); + assert!(v4::request::AuthenticatorRequest::try_from(msg).is_err()); + } + + #[test] + fn downgrade_final_req() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap(); + let private_ips = + v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::10").unwrap()); + let nonce = 42; + let gateway_client = v4::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ips, + nonce, + ); + let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()); + let final_message = v4::registration::FinalMessage { + gateway_client, + credential: credential.clone(), + }; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = + v4::request::AuthenticatorRequest::new_final_request(final_message, reply_to); + assert!(v3::request::AuthenticatorRequest::try_from(msg).is_err()); + } + + #[test] + fn upgrade_query_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v3::request::AuthenticatorRequest::new_query_request(pub_key, reply_to); + let upgraded_msg = v4::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 4, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + ); + } + + #[test] + fn downgrade_query_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v4::request::AuthenticatorRequest::new_query_request(pub_key, reply_to); + let downgraded_msg = v3::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + ); + } + + #[test] + fn downgrade_topup_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); + let top_up_message = v4::topup::TopUpMessage { + pub_key, + credential: credential.clone(), + }; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = + v4::request::AuthenticatorRequest::new_topup_request(top_up_message, reply_to); + let downgraded_msg = v3::request::AuthenticatorRequest::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v3::request::AuthenticatorRequestData::TopUpBandwidth(Box::new( + v3::topup::TopUpMessage { + pub_key, + credential + } + )) + ); + } + + #[test] + fn upgrade_pending_reg_resp() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap(); + let nonce = 42; + let wg_port = 51822; + let gateway_data = v3::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + ipv4.into(), + nonce, + ); + let registration_data = v3::registration::RegistrationData { + nonce, + gateway_data, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v3::response::AuthenticatorResponse::new_pending_registration_success( + registration_data, + request_id, + reply_to, + ); + assert!(v4::response::AuthenticatorResponse::try_from(msg).is_err()); + } + + #[test] + fn downgrade_pending_reg_resp() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap(); + let private_ips = + v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::10").unwrap()); + let nonce = 42; + let wg_port = 51822; + let gateway_data = v4::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + private_ips, + nonce, + ); + let registration_data = v4::registration::RegistrationData { + nonce, + gateway_data, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v4::response::AuthenticatorResponse::new_pending_registration_success( + registration_data, + request_id, + reply_to, + ); + assert!(v3::response::AuthenticatorResponse::try_from(msg).is_err()); + } + + #[test] + fn upgrade_registered_resp() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap(); + let private_ips = + v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::a0a").unwrap()); + let wg_port = 51822; + let registred_data = v3::registration::RegistredData { + pub_key, + private_ip: ipv4.into(), + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v3::response::AuthenticatorResponse::new_registered( + registred_data, + reply_to, + request_id, + ); + let upgraded_msg = v4::response::AuthenticatorResponse::try_from(msg).unwrap(); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 4, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v4::response::AuthenticatorResponseData::Registered(v4::response::RegisteredResponse { + request_id, + reply_to, + reply: v4::registration::RegistredData { + wg_port, + pub_key, + private_ips + } + }) + ); + } + + #[test] + fn downgrade_registered_resp() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap(); + let private_ips = + v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::10").unwrap()); + let wg_port = 51822; + let registred_data = v4::registration::RegistredData { + pub_key, + private_ips, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v4::response::AuthenticatorResponse::new_registered( + registred_data, + reply_to, + request_id, + ); + let downgraded_msg = v3::response::AuthenticatorResponse::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse { + request_id, + reply_to, + reply: v3::registration::RegistredData { + wg_port, + pub_key, + private_ip: ipv4.into() + } + }) + ); + } + + #[test] + fn upgrade_remaining_bandwidth_resp() { + let available_bandwidth = 42; + let remaining_bandwidth_data = Some(v3::registration::RemainingBandwidthData { + available_bandwidth, + }); + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v3::response::AuthenticatorResponse::new_remaining_bandwidth( + remaining_bandwidth_data, + reply_to, + request_id, + ); + let upgraded_msg = v4::response::AuthenticatorResponse::try_from(msg).unwrap(); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 4, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v4::response::AuthenticatorResponseData::RemainingBandwidth( + v4::response::RemainingBandwidthResponse { + request_id, + reply_to, + reply: Some(v4::registration::RemainingBandwidthData { + available_bandwidth, + }) + } + ) + ); + } + + #[test] + fn downgrade_remaining_bandwidth_resp() { + let available_bandwidth = 42; + let remaining_bandwidth_data = Some(v4::registration::RemainingBandwidthData { + available_bandwidth, + }); + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v4::response::AuthenticatorResponse::new_remaining_bandwidth( + remaining_bandwidth_data, + reply_to, + request_id, + ); + let downgraded_msg = v3::response::AuthenticatorResponse::try_from(msg).unwrap(); + + assert_eq!( + downgraded_msg.protocol, + Protocol { + version: 3, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + downgraded_msg.data, + v3::response::AuthenticatorResponseData::RemainingBandwidth( + v3::response::RemainingBandwidthResponse { + request_id, + reply_to, + reply: Some(v3::registration::RemainingBandwidthData { + available_bandwidth, + }) + } + ) + ); + } + + #[test] + fn downgrade_topup_resp() { + let available_bandwidth = 42; + let remaining_bandwidth_data = v4::registration::RemainingBandwidthData { + available_bandwidth, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v4::response::AuthenticatorResponse::new_topup_bandwidth( + remaining_bandwidth_data, + reply_to, + request_id, + ); + assert!(v3::response::AuthenticatorResponse::try_from(msg).is_err()); + } +} diff --git a/common/authenticator-requests/src/v4/registration.rs b/common/authenticator-requests/src/v4/registration.rs index a4a49c66a5..2595922f00 100644 --- a/common/authenticator-requests/src/v4/registration.rs +++ b/common/authenticator-requests/src/v4/registration.rs @@ -81,7 +81,7 @@ impl From for IpPair { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct InitMessage { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, @@ -93,7 +93,7 @@ impl InitMessage { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct FinalMessage { /// Gateway client data pub gateway_client: GatewayClient, @@ -102,28 +102,28 @@ pub struct FinalMessage { pub credential: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RegistrationData { pub nonce: u64, pub gateway_data: GatewayClient, pub wg_port: u16, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RegistredData { pub pub_key: PeerPublicKey, pub private_ips: IpPair, pub wg_port: u16, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct RemainingBandwidthData { pub available_bandwidth: i64, } /// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret. /// Gateway/Nym node can then verify pub_key payload using the same process -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct GatewayClient { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, @@ -199,7 +199,7 @@ impl GatewayClient { // TODO: change the inner type into generic array of size HmacSha256::OutputSize // TODO2: rely on our internal crypto/hmac -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ClientMac(Vec); impl fmt::Display for ClientMac { diff --git a/common/authenticator-requests/src/v4/request.rs b/common/authenticator-requests/src/v4/request.rs index aa4862b057..25cf0e0f1f 100644 --- a/common/authenticator-requests/src/v4/request.rs +++ b/common/authenticator-requests/src/v4/request.rs @@ -20,7 +20,7 @@ fn generate_random() -> u64 { rng.next_u64() } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct AuthenticatorRequest { pub protocol: Protocol, pub data: AuthenticatorRequestData, @@ -106,7 +106,7 @@ impl AuthenticatorRequest { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum AuthenticatorRequestData { Initial(InitMessage), Final(Box), diff --git a/common/authenticator-requests/src/v4/response.rs b/common/authenticator-requests/src/v4/response.rs index 370fc64671..9743e8db43 100644 --- a/common/authenticator-requests/src/v4/response.rs +++ b/common/authenticator-requests/src/v4/response.rs @@ -10,7 +10,7 @@ use crate::make_bincode_serializer; use super::VERSION; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct AuthenticatorResponse { pub protocol: Protocol, pub data: AuthenticatorResponseData, @@ -120,7 +120,7 @@ impl AuthenticatorResponse { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum AuthenticatorResponseData { PendingRegistration(PendingRegistrationResponse), Registered(RegisteredResponse), @@ -128,28 +128,28 @@ pub enum AuthenticatorResponseData { TopUpBandwidth(TopUpBandwidthResponse), } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct PendingRegistrationResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: RegistrationData, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct RegisteredResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: RegistredData, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct RemainingBandwidthResponse { pub request_id: u64, pub reply_to: Recipient, pub reply: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TopUpBandwidthResponse { pub request_id: u64, pub reply_to: Recipient, diff --git a/common/authenticator-requests/src/v4/topup.rs b/common/authenticator-requests/src/v4/topup.rs index 31a61a0659..1163d07f12 100644 --- a/common/authenticator-requests/src/v4/topup.rs +++ b/common/authenticator-requests/src/v4/topup.rs @@ -5,7 +5,7 @@ use nym_credentials_interface::CredentialSpendingData; use nym_wireguard_types::PeerPublicKey; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct TopUpMessage { /// Base64 encoded x25519 public key pub pub_key: PeerPublicKey, diff --git a/common/service-provider-requests-common/src/lib.rs b/common/service-provider-requests-common/src/lib.rs index f9f0564e1d..3b294baba7 100644 --- a/common/service-provider-requests-common/src/lib.rs +++ b/common/service-provider-requests-common/src/lib.rs @@ -11,7 +11,7 @@ pub enum ServiceProviderType { Authenticator = 2, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Protocol { pub version: u8, pub service_provider_type: ServiceProviderType, diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/service-providers/authenticator/src/mixnet_listener.rs index 4235a9da15..753bcd2fbf 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/service-providers/authenticator/src/mixnet_listener.rs @@ -184,7 +184,12 @@ impl MixnetListener { v2::response::AuthenticatorResponse::new_pending_registration_success( v2::registration::RegistrationData { nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.clone().into(), + gateway_data: v2::registration::GatewayClient::new( + self.keypair().private_key(), + remote_public.inner(), + registration_data.gateway_data.private_ips.ipv4.into(), + registration_data.nonce, + ), wg_port: registration_data.wg_port, }, request_id, @@ -199,7 +204,12 @@ impl MixnetListener { v3::response::AuthenticatorResponse::new_pending_registration_success( v3::registration::RegistrationData { nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.clone().into(), + gateway_data: v3::registration::GatewayClient::new( + self.keypair().private_key(), + remote_public.inner(), + registration_data.gateway_data.private_ips.ipv4.into(), + registration_data.nonce, + ), wg_port: registration_data.wg_port, }, request_id, From b8130443601195fb582d00a5d1794dae45bad816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 13 Dec 2024 09:09:56 +0000 Subject: [PATCH 48/80] bugfix: make sure to apply gateway score filtering when choosing initial node (#5256) * bugfix: make sure to apply gateway score filtering when choosing initial node * mixfetch build fix --- .../src/cli_helpers/client_add_gateway.rs | 9 ++++-- .../src/cli_helpers/client_init.rs | 9 ++++-- common/client-core/src/init/helpers.rs | 32 ++++++------------- common/wasm/client-core/src/helpers.rs | 3 +- sdk/lib/socks5-listener/src/lib.rs | 2 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 11 ++++++- wasm/client/src/client.rs | 1 + wasm/mix-fetch/src/client.rs | 1 + 8 files changed, 38 insertions(+), 30 deletions(-) diff --git a/common/client-core/src/cli_helpers/client_add_gateway.rs b/common/client-core/src/cli_helpers/client_add_gateway.rs index 022db8b9c3..56b1c3ad8e 100644 --- a/common/client-core/src/cli_helpers/client_add_gateway.rs +++ b/common/client-core/src/cli_helpers/client_add_gateway.rs @@ -115,8 +115,13 @@ where hardcoded_topology.get_gateways() } else { let mut rng = rand::thread_rng(); - crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls, user_agent) - .await? + crate::init::helpers::current_gateways( + &mut rng, + &core.client.nym_api_urls, + user_agent, + core.debug.topology.minimum_gateway_performance, + ) + .await? }; // since we're registering with a brand new gateway, diff --git a/common/client-core/src/cli_helpers/client_init.rs b/common/client-core/src/cli_helpers/client_init.rs index 0e3f3bf234..060c1192da 100644 --- a/common/client-core/src/cli_helpers/client_init.rs +++ b/common/client-core/src/cli_helpers/client_init.rs @@ -170,8 +170,13 @@ where hardcoded_topology.get_gateways() } else { let mut rng = rand::thread_rng(); - crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls, user_agent) - .await? + crate::init::helpers::current_gateways( + &mut rng, + &core.client.nym_api_urls, + user_agent, + core.debug.topology.minimum_gateway_performance, + ) + .await? }; let gateway_setup = GatewaySetup::New { diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 60c692df9f..68b3b8d457 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -7,7 +7,7 @@ use futures::{SinkExt, StreamExt}; use log::{debug, info, trace, warn}; use nym_crypto::asymmetric::identity; use nym_gateway_client::GatewayClient; -use nym_topology::{gateway, mix}; +use nym_topology::gateway; use nym_validator_client::client::IdentityKeyRef; use nym_validator_client::UserAgent; use rand::{seq::SliceRandom, Rng}; @@ -82,6 +82,7 @@ pub async fn current_gateways( rng: &mut R, nym_apis: &[Url], user_agent: Option, + minimum_performance: u8, ) -> Result, ClientCoreError> { let nym_api = nym_apis .choose(rng) @@ -95,41 +96,26 @@ pub async fn current_gateways( log::debug!("Fetching list of gateways from: {nym_api}"); let gateways = client.get_all_basic_entry_assigned_nodes().await?; - log::debug!("Found {} gateways", gateways.len()); + info!("nym api reports {} gateways", gateways.len()); + log::trace!("Gateways: {:#?}", gateways); let valid_gateways = gateways .iter() + .filter(|g| g.performance.round_to_integer() >= minimum_performance) .filter_map(|gateway| gateway.try_into().ok()) .collect::>(); log::debug!("After checking validity: {}", valid_gateways.len()); log::trace!("Valid gateways: {:#?}", valid_gateways); - log::info!("nym-api reports {} valid gateways", valid_gateways.len()); + log::info!( + "and {} after validity and performance filtering", + valid_gateways.len() + ); Ok(valid_gateways) } -pub async fn current_mixnodes( - rng: &mut R, - nym_apis: &[Url], -) -> Result, ClientCoreError> { - let nym_api = nym_apis - .choose(rng) - .ok_or(ClientCoreError::ListOfNymApisIsEmpty)?; - let client = nym_validator_client::client::NymApiClient::new(nym_api.clone()); - - log::trace!("Fetching list of mixnodes from: {nym_api}"); - - let mixnodes = client.get_all_basic_active_mixing_assigned_nodes().await?; - let valid_mixnodes = mixnodes - .iter() - .filter_map(|mixnode| mixnode.try_into().ok()) - .collect::>(); - - Ok(valid_mixnodes) -} - #[cfg(not(target_arch = "wasm32"))] async fn connect(endpoint: &str) -> Result { match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await { diff --git a/common/wasm/client-core/src/helpers.rs b/common/wasm/client-core/src/helpers.rs index eee064e2cc..04d6960377 100644 --- a/common/wasm/client-core/src/helpers.rs +++ b/common/wasm/client-core/src/helpers.rs @@ -121,9 +121,10 @@ pub async fn setup_gateway_from_api( force_tls: bool, chosen_gateway: Option, nym_apis: &[Url], + minimum_performance: u8, ) -> Result { let mut rng = thread_rng(); - let gateways = current_gateways(&mut rng, nym_apis, None).await?; + let gateways = current_gateways(&mut rng, nym_apis, None, minimum_performance).await?; setup_gateway_wasm(client_store, force_tls, chosen_gateway, &gateways).await } diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs index bc5350718c..3019402182 100644 --- a/sdk/lib/socks5-listener/src/lib.rs +++ b/sdk/lib/socks5-listener/src/lib.rs @@ -275,7 +275,7 @@ where specification: GatewaySelectionSpecification::UniformRemote { must_use_tls: false, }, - available_gateways: current_gateways(&mut rng, &nym_apis, None).await?, + available_gateways: current_gateways(&mut rng, &nym_apis, None, 50).await?, }); eprintln!("starting the socks5 client"); diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 54885b24eb..118885a650 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -457,7 +457,16 @@ where let user_agent = self.user_agent.clone(); let mut rng = OsRng; - let available_gateways = current_gateways(&mut rng, &nym_api_endpoints, user_agent).await?; + let available_gateways = current_gateways( + &mut rng, + &nym_api_endpoints, + user_agent, + self.config + .debug_config + .topology + .minimum_gateway_performance, + ) + .await?; Ok(GatewaySetup::New { specification: selection_spec, diff --git a/wasm/client/src/client.rs b/wasm/client/src/client.rs index 645fda613c..4bb9f2c2f2 100644 --- a/wasm/client/src/client.rs +++ b/wasm/client/src/client.rs @@ -165,6 +165,7 @@ impl NymClientBuilder { self.force_tls, user_chosen, &nym_api_endpoints, + self.config.base.debug.topology.minimum_gateway_performance, ) .await? }; diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index 9e4bff5e32..e905d01aad 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -102,6 +102,7 @@ impl MixFetchClientBuilder { self.force_tls, user_chosen, &nym_api_endpoints, + self.config.base.debug.topology.minimum_gateway_performance, ) .await?; From 684d7ac1a2d4ea3a2bd55982b89474aa4773b39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 13 Dec 2024 10:03:43 +0000 Subject: [PATCH 49/80] removed legacy socks5 listener (#5259) --- .../workflows/publish-nyms5-android-apk.yml | 8 - Cargo.lock | 245 ------------ Cargo.toml | 5 - sdk/lib/socks5-listener/Cargo.toml | 39 -- sdk/lib/socks5-listener/Makefile | 47 --- .../socks5-listener/build-android-darwin.sh | 10 - sdk/lib/socks5-listener/build-android.sh | 78 ---- sdk/lib/socks5-listener/socks5_c.h | 108 ----- sdk/lib/socks5-listener/src/android.rs | 97 ----- .../src/bin/generate-headers.rs | 6 - sdk/lib/socks5-listener/src/config/mod.rs | 101 ----- .../socks5-listener/src/config/persistence.rs | 26 -- .../socks5-listener/src/config/template.rs | 111 ----- sdk/lib/socks5-listener/src/lib.rs | 378 ------------------ sdk/lib/socks5-listener/src/persistence.rs | 65 --- 15 files changed, 1324 deletions(-) delete mode 100644 sdk/lib/socks5-listener/Cargo.toml delete mode 100644 sdk/lib/socks5-listener/Makefile delete mode 100755 sdk/lib/socks5-listener/build-android-darwin.sh delete mode 100755 sdk/lib/socks5-listener/build-android.sh delete mode 100644 sdk/lib/socks5-listener/socks5_c.h delete mode 100644 sdk/lib/socks5-listener/src/android.rs delete mode 100644 sdk/lib/socks5-listener/src/bin/generate-headers.rs delete mode 100644 sdk/lib/socks5-listener/src/config/mod.rs delete mode 100644 sdk/lib/socks5-listener/src/config/persistence.rs delete mode 100644 sdk/lib/socks5-listener/src/config/template.rs delete mode 100644 sdk/lib/socks5-listener/src/lib.rs delete mode 100644 sdk/lib/socks5-listener/src/persistence.rs diff --git a/.github/workflows/publish-nyms5-android-apk.yml b/.github/workflows/publish-nyms5-android-apk.yml index ab665ea660..b4518d57c3 100644 --- a/.github/workflows/publish-nyms5-android-apk.yml +++ b/.github/workflows/publish-nyms5-android-apk.yml @@ -56,14 +56,6 @@ jobs: rustup target add aarch64-linux-android \ x86_64-linux-android - - name: Build lib nym-socks5-listener - working-directory: sdk/lib/socks5-listener/ - env: - RELEASE: true - RUSTFLAGS: "-C link-args=-Wl,--hash-style=gnu" - # build for arm64 and x86_64 - run: ./build-android.sh aarch64 x86_64 - - name: Build APKs (unsigned) working-directory: nym-connect/native/android env: diff --git a/Cargo.lock b/Cargo.lock index 4b69324120..c7c85e0d9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,23 +140,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" -[[package]] -name = "android_log-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" - -[[package]] -name = "android_logger" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" -dependencies = [ - "android_log-sys", - "env_filter", - "log", -] - [[package]] name = "android_system_properties" version = "0.1.5" @@ -981,12 +964,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.0" @@ -1182,16 +1159,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "comfy-table" version = "7.1.1" @@ -2482,26 +2449,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "ext-trait" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" -dependencies = [ - "ext-trait-proc_macros", -] - -[[package]] -name = "ext-trait-proc_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "extension-storage" version = "1.3.0-rc.0" @@ -2518,21 +2465,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "extension-traits" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" -dependencies = [ - "ext-trait", -] - -[[package]] -name = "extern-c" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23" - [[package]] name = "eyre" version = "0.6.12" @@ -3730,28 +3662,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "js-sys" version = "0.3.72" @@ -3941,22 +3851,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "macro_rules_attribute" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862" -dependencies = [ - "macro_rules_attribute-proc_macro", - "paste", -] - -[[package]] -name = "macro_rules_attribute-proc_macro" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" - [[package]] name = "macroific" version = "1.3.1" @@ -6394,28 +6288,6 @@ dependencies = [ "url", ] -[[package]] -name = "nym-socks5-listener" -version = "0.1.0" -dependencies = [ - "android_logger", - "anyhow", - "futures", - "jni", - "lazy_static", - "log", - "nym-bin-common", - "nym-client-core", - "nym-config", - "nym-credential-storage", - "nym-crypto", - "nym-socks5-client-core", - "rand", - "safer-ffi", - "serde", - "tokio", -] - [[package]] name = "nym-socks5-proxy-helpers" version = "0.1.0" @@ -7548,16 +7420,6 @@ dependencies = [ "log", ] -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] - [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -8455,38 +8317,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "safer-ffi" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd" -dependencies = [ - "extern-c", - "inventory", - "libc", - "macro_rules_attribute", - "paste", - "safer_ffi-proc_macros", - "scopeguard", - "stabby", - "uninit", - "unwind_safe", - "with_builtin_macros", -] - -[[package]] -name = "safer_ffi-proc_macros" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156" -dependencies = [ - "macro_rules_attribute", - "prettyplease", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "same-file" version = "1.0.6" @@ -8919,12 +8749,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha2-const-stable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" - [[package]] name = "sharded-slab" version = "0.1.7" @@ -9325,40 +9149,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "stabby" -version = "36.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311d6bcf0070c462ff626122ec2246f42bd2acd44b28908eedbfd07d500c7d99" -dependencies = [ - "rustversion", - "stabby-abi", -] - -[[package]] -name = "stabby-abi" -version = "36.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6daae1a0707399f56d27fce7f212e50e31d215112a447e1bbcd837ae1bf5f49" -dependencies = [ - "rustversion", - "sha2-const-stable", - "stabby-macros", -] - -[[package]] -name = "stabby-macros" -version = "36.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cf89a0cc9131279235baf8599b0e073fbcb096419204de0cc5d1a48ae73f74" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "rand", - "syn 1.0.109", -] - [[package]] name = "stable-pattern" version = "0.1.0" @@ -10677,15 +10467,6 @@ dependencies = [ "weedle2", ] -[[package]] -name = "uninit" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" -dependencies = [ - "extension-traits", -] - [[package]] name = "universal-hash" version = "0.5.1" @@ -10708,12 +10489,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unwind_safe" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" - [[package]] name = "url" version = "2.5.2" @@ -11441,26 +11216,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "with_builtin_macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" -dependencies = [ - "with_builtin_macros-proc_macros", -] - -[[package]] -name = "with_builtin_macros-proc_macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index d833eb8fe3..52ef268311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,6 @@ members = [ "sdk/ffi/cpp", "sdk/ffi/go", "sdk/ffi/shared", - "sdk/lib/socks5-listener", "sdk/rust/nym-sdk", "service-providers/authenticator", "service-providers/common", @@ -417,10 +416,6 @@ web-sys = "0.3.72" [profile.dev.package.sqlx-macros] opt-level = 3 -[profile.release.package.nym-socks5-listener] -strip = true -codegen-units = 1 - [profile.release.package.nym-client-wasm] # lto = true opt-level = 'z' diff --git a/sdk/lib/socks5-listener/Cargo.toml b/sdk/lib/socks5-listener/Cargo.toml deleted file mode 100644 index ac8c4d3e81..0000000000 --- a/sdk/lib/socks5-listener/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "nym-socks5-listener" -version = "0.1.0" -edition = "2021" -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[[bin]] -name = "generate-headers" -required-features = ["headers"] - -[lib] -crate-type = ["cdylib", "staticlib", "rlib"] - -[dependencies] -anyhow = { workspace = true } -futures = { workspace = true } -lazy_static = { workspace = true } -nym-bin-common = { path = "../../../common/bin-common"} -nym-client-core = { path = "../../../common/client-core", default-features = false } -nym-config-common = { path = "../../../common/config", package = "nym-config" } -nym-credential-storage = { path = "../../../common/credential-storage" } -nym-crypto = { path = "../../../common/crypto" } -nym-socks5-client-core = { path = "../../../common/socks5-client-core", default-features = false } -serde = { workspace = true } -tokio = { workspace = true, features = ["sync", "time"] } -log = { workspace = true } -rand = { workspace = true } - -safer-ffi = { workspace = true } - -[target.'cfg(target_os="android")'.dependencies] -jni = { version = "0.21", default-features = false } -android_logger = "0.14.1" - - -[features] -headers = ["safer-ffi/headers"] diff --git a/sdk/lib/socks5-listener/Makefile b/sdk/lib/socks5-listener/Makefile deleted file mode 100644 index 85edf5b352..0000000000 --- a/sdk/lib/socks5-listener/Makefile +++ /dev/null @@ -1,47 +0,0 @@ -UNAME_S := $(shell uname -s) - -# Default arch -ANDROID_TOOLCHAIN_ARCH=linux-x86_64 -ifeq ($(UNAME_S),Linux) - ANDROID_TOOLCHAIN_ARCH=linux-x86_64 -endif -ifeq ($(UNAME_S),Darwin) - ANDROID_TOOLCHAIN_ARCH=darwin-x86_64 -endif - -ANDROID_TOOLCHAIN=$(NDK_HOME)/toolchains/llvm/prebuilt/$(ANDROID_TOOLCHAIN_ARCH) -ANDROID_API=33 -ANDROID_TARGET_CC=$(ANDROID_TOOLCHAIN)/bin/x86_64-linux-android$(ANDROID_API)-clang -ANDROID_TARGET_AR=$(ANDROID_TOOLCHAIN)/bin/llvm-ar -ANDROID_TARGET_RANLIB=$(ANDROID_TOOLCHAIN)/bin/llvm-ranlib -ANDROID_CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=$(ANDROID_TOOLCHAIN)/bin/x86_64-linux-android$(ANDROID_API)-clang - -# Common environment -ANDROID_ENV = \ - TARGET_CC=$(ANDROID_TARGET_CC) \ - TARGET_AR=$(ANDROID_TARGET_AR) \ - TARGET_RANLIB=$(ANDROID_TARGET_RANLIB) \ - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=$(ANDROID_CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER) - -ANDROID_TARGET=x86_64-linux-android - -none: - @echo "No default target. Please specify a target." - -gen-headers: - cargo run --features headers --bin generate-headers - -#gen-header: -# cbindgen --lang c --crate socks5-c --output socks5_c.h - -android: - $(ANDROID_ENV) cargo build --lib --target $(ANDROID_TARGET) - -android-release: - $(ANDROID_ENV) cargo build --lib --target $(ANDROID_TARGET) --release - -ios: - cargo lipo - -ios-release: - cargo lipo --release diff --git a/sdk/lib/socks5-listener/build-android-darwin.sh b/sdk/lib/socks5-listener/build-android-darwin.sh deleted file mode 100755 index 73a152076f..0000000000 --- a/sdk/lib/socks5-listener/build-android-darwin.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -export API=33 -export TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64" -export TARGET_CC="$TOOLCHAIN/bin/x86_64-linux-android$API-clang" -export TARGET_AR="$TOOLCHAIN/bin/llvm-ar" -export TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" -export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/x86_64-linux-android$API-clang" - -cargo build --lib --target x86_64-linux-android --release diff --git a/sdk/lib/socks5-listener/build-android.sh b/sdk/lib/socks5-listener/build-android.sh deleted file mode 100755 index 8f199d347a..0000000000 --- a/sdk/lib/socks5-listener/build-android.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -# -# Usage -# -# build-android.sh [ARCH ...] -# -# This script builds the lib for android and moves the shared -# objects (*.so) into the right app's directories -# -# ARCH: -# - aarch64 (arm 64) -# - x86_64 (classic PC 64) -# - i686 (x86) -# - armv7 -# -# ⚠ to build for release set the env var `RELEASE=true` - -set -E -set -o pipefail -trap 'catch $? ${FUNCNAME[0]:-main} $LINENO' ERR - -# ANSI style codes -RED="\e[38;5;1m" # red -GRN="\e[38;5;2m" # green -YLW="\e[38;5;3m" # yellow -BLD="\e[1m" # bold -RS="\e[0m" # style reset -# bold variants -B_RED="$BLD$RED" -B_GRN="$BLD$GRN" -B_YLW="$BLD$YLW" - -catch() { - echo -e " $B_RED✗$RS unexpected error, $BLD$2$RS [$BLD$1$RS] L#$BLD$3$RS" - exit 1 -} - -export API=33 -export TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64" -export TARGET_AR="$TOOLCHAIN/bin/llvm-ar" -export TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" -export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/x86_64-linux-android$API-clang" -export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/aarch64-linux-android$API-clang" - -# arch mapping between Rust targets (keys) and Android ABIs (values) -# https://developer.android.com/ndk/guides/abis.html -declare -A arch_map=([x86_64]="x86_64" [aarch64]="arm64-v8a" [armv7]="armeabi-v7a" [i686]="x86") - -output_dir=../../../target -jni_dir=../../../nym-connect/native/android/app/src/main/jniLibs -lib=libnym_socks5_listener.so - -build () { - abi="${arch_map[$1]}" - echo -e " $B_YLW⚡$RS building for arch $BLD$1$RS" - export TARGET_CC="$TOOLCHAIN/bin/$1-linux-android$API-clang" - if [ -a "$jni_dir/$abi/$lib" ]; then - # remove any previously built library - rm "$jni_dir/$abi/$lib" - fi - if [ "$RELEASE" = true ]; then - cargo build --lib --target "$1-linux-android" --release - mv "$output_dir/$1-linux-android/release/$lib" "$jni_dir/$abi/" - else - cargo build --lib --target "$1-linux-android" - mv "$output_dir/$1-linux-android/debug/$lib" "$jni_dir/$abi/" - fi - echo -e " $B_GRN✓$RS lib built successfully for $BLD$1$RS, moved under app's dir$BLD jniLibs/$abi/$RS" -} - -for arch in "$@"; do - if [ "${arch_map[$arch]}" ]; then - build "$arch" - else - echo -e " $B_RED✗$RS unknown arch $BLD$arch$RS" - exit 1 - fi -done diff --git a/sdk/lib/socks5-listener/socks5_c.h b/sdk/lib/socks5-listener/socks5_c.h deleted file mode 100644 index e5a570cba4..0000000000 --- a/sdk/lib/socks5-listener/socks5_c.h +++ /dev/null @@ -1,108 +0,0 @@ -/*! \file */ -/******************************************* - * * - * File auto-generated by `::safer_ffi`. * - * * - * Do not manually edit this file. * - * * - *******************************************/ - -#ifndef __RUST_NYM_SOCKS5_LISTENER__ -#define __RUST_NYM_SOCKS5_LISTENER__ -#ifdef __cplusplus -extern "C" { -#endif - - -#include -#include - -/** */ -/** \remark Has the same ABI as `uint8_t` **/ -#ifdef DOXYGEN -typedef -#endif -enum ClientState { - /** */ - CLIENT_STATE_UNINITIALISED, - /** */ - CLIENT_STATE_CONNECTED, - /** */ - CLIENT_STATE_DISCONNECTED, -} -#ifndef DOXYGEN -; typedef uint8_t -#endif -ClientState_t; - -/** \brief - * `&'lt mut (dyn 'lt + Send + FnMut(A1) -> Ret)` - */ -typedef struct RefDynFnMut1_void_char_ptr { - /** */ - void * env_ptr; - - /** */ - void (*call)(void *, char *); -} RefDynFnMut1_void_char_ptr_t; - -/** \brief - * `&'lt mut (dyn 'lt + Send + FnMut() -> Ret)` - */ -typedef struct RefDynFnMut0_void { - /** */ - void * env_ptr; - - /** */ - void (*call)(void *); -} RefDynFnMut0_void_t; - -/** */ -void -blocking_run_client ( - char const * storage_directory, - char const * service_provider, - RefDynFnMut1_void_char_ptr_t on_start_callback, - RefDynFnMut0_void_t on_shutdown_callback); - -/** */ -char * -existing_service_provider ( - char const * storage_directory); - -/** */ -ClientState_t -get_client_state (void); - -/** */ -void -initialise_logger (void); - -/** */ -void -reset_client_data ( - char const * root_directory); - -/** */ -void -rust_free_string ( - char * string); - -/** */ -void -start_client ( - char const * storage_directory, - char const * service_provider, - RefDynFnMut1_void_char_ptr_t on_start_callback, - RefDynFnMut0_void_t on_shutdown_callback); - -/** */ -void -stop_client (void); - - -#ifdef __cplusplus -} /* extern \"C\" */ -#endif - -#endif /* __RUST_NYM_SOCKS5_LISTENER__ */ diff --git a/sdk/lib/socks5-listener/src/android.rs b/sdk/lib/socks5-listener/src/android.rs deleted file mode 100644 index b8c6032aef..0000000000 --- a/sdk/lib/socks5-listener/src/android.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::ClientState; -use ::safer_ffi::prelude::*; -use jni::{ - objects::{JClass, JObject, JString}, - sys::jint, - JNIEnv, -}; -use std::sync::{Arc, Mutex}; - -fn init_jni_logger() { - use android_logger::{Config, FilterBuilder}; - use log::LevelFilter; - - android_logger::init_once( - Config::default() - .with_max_level(LevelFilter::Trace) - .with_tag("libnyms5") - .with_filter( - FilterBuilder::new() - .parse("debug,tungstenite=warn,mio=warn,tokio_tungstenite=warn") - .build(), - ), - ); - log::debug!("Logger initialized"); -} - -/// Blocking call that starts the socks5 listener -#[no_mangle] -pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_startClient( - mut env: JNIEnv, - _class: JClass, - service_provider: JString, - start_cb: JObject, - stop_cb: JObject, -) { - init_jni_logger(); - - let sp_input: String = env - .get_string(&service_provider) - .expect("Couldn't get java string!") - .into(); - - log::debug!("using sp {}", sp_input); - - let service_provider = char_p::new(sp_input.as_str()); - - let arced = Arc::new(Mutex::new(env)); - let env_start = arced.clone(); - let env_stop = arced.clone(); - - crate::blocking_run_client( - None, - Some(service_provider.as_ref()), - move |_| { - log::debug!("client connected"); - env_start - .lock() - .unwrap() - .call_method(&start_cb, "onStart", "()V", &[]) - .expect("failed to call Java callbacks"); - }, - move || { - log::debug!("client disconnected"); - env_stop - .lock() - .unwrap() - .call_method(&stop_cb, "onStop", "()V", &[]) - .expect("failed to call Java callbacks"); - }, - ); -} - -#[no_mangle] -pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_stopClient(_env: JNIEnv, _class: JClass) { - crate::stop_client(); -} - -#[no_mangle] -pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_getClientState( - _env: JNIEnv, - _class: JClass, -) -> jint { - let state = crate::get_client_state(); - log::debug!("client state {:?}", state); - - match state { - ClientState::Uninitialised => 0, - ClientState::Connected => 1, - ClientState::Disconnected => 2, - } -} - -#[no_mangle] -pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_pingClient(_env: JNIEnv, _class: JClass) { - log::debug!("pong"); - crate::ping_client(); -} diff --git a/sdk/lib/socks5-listener/src/bin/generate-headers.rs b/sdk/lib/socks5-listener/src/bin/generate-headers.rs deleted file mode 100644 index 70a7527b01..0000000000 --- a/sdk/lib/socks5-listener/src/bin/generate-headers.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -fn main() -> ::std::io::Result<()> { - ::nym_socks5_listener::generate_headers() -} diff --git a/sdk/lib/socks5-listener/src/config/mod.rs b/sdk/lib/socks5-listener/src/config/mod.rs deleted file mode 100644 index 5ace48c88f..0000000000 --- a/sdk/lib/socks5-listener/src/config/mod.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::persistence::MobileSocksClientPaths; -use crate::config::template::CONFIG_TEMPLATE; -use nym_bin_common::logging::LoggingSettings; -use nym_config_common::{ - read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, - DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, -}; -use nym_socks5_client_core::config::Config as CoreConfig; -use serde::{Deserialize, Serialize}; -use std::io; -use std::path::{Path, PathBuf}; - -mod persistence; -mod template; - -const DEFAULT_SOCKS5_CLIENTS_DIR: &str = "socks5-clients"; - -/// Derive default path to clients's config directory. -/// It should get resolved to `$HOME/.nym/socks5-clients//config` -pub fn config_directory_from_root, R: AsRef>(root: P, id: R) -> PathBuf { - root.as_ref() - .join(NYM_DIR) - .join(DEFAULT_SOCKS5_CLIENTS_DIR) - .join(id) - .join(DEFAULT_CONFIG_DIR) -} - -/// Derive default path to client's config file. -/// It should get resolved to `$HOME/.nym/socks5-clients//config/config.toml` -pub fn config_filepath_from_root, R: AsRef>(root: P, id: R) -> PathBuf { - config_directory_from_root(root, id).join(DEFAULT_CONFIG_FILENAME) -} - -/// Derive default path to client's data directory where files, such as keys, are stored. -/// It should get resolved to `$HOME/.nym/socks5-clients//data` -pub fn data_directory_from_root, R: AsRef>(root: P, id: R) -> PathBuf { - root.as_ref() - .join(NYM_DIR) - .join(DEFAULT_SOCKS5_CLIENTS_DIR) - .join(id) - .join(DEFAULT_DATA_DIR) -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - pub core: CoreConfig, - - pub storage_paths: Option, - - pub logging: LoggingSettings, -} - -impl NymConfigTemplate for Config { - fn template(&self) -> &'static str { - CONFIG_TEMPLATE - } -} - -impl Config { - pub fn new(storage_root: Option

    , id: S, provider_mix_address: S) -> Self - where - P: AsRef, - S: AsRef, - { - Config { - core: CoreConfig::new( - id.as_ref(), - env!("CARGO_PKG_VERSION"), - provider_mix_address.as_ref(), - ), - storage_paths: storage_root.map(|storage_root| { - MobileSocksClientPaths::new_default(data_directory_from_root( - storage_root, - id.as_ref(), - )) - }), - logging: Default::default(), - } - } - - pub fn read_from_toml_file>(path: P) -> io::Result { - read_config_from_toml_file(path) - } - - pub fn read_from_default_path, R: AsRef>( - storage_root: P, - id: R, - ) -> io::Result { - Self::read_from_toml_file(config_filepath_from_root(storage_root, id)) - } - - pub fn save_to_default_location>(&self, storage_root: P) -> io::Result<()> { - let config_save_location: PathBuf = - config_filepath_from_root(storage_root, &self.core.base.client.id); - save_formatted_config_to_file(self, config_save_location) - } -} diff --git a/sdk/lib/socks5-listener/src/config/persistence.rs b/sdk/lib/socks5-listener/src/config/persistence.rs deleted file mode 100644 index 525ea7066f..0000000000 --- a/sdk/lib/socks5-listener/src/config/persistence.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::data_directory_from_root; -use nym_client_core::config::disk_persistence::CommonClientPaths; -use serde::{Deserialize, Serialize}; -use std::path::Path; - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] -pub struct MobileSocksClientPaths { - #[serde(flatten)] - pub common_paths: CommonClientPaths, -} - -impl MobileSocksClientPaths { - pub fn new_default>(base_data_directory: P) -> Self { - MobileSocksClientPaths { - common_paths: CommonClientPaths::new_base(base_data_directory), - } - } - - pub fn change_root, R: AsRef>(&mut self, new_root: P, id: R) { - let new_data_dir = data_directory_from_root(new_root, id); - self.common_paths = CommonClientPaths::new_base(new_data_dir) - } -} diff --git a/sdk/lib/socks5-listener/src/config/template.rs b/sdk/lib/socks5-listener/src/config/template.rs deleted file mode 100644 index e2ad4c414d..0000000000 --- a/sdk/lib/socks5-listener/src/config/template.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// While using normal toml marshalling would have been way simpler with less overhead, -// I think it's useful to have comments attached to the saved config file to explain behaviour of -// particular fields. -// Note: any changes to the template must be reflected in the appropriate structs. -pub(crate) const CONFIG_TEMPLATE: &str = r#" -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -##### main base client config options ##### - -[core.client] -# Version of the client for which this configuration was created. -version = '{{ core.client.version }}' - -# Human readable ID of this particular client. -id = '{{ core.client.id }}' - -# Indicates whether this client is running in a disabled credentials mode, thus attempting -# to claim bandwidth without presenting bandwidth credentials. -disabled_credentials_mode = {{ core.client.disabled_credentials_mode }} - -# Addresses to nyxd validators via which the client can communicate with the chain. -nyxd_urls = [ - {{#each core.client.nyxd_urls }} - '{{this}}', - {{/each}} -] - -# Addresses to APIs running on validator from which the client gets the view of the network. -nym_api_urls = [ - {{#each core.client.nym_api_urls }} - '{{this}}', - {{/each}} -] - -[storage_paths] - -# Path to file containing private identity key. -keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' - -# Path to file containing public identity key. -keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' - -# Path to file containing private encryption key. -keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}' - -# Path to file containing public encryption key. -keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}' - -# Path to file containing key used for encrypting and decrypting the content of an -# acknowledgement so that nobody besides the client knows which packet it refers to. -keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}' - -# Path to the database containing bandwidth credentials -credentials_database = '{{ storage_paths.credentials_database }}' - -# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database = '{{ storage_paths.reply_surb_database }}' - -# Path to the file containing information about gateways used by this client, -# i.e. details such as their public keys, owner addresses or the network information. -gateway_registrations = '{{ storage_paths.gateway_registrations }}' - -##### socket config options ##### - -[core.socks5] - -# The mix address of the provider to which all requests are going to be sent. -provider_mix_address = '{{ core.socks5.provider_mix_address }}' - -# The address on which the client will be listening for incoming requests -# (default: 127.0.0.1:1080) -bind_address = '{{ core.socks5.bind_address }}' - -# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. -# While this is going to hide its actual address information, it will make the actual communication -# slower and consume nearly double the bandwidth as it will require sending reply SURBs. -# -# Note that some service providers might not support this. -send_anonymously = {{ core.socks5.send_anonymously }} - -##### logging configuration options ##### - -[logging] - -# TODO - - -##### debug configuration options ##### -# The following options should not be modified unless you know EXACTLY what you are doing -# as if set incorrectly, they may impact your anonymity. - -# [core.socks5.socks5_debug] - - -[core.debug] - -[core.debug.traffic] -average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}' -message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}' - -[core.debug.acknowledgements] -average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}' - -[core.debug.cover_traffic] -loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}' - -"#; diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs deleted file mode 100644 index 3019402182..0000000000 --- a/sdk/lib/socks5-listener/src/lib.rs +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::{config_filepath_from_root, Config}; -use crate::persistence::MobileClientStorage; -use ::safer_ffi::prelude::*; -use anyhow::{anyhow, Result}; -use lazy_static::lazy_static; -use log::{debug, info, warn}; -use nym_bin_common::logging::setup_logging; -use nym_client_core::init::helpers::current_gateways; -use nym_client_core::init::types::{GatewaySelectionSpecification, GatewaySetup}; -use nym_config_common::defaults::setup_env; -use nym_socks5_client_core::NymClient as Socks5NymClient; -use rand::rngs::OsRng; -use safer_ffi::char_p::char_p_boxed; -use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::time::Duration; -use tokio::runtime::Runtime; -use tokio::sync::{Mutex, Notify}; -use tokio::time::{sleep, Instant}; - -#[cfg(target_os = "android")] -pub mod android; -mod config; -mod persistence; - -static SOCKS5_CONFIG_ID: &str = "mobile-socks5-test"; -const ANDROID_HEALTHCHECK_INTERVAL: Duration = Duration::from_secs(5); -const HEALTHCHECK_TIMEOUT: Duration = Duration::from_secs(10); - -// hehe, this is so disgusting : ) -lazy_static! { - static ref CLIENT_SHUTDOWN_HANDLE: Mutex>> = Mutex::new(None); - static ref RUNTIME: Runtime = Runtime::new().unwrap(); - static ref LAST_HEALTHCHECK_PING: Mutex> = Mutex::new(None); -} -static ENV_SET: AtomicBool = AtomicBool::new(false); - -async fn set_shutdown_handle(handle: Arc) { - let mut guard = CLIENT_SHUTDOWN_HANDLE.lock().await; - if guard.is_some() { - panic!("client wasn't properly stopped") - } - *guard = Some(handle) -} - -async fn stop_and_reset_shutdown_handle() { - let mut guard = CLIENT_SHUTDOWN_HANDLE.lock().await; - if let Some(sh) = &*guard { - sh.notify_waiters() - } else { - panic!("client wasn't properly started") - } - - *guard = None -} - -async fn is_shutdown_handle_set() -> bool { - CLIENT_SHUTDOWN_HANDLE.lock().await.is_some() -} - -fn set_default_env() { - if !ENV_SET.swap(true, Ordering::SeqCst) { - setup_env::<&str>(None); - } -} - -// to be used with the on startup callback which returns the address -#[ffi_export] -pub fn rust_free_string(string: char_p::Box) { - drop(string) -} - -#[ffi_export] -pub fn initialise_logger() { - setup_logging(); - info!("logger initialised"); -} - -#[derive_ReprC] -#[ffi_export] -#[repr(u8)] -#[derive(Eq, PartialEq, Debug)] -pub enum ClientState { - Uninitialised, - Connected, - Disconnected, -} - -#[ffi_export] -pub fn get_client_state() -> ClientState { - // if the environment is not set, we never called start before - // if the shutdown was never set, the client can't possibly be running - // and similarly if it's set, it's most likely running - if !ENV_SET.load(Ordering::Relaxed) { - ClientState::Uninitialised - } else if RUNTIME.block_on(is_shutdown_handle_set()) { - ClientState::Connected - } else { - ClientState::Disconnected - } -} - -pub fn start_client( - storage_directory: Option>, - service_provider: Option>, - on_start_callback: F, - on_shutdown_callback: S, -) where - F: FnMut(String) + Send + 'static, - S: FnMut() + Send + 'static, -{ - if get_client_state() == ClientState::Connected { - warn!("could not start the client as it's already running"); - return; - } - - let storage_dir = storage_directory.map(|s| s.to_string()); - let service_provider = service_provider.map(|s| s.to_string()); - RUNTIME.spawn(async move { - _async_run_client( - storage_dir, - SOCKS5_CONFIG_ID.to_string(), - service_provider, - on_start_callback, - on_shutdown_callback, - ) - .await - }); -} - -#[ffi_export] -pub fn stop_client() { - if get_client_state() == ClientState::Disconnected { - warn!("could not stop the client as it's not running "); - return; - } - - RUNTIME.block_on(async move { stop_and_reset_shutdown_handle().await }); -} - -#[ffi_export] -pub fn ping_client() { - RUNTIME.spawn(async { - let mut guard = LAST_HEALTHCHECK_PING.lock().await; - *guard = Some(Instant::now()); - }); -} - -// Continusouly poll that we are being pinged from the outside. If the pings stop that means -// that the higher layer somehow terminated without telling us. -pub async fn health_check() { - // init the ping to now - let mut guard = LAST_HEALTHCHECK_PING.lock().await; - *guard = Some(Instant::now()); - // release the mutex - drop(guard); - - loop { - sleep(ANDROID_HEALTHCHECK_INTERVAL).await; - - if !is_shutdown_handle_set().await { - debug!("client has been shutdown, cancelling healthcheck"); - break; - } - let mut guard = LAST_HEALTHCHECK_PING.lock().await; - let Some(last_ping) = *guard else { - warn!("client has not been pinged yet - shutting down"); - *guard = None; - stop_and_reset_shutdown_handle().await; - break; - }; - if last_ping.elapsed() > HEALTHCHECK_TIMEOUT { - warn!( - "client has not been pinged for more than {} seconds - shutting down", - HEALTHCHECK_TIMEOUT.as_secs() - ); - *guard = None; - stop_and_reset_shutdown_handle().await; - break; - } - debug!("✓ android app healthy"); - } -} - -pub fn blocking_run_client<'cb, F, S>( - storage_directory: Option>, - service_provider: Option>, - on_start_callback: F, - on_shutdown_callback: S, -) where - F: FnMut(String) + 'cb, - S: FnMut() + 'cb, -{ - if get_client_state() == ClientState::Connected { - warn!("could not start the client as it's already running"); - return; - } - - // Spawn a task that monitors that we are continuously receiving pings from the outside, - // to make sure we don't end up with a runaway process - RUNTIME.spawn(async { health_check().await }); - - let storage_dir = storage_directory.map(|s| s.to_string()); - let service_provider = service_provider.map(|s| s.to_string()); - RUNTIME - .block_on(async move { - _async_run_client( - storage_dir, - SOCKS5_CONFIG_ID.to_string(), - service_provider, - on_start_callback, - on_shutdown_callback, - ) - .await - }) - .map_err(|err| { - warn!("failed to run client: {}", err); - }) - .ok(); -} - -#[ffi_export] -pub fn reset_client_data(root_directory: char_p::Ref<'_>) { - if get_client_state() == ClientState::Connected { - return; - } - - let root_dir = root_directory.to_string(); - _reset_client_data(root_dir) -} - -#[ffi_export] -pub fn existing_service_provider(storage_directory: char_p::Ref<'_>) -> Option { - if let Ok(config) = Config::read_from_default_path(storage_directory.to_str(), SOCKS5_CONFIG_ID) - { - Some(config.core.socks5.provider_mix_address.try_into().unwrap()) - } else { - None - } -} - -fn _reset_client_data(root_directory: String) { - let client_storage_dir = PathBuf::new().join(root_directory).join(SOCKS5_CONFIG_ID); - std::fs::remove_dir_all(client_storage_dir).expect("failed to clear client data") -} - -async fn _async_run_client( - storage_dir: Option, - client_id: String, - service_provider: Option, - mut on_start_callback: F, - mut on_shutdown_callback: S, -) -> anyhow::Result<()> -where - F: FnMut(String), - S: FnMut(), -{ - let mut rng = OsRng; - - set_default_env(); - let stop_handle = Arc::new(Notify::new()); - set_shutdown_handle(stop_handle.clone()).await; - - let config = load_or_generate_base_config(storage_dir, client_id, service_provider).await?; - let nym_apis = config.core.base.client.nym_api_urls.clone(); - - let storage = MobileClientStorage::new(&config); - let user_agent = nym_bin_common::bin_info!().into(); - let socks5_client = Socks5NymClient::new(config.core, storage, user_agent, None) - .with_gateway_setup(GatewaySetup::New { - specification: GatewaySelectionSpecification::UniformRemote { - must_use_tls: false, - }, - available_gateways: current_gateways(&mut rng, &nym_apis, None, 50).await?, - }); - - eprintln!("starting the socks5 client"); - let started_client = socks5_client.start().await?; - eprintln!("the client has started!"); - - // invoke the callback since we've started! - on_start_callback(started_client.address.to_string()); - - // wait for notify to be set... - stop_handle.notified().await; - - // and then do graceful shutdown of all tasks - let mut task_manager = started_client - .shutdown_handle - .try_into_task_manager() - .unwrap(); - task_manager.signal_shutdown().ok(); - task_manager.wait_for_shutdown().await; - - // and the corresponding one for shutdown! - on_shutdown_callback(); - - Ok(()) -} - -// note: it does might not contain any gateway configuration and should not be persisted in that state! -async fn load_or_generate_base_config( - storage_dir: Option, - client_id: String, - service_provider: Option, -) -> Result { - let Some(storage_dir) = storage_dir else { - eprintln!("no storage path specified"); - return setup_new_client_config(None, client_id, service_provider).await; - }; - - let expected_store_path = config_filepath_from_root(&storage_dir, &client_id); - eprintln!( - "attempting to load socks5 config from {}", - expected_store_path.display() - ); - - // simulator workaround - if let Ok(mut config) = Config::read_from_toml_file(expected_store_path) { - eprintln!("loaded config"); - if let Some(storage_paths) = &mut config.storage_paths { - if !storage_paths - .common_paths - .keys - .public_identity_key_file - .starts_with(&storage_dir) - { - eprintln!("... but it seems to have been made for different container - fixing it up... (ASSUMING DEFAULT PATHS)"); - storage_paths.change_root(storage_dir, &config.core.base.client.id); - } - } - - return Ok(config); - }; - - eprintln!("creating new config"); - setup_new_client_config(Some(storage_dir), client_id, service_provider).await -} - -async fn setup_new_client_config( - storage_dir: Option, - client_id: String, - service_provider: Option, -) -> Result { - let service_provider = service_provider.ok_or(anyhow!( - "service provider was not specified for fresh config" - ))?; - - let mut new_config = Config::new(storage_dir.as_ref(), client_id, service_provider); - - if let Ok(raw_validators) = std::env::var(nym_config_common::defaults::var_names::NYM_API) { - new_config - .core - .base - .set_custom_nym_apis(nym_config_common::parse_urls(&raw_validators)); - } - - if let Some(_storage_paths) = &new_config.storage_paths { - println!("persistent storage is not implemented"); - }; - - if let Some(storage_dir) = storage_dir { - new_config.save_to_default_location(storage_dir)?; - } - - Ok(new_config) -} - -#[cfg(feature = "headers")] // c.f. the `Cargo.toml` section -pub fn generate_headers() -> ::std::io::Result<()> { - ::safer_ffi::headers::builder() - .to_file("socks5_c.h")? - .generate() -} diff --git a/sdk/lib/socks5-listener/src/persistence.rs b/sdk/lib/socks5-listener/src/persistence.rs deleted file mode 100644 index 77a903144a..0000000000 --- a/sdk/lib/socks5-listener/src/persistence.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::Config; -use nym_client_core::client::base_client::storage::{InMemGatewaysDetails, MixnetClientStorage}; -use nym_client_core::client::key_manager::persistence::InMemEphemeralKeys; -use nym_client_core::client::replies::reply_storage; -use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; - -pub struct MobileClientStorage { - // the key storage is now useless without gateway details store. so use ephemeral for everything. - key_store: InMemEphemeralKeys, - gateway_details_store: InMemGatewaysDetails, - - reply_store: reply_storage::Empty, - credential_store: EphemeralCredentialStorage, -} - -impl MixnetClientStorage for MobileClientStorage { - type KeyStore = InMemEphemeralKeys; - type ReplyStore = reply_storage::Empty; - type CredentialStore = EphemeralCredentialStorage; - type GatewaysDetailsStore = InMemGatewaysDetails; - - fn into_runtime_stores( - self, - ) -> ( - Self::ReplyStore, - Self::CredentialStore, - Self::GatewaysDetailsStore, - ) { - ( - self.reply_store, - self.credential_store, - self.gateway_details_store, - ) - } - - fn key_store(&self) -> &Self::KeyStore { - &self.key_store - } - - fn reply_store(&self) -> &Self::ReplyStore { - &self.reply_store - } - - fn credential_store(&self) -> &Self::CredentialStore { - &self.credential_store - } - - fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore { - &self.gateway_details_store - } -} - -impl MobileClientStorage { - pub fn new(_config: &Config) -> Self { - MobileClientStorage { - key_store: Default::default(), - gateway_details_store: Default::default(), - reply_store: Default::default(), - credential_store: Default::default(), - } - } -} From 07435ce3b25f1d3e0dc7d5f5212b2fd24e8dd561 Mon Sep 17 00:00:00 2001 From: windy-ux <75579979+windy-ux@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:09:49 +0100 Subject: [PATCH 50/80] Fix/web 615 seo setup (#5257) * + add header into Packet Mixing docs * + add head changes for testing * / updated version of metatags in theme.config * + add env file * / theme.config to use NEXT_PUBLIC_SITE_URL from env file * @ Fix broken link in theme.config * - remove favicon code * + add desription for intro pages --- documentation/docs/.env.example | 1 + documentation/docs/.gitignore | 4 ++ .../docs/pages/apis/introduction.mdx | 4 ++ documentation/docs/pages/developers/index.mdx | 4 ++ .../docs/pages/network/concepts/mixing.mdx | 5 +++ documentation/docs/pages/network/index.md | 4 ++ .../docs/pages/operators/introduction.mdx | 4 ++ documentation/docs/theme.config.tsx | 42 ++++++++++++++++--- 8 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 documentation/docs/.env.example diff --git a/documentation/docs/.env.example b/documentation/docs/.env.example new file mode 100644 index 0000000000..2ed3461d68 --- /dev/null +++ b/documentation/docs/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_SITE_URL=http://localhost:3000 \ No newline at end of file diff --git a/documentation/docs/.gitignore b/documentation/docs/.gitignore index 46f9bd0984..515d7fce8d 100644 --- a/documentation/docs/.gitignore +++ b/documentation/docs/.gitignore @@ -4,3 +4,7 @@ out # the lock file will break Vercel because it may get committed from a machine with a different build architecture package-lock.json + +# local env files +.env*.local +.env \ No newline at end of file diff --git a/documentation/docs/pages/apis/introduction.mdx b/documentation/docs/pages/apis/introduction.mdx index c2c521ba8c..03d4998584 100644 --- a/documentation/docs/pages/apis/introduction.mdx +++ b/documentation/docs/pages/apis/introduction.mdx @@ -1,3 +1,7 @@ +--- +description: Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. +--- + # Introduction This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. diff --git a/documentation/docs/pages/developers/index.mdx b/documentation/docs/pages/developers/index.mdx index 9f27706c0d..a05d61780a 100644 --- a/documentation/docs/pages/developers/index.mdx +++ b/documentation/docs/pages/developers/index.mdx @@ -1,3 +1,7 @@ +--- +description: Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs. +--- + # Introduction Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs. diff --git a/documentation/docs/pages/network/concepts/mixing.mdx b/documentation/docs/pages/network/concepts/mixing.mdx index 387480a8b7..7027182d14 100644 --- a/documentation/docs/pages/network/concepts/mixing.mdx +++ b/documentation/docs/pages/network/concepts/mixing.mdx @@ -1,3 +1,8 @@ +--- +title: Packet Mixing +description: Mixnets are networks of nodes that route traffic in a way that makes it untraceable +--- + # Packet Mixing > Continuous-time mixing strategies ... delay each message independently, forwarding it to its next destination once a specified delay has timed out. The aggregate effect of independently delaying each message is an output sequence of messages that is randomly reordered with respect to the input sequence. diff --git a/documentation/docs/pages/network/index.md b/documentation/docs/pages/network/index.md index 2fc3a1a583..8ee579d0dc 100644 --- a/documentation/docs/pages/network/index.md +++ b/documentation/docs/pages/network/index.md @@ -1,3 +1,7 @@ +--- +description: Nym's network documentation covering network architecture, node types, tokenomics, and cryptography. +--- + # Introduction Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems. diff --git a/documentation/docs/pages/operators/introduction.mdx b/documentation/docs/pages/operators/introduction.mdx index 82afe0998f..0261f8fa07 100644 --- a/documentation/docs/pages/operators/introduction.mdx +++ b/documentation/docs/pages/operators/introduction.mdx @@ -1,3 +1,7 @@ +--- +description: Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators. +--- + # Introduction This is **Nym's Operators guide**, containing information and setup guides for the various components of Nym network and Nyx blockchain validators. diff --git a/documentation/docs/theme.config.tsx b/documentation/docs/theme.config.tsx index 2b85bd88bd..acb9e39c4c 100644 --- a/documentation/docs/theme.config.tsx +++ b/documentation/docs/theme.config.tsx @@ -1,14 +1,46 @@ import React from "react"; -import { DocsThemeConfig } from "nextra-theme-docs"; +import { DocsThemeConfig, useConfig } from "nextra-theme-docs"; import { Footer } from "./components/footer"; import { Matrix } from "./components/matrix-link"; import { useRouter } from "next/router"; const config: DocsThemeConfig = { - useNextSeoProps() { - return { - titleTemplate: "%s – Nym Docs", - }; + head: function useHead() { + const config = useConfig() + const { route } = useRouter() + const url = process.env.NEXT_PUBLIC_SITE_URL + const image = url + '/nym_logo.jpg' + + const description = + config.frontMatter.description || + 'Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.' + const title = config.title + (route === '/' ? '' : ' - Nym docs') + + return ( + <> + {title} + + + + + + + + + + + + + + + + + + + + + + ) }, logo: Nym Docs, project: { From e7702a1e7a4aac4339ae8156f4f40a0bdddf45e4 Mon Sep 17 00:00:00 2001 From: Fran Arbanas Date: Fri, 13 Dec 2024 14:44:36 +0100 Subject: [PATCH 51/80] fix: remove documentation from dockerignore since it's refernced in Cargo.toml (#5264) --- .dockerignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 09ed129139..a11022eb2b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,3 @@ **/node_modules **/target dist -documentation From c0b4e8dd70598e36d999baaacea4ae9a9fb97ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 16 Dec 2024 09:15:46 +0100 Subject: [PATCH 52/80] Remove unneeded async function annotation (#5246) --- common/http-api-client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 824f4fa278..19826bd9cc 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -282,7 +282,7 @@ impl Client { } } - pub async fn create_delete_request( + pub fn create_delete_request( &self, path: PathSegments<'_>, params: Params<'_, K, V>, From 04c2045d944d718bc31672ebb43bf3fcb3ebfa74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 16 Dec 2024 12:28:44 +0100 Subject: [PATCH 53/80] Add PATCH support to nym-http-api-client (#5260) --- common/http-api-client/src/lib.rs | 103 ++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 19826bd9cc..c826fe295b 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -324,6 +324,56 @@ impl Client { } } + pub fn create_patch_request( + &self, + path: PathSegments<'_>, + params: Params<'_, K, V>, + json_body: &B, + ) -> RequestBuilder + where + B: Serialize + ?Sized, + K: AsRef, + V: AsRef, + { + let url = sanitize_url(&self.base_url, path, params); + self.reqwest_client.patch(url).json(json_body) + } + + pub async fn send_patch_request( + &self, + path: PathSegments<'_>, + params: Params<'_, K, V>, + json_body: &B, + ) -> Result> + where + B: Serialize + ?Sized, + K: AsRef, + V: AsRef, + E: Display, + { + let url = sanitize_url(&self.base_url, path, params); + + #[cfg(target_arch = "wasm32")] + { + Ok(wasmtimer::tokio::timeout( + self.request_timeout, + self.reqwest_client.patch(url).json(json_body).send(), + ) + .await + .map_err(|_timeout| HttpClientError::RequestTimeout)??) + } + + #[cfg(not(target_arch = "wasm32"))] + { + Ok(self + .reqwest_client + .patch(url) + .json(json_body) + .send() + .await?) + } + } + #[instrument(level = "debug", skip_all)] pub async fn get_json( &self, @@ -372,6 +422,23 @@ impl Client { parse_response(res, false).await } + pub async fn patch_json( + &self, + path: PathSegments<'_>, + params: Params<'_, K, V>, + json_body: &B, + ) -> Result> + where + B: Serialize + ?Sized, + for<'a> T: Deserialize<'a>, + K: AsRef, + V: AsRef, + E: Display + DeserializeOwned, + { + let res = self.send_patch_request(path, params, json_body).await?; + parse_response(res, true).await + } + #[instrument(level = "debug", skip_all)] pub async fn get_json_endpoint(&self, endpoint: S) -> Result> where @@ -466,6 +533,42 @@ impl Client { parse_response(res, false).await } + + pub async fn patch_json_endpoint( + &self, + endpoint: S, + json_body: &B, + ) -> Result> + where + B: Serialize + ?Sized, + for<'a> T: Deserialize<'a>, + E: Display + DeserializeOwned, + S: AsRef, + { + #[cfg(target_arch = "wasm32")] + let res = { + wasmtimer::tokio::timeout( + self.request_timeout, + self.reqwest_client + .patch(self.base_url.join(endpoint.as_ref())?) + .json(json_body) + .send(), + ) + .await + .map_err(|_timeout| HttpClientError::RequestTimeout)?? + }; + + #[cfg(not(target_arch = "wasm32"))] + let res = { + self.reqwest_client + .patch(self.base_url.join(endpoint.as_ref())?) + .json(json_body) + .send() + .await? + }; + + parse_response(res, true).await + } } // define those methods on the trait for nicer extensions (and not having to type the thing twice) From 88d6fb4e22a378c27bbe673cbe2819bde0f32958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 16 Dec 2024 13:57:34 +0200 Subject: [PATCH 54/80] Add fd callback to client core (#5230) * Add fd callback to client core * Include in sdk * Fix clippy many args * Method in builder * Replace Box with Arc --- .../client-core/src/client/base_client/mod.rs | 23 ++++++++++++++++ .../gateway-client/src/client/mod.rs | 17 ++++++++++++ nym-api/src/network_monitor/monitor/sender.rs | 1 + sdk/rust/nym-sdk/src/mixnet/client.rs | 27 +++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 856ddd9497..839df3ec90 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -188,6 +188,9 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> { user_agent: Option, setup_method: GatewaySetup, + + #[cfg(unix)] + connection_fd_callback: Option>, } impl<'a, C, S> BaseClientBuilder<'a, C, S> @@ -210,6 +213,8 @@ where shutdown: None, user_agent: None, setup_method: GatewaySetup::MustLoad { gateway_id: None }, + #[cfg(unix)] + connection_fd_callback: None, } } @@ -261,6 +266,15 @@ where Ok(self) } + #[cfg(unix)] + pub fn with_connection_fd_callback( + mut self, + callback: Arc, + ) -> Self { + self.connection_fd_callback = Some(callback); + self + } + // note: do **NOT** make this method public as its only valid usage is from within `start_base` // because it relies on the crypto keys being already loaded fn mix_address(details: &InitialisationResult) -> Recipient { @@ -352,6 +366,7 @@ where controller.start_with_shutdown(shutdown) } + #[allow(clippy::too_many_arguments)] async fn start_gateway_client( config: &Config, initialisation_result: InitialisationResult, @@ -359,6 +374,7 @@ where details_store: &S::GatewaysDetailsStore, packet_router: PacketRouter, stats_reporter: ClientStatsSender, + #[cfg(unix)] connection_fd_callback: Option>, shutdown: TaskClient, ) -> Result, ClientCoreError> where @@ -401,6 +417,8 @@ where packet_router, bandwidth_controller, stats_reporter, + #[cfg(unix)] + connection_fd_callback, shutdown, ) }; @@ -462,6 +480,7 @@ where details_store: &S::GatewaysDetailsStore, packet_router: PacketRouter, stats_reporter: ClientStatsSender, + #[cfg(unix)] connection_fd_callback: Option>, mut shutdown: TaskClient, ) -> Result, ClientCoreError> where @@ -493,6 +512,8 @@ where details_store, packet_router, stats_reporter, + #[cfg(unix)] + connection_fd_callback, shutdown, ) .await?; @@ -772,6 +793,8 @@ where &details_store, gateway_packet_router, stats_reporter.clone(), + #[cfg(unix)] + self.connection_fd_callback, shutdown.fork("gateway_transceiver"), ) .await?; diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 6cb7b83f02..481e57dd6f 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -101,6 +101,10 @@ pub struct GatewayClient { // currently unused (but populated) negotiated_protocol: Option, + // Callback on the fd as soon as the connection has been established + #[cfg(unix)] + connection_fd_callback: Option>, + /// Listen to shutdown messages and send notifications back to the task manager task_client: TaskClient, } @@ -116,6 +120,7 @@ impl GatewayClient { packet_router: PacketRouter, bandwidth_controller: Option>, stats_reporter: ClientStatsSender, + #[cfg(unix)] connection_fd_callback: Option>, task_client: TaskClient, ) -> Self { GatewayClient { @@ -131,6 +136,8 @@ impl GatewayClient { bandwidth_controller, stats_reporter, negotiated_protocol: None, + #[cfg(unix)] + connection_fd_callback, task_client, } } @@ -205,6 +212,12 @@ impl GatewayClient { }; self.connection = SocketState::Available(Box::new(ws_stream)); + + #[cfg(unix)] + if let (Some(callback), Some(fd)) = (self.connection_fd_callback.as_ref(), self.ws_fd()) { + callback.as_ref()(fd); + } + Ok(()) } @@ -1034,6 +1047,8 @@ impl GatewayClient { bandwidth_controller: None, stats_reporter: ClientStatsSender::new(None), negotiated_protocol: None, + #[cfg(unix)] + connection_fd_callback: None, task_client, } } @@ -1064,6 +1079,8 @@ impl GatewayClient { bandwidth_controller, stats_reporter, negotiated_protocol: self.negotiated_protocol, + #[cfg(unix)] + connection_fd_callback: self.connection_fd_callback, task_client, } } diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index c1140a55ac..172d6b3680 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -183,6 +183,7 @@ impl PacketSender { gateway_packet_router, Some(fresh_gateway_client_data.bandwidth_controller.clone()), nym_statistics_common::clients::ClientStatsSender::new(None), + None, task_client, ); diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 118885a650..f223037572 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -36,6 +36,7 @@ use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient, UserAgent}; use rand::rngs::OsRng; use std::path::Path; use std::path::PathBuf; +use std::sync::Arc; use url::Url; use zeroize::Zeroizing; @@ -54,6 +55,7 @@ pub struct MixnetClientBuilder { custom_shutdown: Option, force_tls: bool, user_agent: Option, + connection_fd_callback: Option>, // TODO: incorporate it properly into `MixnetClientStorage` (I will need it in wasm anyway) gateway_endpoint_config_path: Option, @@ -93,6 +95,8 @@ impl MixnetClientBuilder { custom_gateway_transceiver: None, force_tls: false, user_agent: None, + #[cfg(unix)] + connection_fd_callback: None, }) } } @@ -120,6 +124,8 @@ where custom_shutdown: None, force_tls: false, user_agent: None, + #[cfg(unix)] + connection_fd_callback: None, gateway_endpoint_config_path: None, storage, } @@ -138,6 +144,8 @@ where custom_shutdown: self.custom_shutdown, force_tls: self.force_tls, user_agent: self.user_agent, + #[cfg(unix)] + connection_fd_callback: self.connection_fd_callback, gateway_endpoint_config_path: self.gateway_endpoint_config_path, storage, } @@ -237,6 +245,15 @@ where self } + #[must_use] + pub fn with_connection_fd_callback( + mut self, + connection_fd_callback: Arc, + ) -> Self { + self.connection_fd_callback = Some(connection_fd_callback); + self + } + /// Use custom mixnet sender that might not be the default websocket gateway connection. /// only for advanced use #[must_use] @@ -265,6 +282,7 @@ where client.wait_for_gateway = self.wait_for_gateway; client.force_tls = self.force_tls; client.user_agent = self.user_agent; + client.connection_fd_callback = self.connection_fd_callback; Ok(client) } @@ -314,6 +332,9 @@ where custom_shutdown: Option, user_agent: Option, + + /// Callback on the websocket fd as soon as the connection has been established + connection_fd_callback: Option>, } impl DisconnectedMixnetClient @@ -363,6 +384,7 @@ where force_tls: false, custom_shutdown: None, user_agent: None, + connection_fd_callback: None, }) } @@ -604,6 +626,11 @@ where base_builder = base_builder.with_gateway_transceiver(gateway_transceiver); } + #[cfg(unix)] + if let Some(connection_fd_callback) = self.connection_fd_callback { + base_builder = base_builder.with_connection_fd_callback(connection_fd_callback); + } + let started_client = base_builder.start_base().await?; self.state = BuilderState::Registered {}; let nym_address = started_client.address; From 0a712b9fce75c5f4bf1c95f0f8ee49a6aa919bcc Mon Sep 17 00:00:00 2001 From: windy-ux <75579979+windy-ux@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:17:25 +0100 Subject: [PATCH 55/80] Fix/web 615 seo setup (#5265) * + add header into Packet Mixing docs * + add head changes for testing * / updated version of metatags in theme.config * + add env file * / theme.config to use NEXT_PUBLIC_SITE_URL from env file * @ Fix broken link in theme.config * - remove favicon code * + add desription for intro pages * + add default book's desriptions * Revert "+ add desription for intro pages" This reverts commit 98c78242d4e7eb51d91e7a178319dab16b1daab8. --- documentation/docs/pages/apis/introduction.mdx | 4 ---- documentation/docs/pages/developers/index.mdx | 4 ---- documentation/docs/pages/network/index.md | 4 ---- .../docs/pages/operators/introduction.mdx | 4 ---- documentation/docs/theme.config.tsx | 17 +++++++++++++++-- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/documentation/docs/pages/apis/introduction.mdx b/documentation/docs/pages/apis/introduction.mdx index 03d4998584..c2c521ba8c 100644 --- a/documentation/docs/pages/apis/introduction.mdx +++ b/documentation/docs/pages/apis/introduction.mdx @@ -1,7 +1,3 @@ ---- -description: Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. ---- - # Introduction This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. diff --git a/documentation/docs/pages/developers/index.mdx b/documentation/docs/pages/developers/index.mdx index a05d61780a..9f27706c0d 100644 --- a/documentation/docs/pages/developers/index.mdx +++ b/documentation/docs/pages/developers/index.mdx @@ -1,7 +1,3 @@ ---- -description: Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs. ---- - # Introduction Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs. diff --git a/documentation/docs/pages/network/index.md b/documentation/docs/pages/network/index.md index 8ee579d0dc..2fc3a1a583 100644 --- a/documentation/docs/pages/network/index.md +++ b/documentation/docs/pages/network/index.md @@ -1,7 +1,3 @@ ---- -description: Nym's network documentation covering network architecture, node types, tokenomics, and cryptography. ---- - # Introduction Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems. diff --git a/documentation/docs/pages/operators/introduction.mdx b/documentation/docs/pages/operators/introduction.mdx index 0261f8fa07..82afe0998f 100644 --- a/documentation/docs/pages/operators/introduction.mdx +++ b/documentation/docs/pages/operators/introduction.mdx @@ -1,7 +1,3 @@ ---- -description: Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators. ---- - # Introduction This is **Nym's Operators guide**, containing information and setup guides for the various components of Nym network and Nyx blockchain validators. diff --git a/documentation/docs/theme.config.tsx b/documentation/docs/theme.config.tsx index acb9e39c4c..1ad62048a3 100644 --- a/documentation/docs/theme.config.tsx +++ b/documentation/docs/theme.config.tsx @@ -11,10 +11,23 @@ const config: DocsThemeConfig = { const url = process.env.NEXT_PUBLIC_SITE_URL const image = url + '/nym_logo.jpg' + // Define descriptions for different "books" + const bookDescriptions: Record = { + '/developers': "Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.", + '/network': "Nym's network documentation covering network architecture, node types, tokenomics, and cryptography.", + '/operators': "Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators.", + '/apis': "Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet." + } + + const defaultDescription = 'Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.' + + const topLevel = '/' + route.split('/')[1] const description = config.frontMatter.description || - 'Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.' - const title = config.title + (route === '/' ? '' : ' - Nym docs') + bookDescriptions[topLevel] || + defaultDescription + + const title = config.title + (route === '/' ? '' : ' - Nym docs') return ( <> From be063a36eb02f4eb50a6a3abdc57b62850e75553 Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:17:38 +0000 Subject: [PATCH 56/80] syntax hotfix (#5266) --- .../docs/pages/operators/tokenomics/mixnet-rewards.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx index fb1225af15..b329a227c0 100644 --- a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx +++ b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx @@ -222,7 +222,7 @@ Good performance is much more essential than [total stake](../tokenomics.mdx#sta For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1 for simplification):
    - + > node_1 = 1.00 ^ 20 \* 1.0 = 1
    > node_2 = 1.00 ^ 20 \* 0.5 = 0.5
    > node_3 = 0.99 ^ 20 \* 1.0 = 0.818
    From 84d7004cb2cba6ff5c83af8322061b3f30637664 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 16 Dec 2024 15:18:04 +0100 Subject: [PATCH 57/80] Add control messages to GatewayTransciver (#5247) * Add control messages to GatewayTransciver * Add forget me flag to clients * CI gate IPIINFO test * Handle ForgetMe for client and stats db * fmt --- .gitignore | 3 +- Cargo.lock | 1 + .../client-core/src/client/base_client/mod.rs | 17 ++++++- .../client-core/src/client/mix_traffic/mod.rs | 34 +++++++++++-- .../src/client/mix_traffic/transceiver.rs | 47 +++++++++++++++++- common/client-core/src/lib.rs | 45 +++++++++++++++++ .../gateway-client/src/client/mod.rs | 2 +- .../src/types/text_request.rs | 4 ++ .../src/types/text_response.rs | 1 + common/gateway-stats-storage/src/lib.rs | 10 ++++ common/gateway-stats-storage/src/sessions.rs | 10 ++++ ...0c61aa0ddd30fee2565803b88c6086bd2a734.json | 12 +++++ ...5f9e8d251a3e764104d2a54153895dee1a118.json | 12 +++++ common/gateway-storage/src/bandwidth.rs | 10 ++++ common/gateway-storage/src/inboxes.rs | 13 +++++ common/gateway-storage/src/lib.rs | 42 ++++++++++++++++ common/statistics/src/gateways.rs | 8 ++++ .../client_handling/websocket/common_state.rs | 6 +++ .../connection_handler/authenticated.rs | 23 +++++++++ .../websocket/connection_handler/fresh.rs | 4 ++ nym-network-monitor/Cargo.toml | 1 + nym-network-monitor/src/main.rs | 8 +++- .../src/monitor/geodata.rs | 48 +++++++++++-------- .../node/metrics/handler/client_sessions.rs | 14 ++++++ sdk/rust/nym-sdk/src/mixnet/client.rs | 19 +++++++- 25 files changed, 363 insertions(+), 31 deletions(-) create mode 100644 common/gateway-storage/.sqlx/query-3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734.json create mode 100644 common/gateway-storage/.sqlx/query-a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118.json diff --git a/.gitignore b/.gitignore index 597794c0e3..8953d2a2da 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ ppa-private-key.b64 ppa-private-key.asc nym-network-monitor/topology.json nym-network-monitor/__pycache__ -nym-network-monitor/*.key \ No newline at end of file +nym-network-monitor/*.key +nym-network-monitor/.envrc diff --git a/Cargo.lock b/Cargo.lock index c7c85e0d9e..2fd2fbf19c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5782,6 +5782,7 @@ dependencies = [ "nym-bin-common", "nym-client-core", "nym-crypto", + "nym-gateway-requests", "nym-network-defaults", "nym-sdk", "nym-sphinx", diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 839df3ec90..bb0ffd5d03 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -32,7 +32,7 @@ use crate::init::{ setup_gateway, types::{GatewaySetup, InitialisationResult}, }; -use crate::{config, spawn_future}; +use crate::{config, spawn_future, ForgetMe}; use futures::channel::mpsc; use log::*; use nym_bandwidth_controller::BandwidthController; @@ -191,6 +191,8 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> { #[cfg(unix)] connection_fd_callback: Option>, + + forget_me: ForgetMe, } impl<'a, C, S> BaseClientBuilder<'a, C, S> @@ -215,9 +217,16 @@ where setup_method: GatewaySetup::MustLoad { gateway_id: None }, #[cfg(unix)] connection_fd_callback: None, + forget_me: Default::default(), } } + #[must_use] + pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self { + self.forget_me = forget_me.clone(); + self + } + #[must_use] pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self { self.setup_method = setup; @@ -636,9 +645,11 @@ where fn start_mix_traffic_controller( gateway_transceiver: Box, shutdown: TaskClient, + forget_me: ForgetMe, ) -> BatchMixMessageSender { info!("Starting mix traffic controller..."); - let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_transceiver); + let (mix_traffic_controller, mix_tx) = + MixTrafficController::new(gateway_transceiver, forget_me); mix_traffic_controller.start_with_shutdown(shutdown); mix_tx } @@ -820,9 +831,11 @@ where // that are to be sent to the mixnet. They are used by cover traffic stream and real // traffic stream. // The MixTrafficController then sends the actual traffic + let message_sender = Self::start_mix_traffic_controller( gateway_transceiver, shutdown.fork("mix_traffic_controller"), + self.forget_me, ); // Channels that the websocket listener can use to signal downstream to the real traffic diff --git a/common/client-core/src/client/mix_traffic/mod.rs b/common/client-core/src/client/mix_traffic/mod.rs index 91c652efba..fa73cc82a8 100644 --- a/common/client-core/src/client/mix_traffic/mod.rs +++ b/common/client-core/src/client/mix_traffic/mod.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::mix_traffic::transceiver::GatewayTransceiver; -use crate::spawn_future; +use crate::{spawn_future, ForgetMe}; use log::*; +use nym_gateway_requests::ClientRequest; use nym_sphinx::forwarding::packet::MixPacket; pub type BatchMixMessageSender = tokio::sync::mpsc::Sender>; @@ -26,10 +27,14 @@ pub struct MixTrafficController { // TODO: this is temporary work-around. // in long run `gateway_client` will be moved away from `MixTrafficController` anyway. consecutive_gateway_failure_count: usize, + forget_me: ForgetMe, } impl MixTrafficController { - pub fn new(gateway_transceiver: T) -> (MixTrafficController, BatchMixMessageSender) + pub fn new( + gateway_transceiver: T, + forget_me: ForgetMe, + ) -> (MixTrafficController, BatchMixMessageSender) where T: GatewayTransceiver + Send + 'static, { @@ -40,6 +45,7 @@ impl MixTrafficController { gateway_transceiver: Box::new(gateway_transceiver), mix_rx: message_receiver, consecutive_gateway_failure_count: 0, + forget_me, }, message_sender, ) @@ -47,6 +53,7 @@ impl MixTrafficController { pub fn new_dynamic( gateway_transceiver: Box, + forget_me: ForgetMe, ) -> (MixTrafficController, BatchMixMessageSender) { let (message_sender, message_receiver) = tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE); @@ -55,6 +62,7 @@ impl MixTrafficController { gateway_transceiver, mix_rx: message_receiver, consecutive_gateway_failure_count: 0, + forget_me, }, message_sender, ) @@ -111,7 +119,27 @@ impl MixTrafficController { } } shutdown.recv_timeout().await; + + if self.forget_me.any() { + log::info!("Sending forget me request to the gateway"); + match self + .gateway_transceiver + .send_client_request(ClientRequest::ForgetMe { + client: self.forget_me.client(), + stats: self.forget_me.stats(), + }) + .await + { + Ok(_) => { + log::info!("Successfully sent forget me request to the gateway"); + } + Err(err) => { + log::error!("Failed to send forget me request to the gateway: {err}"); + } + } + } + log::debug!("MixTrafficController: Exiting"); - }) + }); } } diff --git a/common/client-core/src/client/mix_traffic/transceiver.rs b/common/client-core/src/client/mix_traffic/transceiver.rs index 6d9b4fa6de..77c226d245 100644 --- a/common/client-core/src/client/mix_traffic/transceiver.rs +++ b/common/client-core/src/client/mix_traffic/transceiver.rs @@ -5,8 +5,10 @@ use async_trait::async_trait; use log::{debug, error}; use nym_credential_storage::storage::Storage as CredentialStorage; use nym_crypto::asymmetric::identity; +use nym_gateway_client::error::GatewayClientError; use nym_gateway_client::GatewayClient; pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter}; +use nym_gateway_requests::ClientRequest; use nym_sphinx::forwarding::packet::MixPacket; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use std::fmt::Debug; @@ -26,9 +28,14 @@ fn erase_err(err: E) -> ErasedGate } /// This combines combines the functionalities of being able to send and receive mix packets. +#[async_trait] pub trait GatewayTransceiver: GatewaySender + GatewayReceiver { fn gateway_identity(&self) -> identity::PublicKey; fn ws_fd(&self) -> Option; + async fn send_client_request( + &mut self, + message: ClientRequest, + ) -> Result<(), GatewayClientError>; } /// This trait defines the functionality of sending `MixPacket` into the mixnet, @@ -65,6 +72,7 @@ pub trait GatewayReceiver { } // to allow for dynamic dispatch +#[async_trait] impl GatewayTransceiver for Box { #[inline] fn gateway_identity(&self) -> identity::PublicKey { @@ -73,6 +81,13 @@ impl GatewayTransceiver for Box { fn ws_fd(&self) -> Option { (**self).ws_fd() } + + async fn send_client_request( + &mut self, + message: ClientRequest, + ) -> Result<(), GatewayClientError> { + (**self).send_client_request(message).await + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -91,7 +106,6 @@ impl GatewaySender for Box { (**self).batch_send_mix_packets(packets).await } } - impl GatewayReceiver for Box { #[inline] fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> { @@ -111,6 +125,7 @@ impl RemoteGateway { } } +#[async_trait] impl GatewayTransceiver for RemoteGateway where C: DkgQueryClient + Send + Sync, @@ -123,6 +138,20 @@ where fn ws_fd(&self) -> Option { self.gateway_client.ws_fd() } + + async fn send_client_request( + &mut self, + message: ClientRequest, + ) -> Result<(), GatewayClientError> { + if let Some(shared_key) = self.gateway_client.shared_key() { + self.gateway_client + .send_websocket_message(message.encrypt(&*shared_key)?) + .await?; + Ok(()) + } else { + Err(GatewayClientError::ConnectionInInvalidState) + } + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -195,6 +224,7 @@ impl LocalGateway { mod nonwasm_sealed { use super::*; + #[async_trait] impl GatewayTransceiver for LocalGateway { fn gateway_identity(&self) -> identity::PublicKey { self.local_identity @@ -202,6 +232,13 @@ mod nonwasm_sealed { fn ws_fd(&self) -> Option { None } + + async fn send_client_request( + &mut self, + _message: ClientRequest, + ) -> Result<(), GatewayClientError> { + Ok(()) + } } #[async_trait] @@ -269,6 +306,7 @@ impl GatewaySender for MockGateway { } } +#[async_trait] impl GatewayTransceiver for MockGateway { fn gateway_identity(&self) -> identity::PublicKey { self.dummy_identity @@ -276,4 +314,11 @@ impl GatewayTransceiver for MockGateway { fn ws_fd(&self) -> Option { None } + + async fn send_client_request( + &mut self, + _message: ClientRequest, + ) -> Result<(), GatewayClientError> { + Ok(()) + } } diff --git a/common/client-core/src/lib.rs b/common/client-core/src/lib.rs index ffa1402859..12ea3f7d5c 100644 --- a/common/client-core/src/lib.rs +++ b/common/client-core/src/lib.rs @@ -34,3 +34,48 @@ where { tokio::spawn(future); } + +#[derive(Clone, Default, Debug)] +pub struct ForgetMe { + client: bool, + stats: bool, +} + +impl ForgetMe { + pub fn new_all() -> Self { + Self { + client: true, + stats: true, + } + } + + pub fn new_client() -> Self { + Self { + client: true, + stats: false, + } + } + + pub fn new_stats() -> Self { + Self { + client: false, + stats: true, + } + } + + pub fn new(client: bool, stats: bool) -> Self { + Self { client, stats } + } + + pub fn any(&self) -> bool { + self.client || self.stats + } + + pub fn client(&self) -> bool { + self.client + } + + pub fn stats(&self) -> bool { + self.stats + } +} diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 481e57dd6f..26986e3df8 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -324,7 +324,7 @@ impl GatewayClient { // If we want to send a message (with response), we need to have a full control over the socket, // as we need to be able to write the request and read the subsequent response - async fn send_websocket_message( + pub async fn send_websocket_message( &mut self, msg: impl Into, ) -> Result { diff --git a/common/gateway-requests/src/types/text_request.rs b/common/gateway-requests/src/types/text_request.rs index 8be56bcfb3..72eebc3e17 100644 --- a/common/gateway-requests/src/types/text_request.rs +++ b/common/gateway-requests/src/types/text_request.rs @@ -20,6 +20,10 @@ pub enum ClientRequest { hkdf_salt: Vec, derived_key_digest: Vec, }, + ForgetMe { + client: bool, + stats: bool, + }, } impl ClientRequest { diff --git a/common/gateway-requests/src/types/text_response.rs b/common/gateway-requests/src/types/text_response.rs index b0c8250f1e..5c6ce668b5 100644 --- a/common/gateway-requests/src/types/text_response.rs +++ b/common/gateway-requests/src/types/text_response.rs @@ -11,6 +11,7 @@ use tungstenite::Message; #[non_exhaustive] pub enum SensitiveServerResponse { KeyUpgradeAck {}, + ForgetMeAck {}, } impl SensitiveServerResponse { diff --git a/common/gateway-stats-storage/src/lib.rs b/common/gateway-stats-storage/src/lib.rs index 74b45e9e7a..e57f7452cd 100644 --- a/common/gateway-stats-storage/src/lib.rs +++ b/common/gateway-stats-storage/src/lib.rs @@ -116,6 +116,16 @@ impl PersistentStatsStorage { .await?) } + pub async fn delete_unique_user( + &self, + client_address: DestinationAddressBytes, + ) -> Result<(), StatsStorageError> { + Ok(self + .session_manager + .delete_unique_user(client_address.as_base58_string()) + .await?) + } + pub async fn insert_active_session( &self, client_address: DestinationAddressBytes, diff --git a/common/gateway-stats-storage/src/sessions.rs b/common/gateway-stats-storage/src/sessions.rs index 920ee4d1d5..a919696967 100644 --- a/common/gateway-stats-storage/src/sessions.rs +++ b/common/gateway-stats-storage/src/sessions.rs @@ -71,6 +71,16 @@ impl SessionManager { Ok(()) } + pub(crate) async fn delete_unique_user(&self, client_address_b58: String) -> Result<()> { + sqlx::query!( + "DELETE FROM sessions_unique_users WHERE client_address = ?", + client_address_b58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + pub(crate) async fn get_unique_users(&self, date: Date) -> Result> { sqlx::query_scalar!( "SELECT client_address as count FROM sessions_unique_users WHERE day = ?", diff --git a/common/gateway-storage/.sqlx/query-3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734.json b/common/gateway-storage/.sqlx/query-3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734.json new file mode 100644 index 0000000000..8c8994d4ca --- /dev/null +++ b/common/gateway-storage/.sqlx/query-3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM message_store WHERE client_address_bs58 = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734" +} diff --git a/common/gateway-storage/.sqlx/query-a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118.json b/common/gateway-storage/.sqlx/query-a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118.json new file mode 100644 index 0000000000..bc58f0aa1b --- /dev/null +++ b/common/gateway-storage/.sqlx/query-a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM available_bandwidth WHERE client_id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118" +} diff --git a/common/gateway-storage/src/bandwidth.rs b/common/gateway-storage/src/bandwidth.rs index 1f1cdce1c8..be64357350 100644 --- a/common/gateway-storage/src/bandwidth.rs +++ b/common/gateway-storage/src/bandwidth.rs @@ -49,6 +49,16 @@ impl BandwidthManager { Ok(()) } + pub(crate) async fn remove_client(&self, client_id: i64) -> Result<(), sqlx::Error> { + sqlx::query!( + "DELETE FROM available_bandwidth WHERE client_id = ?", + client_id + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + /// Set the expiration date of the particular client to the provided date. pub(crate) async fn set_expiration( &self, diff --git a/common/gateway-storage/src/inboxes.rs b/common/gateway-storage/src/inboxes.rs index 6c1ac23c47..bdf798aa02 100644 --- a/common/gateway-storage/src/inboxes.rs +++ b/common/gateway-storage/src/inboxes.rs @@ -133,4 +133,17 @@ impl InboxManager { .await?; Ok(()) } + + pub(crate) async fn remove_messages_for_client( + &self, + client_address_bs58: &str, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "DELETE FROM message_store WHERE client_address_bs58 = ?", + client_address_bs58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } } diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 7b0f70a5b3..9d574dbe91 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -41,6 +41,33 @@ pub struct GatewayStorage { } impl GatewayStorage { + #[allow(dead_code)] + pub(crate) fn client_manager(&self) -> &ClientManager { + &self.client_manager + } + + pub(crate) fn shared_key_manager(&self) -> &SharedKeysManager { + &self.shared_key_manager + } + + pub(crate) fn inbox_manager(&self) -> &InboxManager { + &self.inbox_manager + } + + pub(crate) fn bandwidth_manager(&self) -> &BandwidthManager { + &self.bandwidth_manager + } + + #[allow(dead_code)] + pub(crate) fn ticket_manager(&self) -> &TicketStorageManager { + &self.ticket_manager + } + + #[allow(dead_code)] + pub(crate) fn wireguard_peer_manager(&self) -> &wireguard_peers::WgPeerManager { + &self.wireguard_peer_manager + } + /// Initialises `PersistentStorage` using the provided path. /// /// # Arguments @@ -101,6 +128,21 @@ impl GatewayStorage { .await?) } + pub async fn handle_forget_me( + &self, + client_address: DestinationAddressBytes, + ) -> Result<(), GatewayStorageError> { + let client_id = self.get_mixnet_client_id(client_address).await?; + self.inbox_manager() + .remove_messages_for_client(&client_address.as_base58_string()) + .await?; + self.bandwidth_manager().remove_client(client_id).await?; + self.shared_key_manager() + .remove_shared_keys(&client_address.as_base58_string()) + .await?; + Ok(()) + } + pub async fn insert_shared_keys( &self, client_address: DestinationAddressBytes, diff --git a/common/statistics/src/gateways.rs b/common/statistics/src/gateways.rs index cc617ae027..4e8e701de1 100644 --- a/common/statistics/src/gateways.rs +++ b/common/statistics/src/gateways.rs @@ -58,6 +58,10 @@ pub enum GatewaySessionEvent { /// Address of the remote client opening the connection client: DestinationAddressBytes, }, + SessionDelete { + /// Address of the remote client opening the connection + client: DestinationAddressBytes, + }, } impl GatewaySessionEvent { @@ -87,4 +91,8 @@ impl GatewaySessionEvent { client, } } + + pub fn new_session_delete(client: DestinationAddressBytes) -> GatewaySessionEvent { + GatewaySessionEvent::SessionDelete { client } + } } diff --git a/gateway/src/node/client_handling/websocket/common_state.rs b/gateway/src/node/client_handling/websocket/common_state.rs index 4ff8b8991a..19a5943c53 100644 --- a/gateway/src/node/client_handling/websocket/common_state.rs +++ b/gateway/src/node/client_handling/websocket/common_state.rs @@ -21,3 +21,9 @@ pub(crate) struct CommonHandlerState { pub(crate) outbound_mix_sender: MixForwardingSender, pub(crate) active_clients_store: ActiveClientsStore, } + +impl CommonHandlerState { + pub(crate) fn storage(&self) -> &GatewayStorage { + &self.storage + } +} diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index d7bc9dd32e..6c39b613ce 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -157,6 +157,10 @@ impl Drop for AuthenticatedHandler { } impl AuthenticatedHandler { + pub(crate) fn inner(&self) -> &FreshHandler { + &self.inner + } + /// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated /// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or /// sending sphinx packets. @@ -327,6 +331,24 @@ impl AuthenticatedHandler { } } + async fn handle_forget_me( + &mut self, + client: bool, + stats: bool, + ) -> Result { + if client { + self.inner() + .shared_state() + .storage() + .handle_forget_me(self.client.address) + .await?; + } + if stats { + self.send_metrics(GatewaySessionEvent::new_session_delete(self.client.address)); + } + Ok(SensitiveServerResponse::ForgetMeAck {}.encrypt(&self.client.shared_keys)?) + } + async fn handle_key_upgrade( &mut self, hkdf_salt: Vec, @@ -370,6 +392,7 @@ impl AuthenticatedHandler { hkdf_salt, derived_key_digest, } => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await, + ClientRequest::ForgetMe { client, stats } => self.handle_forget_me(client, stats).await, _ => Err(RequestHandlingError::UnknownEncryptedTextRequest), } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 188699c64c..818aea8fdb 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -114,6 +114,10 @@ pub(crate) struct FreshHandler { } impl FreshHandler { + pub(crate) fn shared_state(&self) -> &CommonHandlerState { + &self.shared_state + } + // for time being we assume handle is always constructed from raw socket. // if we decide we want to change it, that's not too difficult // also at this point I'm not entirely sure how to deal with this warning without diff --git a/nym-network-monitor/Cargo.toml b/nym-network-monitor/Cargo.toml index 3d80fed9a0..bd537d5483 100644 --- a/nym-network-monitor/Cargo.toml +++ b/nym-network-monitor/Cargo.toml @@ -33,6 +33,7 @@ nym-bin-common = { path = "../common/bin-common" } nym-client-core = { path = "../common/client-core" } nym-crypto = { path = "../common/crypto" } nym-network-defaults = { path = "../common/network-defaults" } +nym-gateway-requests = { path = "../common/gateway-requests" } nym-sdk = { path = "../sdk/rust/nym-sdk" } nym-sphinx = { path = "../common/nymsphinx" } nym-topology = { path = "../common/topology" } diff --git a/nym-network-monitor/src/main.rs b/nym-network-monitor/src/main.rs index 141944eb8a..76ec69440d 100644 --- a/nym-network-monitor/src/main.rs +++ b/nym-network-monitor/src/main.rs @@ -3,6 +3,7 @@ use accounting::submit_metrics; use anyhow::Result; use clap::Parser; use log::{info, warn}; +use nym_client_core::ForgetMe; use nym_crypto::asymmetric::ed25519::PrivateKey; use nym_network_defaults::setup_env; use nym_network_defaults::var_names::NYM_API; @@ -56,7 +57,11 @@ async fn make_clients( loop { if Arc::strong_count(&dropped_client) == 1 { if let Some(client) = Arc::into_inner(dropped_client) { - client.into_inner().disconnect().await; + // let forget_me = ClientRequest::ForgetMe { + // also_from_stats: true, + // }; + let client_handle = client.into_inner(); + client_handle.disconnect().await; } else { warn!("Failed to drop client, client had more then one strong ref") } @@ -89,6 +94,7 @@ async fn make_client(topology: NymTopology) -> Result { .network_details(net) .custom_topology_provider(topology_provider) .debug_config(mixnet_debug_config(0)) + .with_forget_me(ForgetMe::new_all()) // .enable_credentials_mode() .build()?; diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs index 369acee1a7..8841d3f505 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs @@ -154,31 +154,39 @@ mod api_regression { use super::*; use std::{env::var, sync::LazyLock}; - static IPINFO_TOKEN: LazyLock = LazyLock::new(|| var("IPINFO_API_TOKEN").unwrap()); + static IPINFO_TOKEN: LazyLock> = LazyLock::new(|| var("IPINFO_API_TOKEN").ok()); + static CI: LazyLock> = LazyLock::new(|| var("CI").ok()); #[tokio::test] async fn should_parse_response() { - let client = IpInfoClient::new(&(*IPINFO_TOKEN)); - let my_ip = reqwest::get("https://api.ipify.org") - .await - .expect("Couldn't get own IP") - .text() - .await - .unwrap(); + if CI.is_none() { + return; + } + if let Some(token) = &*IPINFO_TOKEN { + let client = IpInfoClient::new(token); + let my_ip = reqwest::get("https://api.ipify.org") + .await + .expect("Couldn't get own IP") + .text() + .await + .unwrap(); - let location_result = client.locate_ip(my_ip).await; - assert!(location_result.is_ok(), "Did ipinfo response change?"); + let location_result = client.locate_ip(my_ip).await; + assert!(location_result.is_ok(), "Did ipinfo response change?"); - assert!( - client.check_remaining_bandwidth().await.is_ok(), - "Failed to check remaining bandwidth?" - ); + assert!( + client.check_remaining_bandwidth().await.is_ok(), + "Failed to check remaining bandwidth?" + ); - // when serialized, these fields should be present because they're exposed over API - let location_result = location_result.unwrap(); - let json = serde_json::to_value(&location_result).unwrap(); - assert!(json.get("two_letter_iso_country_code").is_some()); - assert!(json.get("latitude").is_some()); - assert!(json.get("longitude").is_some()); + // when serialized, these fields should be present because they're exposed over API + let location_result = location_result.unwrap(); + let json = serde_json::to_value(&location_result).unwrap(); + assert!(json.get("two_letter_iso_country_code").is_some()); + assert!(json.get("latitude").is_some()); + assert!(json.get("longitude").is_some()); + } else { + panic!("IPINFO_API_TOKEN not set"); + } } } diff --git a/nym-node/src/node/metrics/handler/client_sessions.rs b/nym-node/src/node/metrics/handler/client_sessions.rs index 5f9e870a2a..8394eb1d89 100644 --- a/nym-node/src/node/metrics/handler/client_sessions.rs +++ b/nym-node/src/node/metrics/handler/client_sessions.rs @@ -73,6 +73,15 @@ impl GatewaySessionStatsHandler { Ok(()) } + async fn handle_session_delete( + &mut self, + client: DestinationAddressBytes, + ) -> Result<(), StatsStorageError> { + self.storage.delete_active_session(client).await?; + self.storage.delete_unique_user(client).await?; + Ok(()) + } + async fn handle_session_event( &mut self, event: GatewaySessionEvent, @@ -90,6 +99,11 @@ impl GatewaySessionStatsHandler { ticket_type, client, } => self.handle_ecash_ticket(ticket_type, client).await, + + // As long as delete is sent before stop, everything should work as expected + GatewaySessionEvent::SessionDelete { client } => { + self.handle_session_delete(client).await + } } } diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index f223037572..598bbd4808 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -28,6 +28,7 @@ use nym_client_core::error::ClientCoreError; use nym_client_core::init::helpers::current_gateways; use nym_client_core::init::setup_gateway; use nym_client_core::init::types::{GatewaySelectionSpecification, GatewaySetup}; +use nym_client_core::ForgetMe; use nym_credentials_interface::TicketType; use nym_socks5_client_core::config::Socks5; use nym_task::{TaskClient, TaskHandle, TaskStatus}; @@ -61,6 +62,7 @@ pub struct MixnetClientBuilder { gateway_endpoint_config_path: Option, storage: S, + forget_me: ForgetMe, } impl MixnetClientBuilder { @@ -97,6 +99,7 @@ impl MixnetClientBuilder { user_agent: None, #[cfg(unix)] connection_fd_callback: None, + forget_me: Default::default(), }) } } @@ -128,6 +131,7 @@ where connection_fd_callback: None, gateway_endpoint_config_path: None, storage, + forget_me: Default::default(), } } @@ -148,6 +152,7 @@ where connection_fd_callback: self.connection_fd_callback, gateway_endpoint_config_path: self.gateway_endpoint_config_path, storage, + forget_me: self.forget_me, } } @@ -160,6 +165,12 @@ where self.set_storage(storage) } + #[must_use] + pub fn with_forget_me(mut self, forget_me: ForgetMe) -> Self { + self.forget_me = forget_me; + self + } + /// Request a specific gateway instead of a random one. #[must_use] pub fn request_gateway(mut self, user_chosen_gateway: String) -> Self { @@ -283,7 +294,7 @@ where client.force_tls = self.force_tls; client.user_agent = self.user_agent; client.connection_fd_callback = self.connection_fd_callback; - + client.forget_me = self.forget_me; Ok(client) } } @@ -335,6 +346,8 @@ where /// Callback on the websocket fd as soon as the connection has been established connection_fd_callback: Option>, + + forget_me: ForgetMe, } impl DisconnectedMixnetClient @@ -385,6 +398,7 @@ where custom_shutdown: None, user_agent: None, connection_fd_callback: None, + forget_me: Default::default(), }) } @@ -608,7 +622,8 @@ where let mut base_builder: BaseClientBuilder<_, _> = BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client) - .with_wait_for_gateway(self.wait_for_gateway); + .with_wait_for_gateway(self.wait_for_gateway) + .with_forget_me(&self.forget_me); if let Some(user_agent) = self.user_agent { base_builder = base_builder.with_user_agent(user_agent); From a3f3d83c1b43fba94514f4c145895d394c322a67 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 16 Dec 2024 16:19:37 +0100 Subject: [PATCH 58/80] Shipping raw metrics to PG (#5216) * Shipping raw metrics to PG * Put cancel token back in its place * fmt --- .gitignore | 1 + Cargo.lock | 81 +++++++++++++ nym-network-monitor/Cargo.toml | 1 + nym-network-monitor/entrypoint.sh | 5 +- nym-network-monitor/src/accounting.rs | 165 +++++++++++++++++++------- nym-network-monitor/src/main.rs | 33 +++++- 6 files changed, 236 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 8953d2a2da..817ddac4f2 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ nym-network-monitor/topology.json nym-network-monitor/__pycache__ nym-network-monitor/*.key nym-network-monitor/.envrc +nym-network-monitor/.envrc diff --git a/Cargo.lock b/Cargo.lock index 2fd2fbf19c..d84ab9916c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2475,6 +2475,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fancy_constructor" version = "1.2.2" @@ -5796,6 +5802,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tokio-postgres", "tokio-util", "utoipa", "utoipa-swagger-ui", @@ -7251,6 +7258,24 @@ dependencies = [ "indexmap 2.2.6", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher 0.3.11", +] + [[package]] name = "pin-project" version = "1.1.6" @@ -7389,6 +7414,35 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +[[package]] +name = "postgres-protocol" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2 0.10.8", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -9723,6 +9777,32 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tokio-postgres" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -10913,6 +10993,7 @@ checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ "redox_syscall 0.5.1", "wasite", + "web-sys", ] [[package]] diff --git a/nym-network-monitor/Cargo.toml b/nym-network-monitor/Cargo.toml index bd537d5483..a6def79278 100644 --- a/nym-network-monitor/Cargo.toml +++ b/nym-network-monitor/Cargo.toml @@ -27,6 +27,7 @@ tokio = { workspace = true, features = ["macros", "time"] } tokio-util = { workspace = true } utoipa = { workspace = true, features = ["axum_extras"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } +tokio-postgres = "0.7" # internal nym-bin-common = { path = "../common/bin-common" } diff --git a/nym-network-monitor/entrypoint.sh b/nym-network-monitor/entrypoint.sh index 91a0830639..62b527944d 100755 --- a/nym-network-monitor/entrypoint.sh +++ b/nym-network-monitor/entrypoint.sh @@ -9,13 +9,14 @@ network=${NYM_NETWORK:-mainnet} timeout=${LOCUST_TIMEOUT:-600} users=${LOCUST_USERS:-10} processes=${LOCUST_PROCESSES:-4} +_database_url=${DATABASE_URL} -RUST_LOG=info nym-network-monitor --env envs/"${network}".env --private-key "${_private_key}" & +RUST_LOG=info nym-network-monitor --env envs/"${network}".env --private-key "${_private_key}" --database-url "${_database_url}" & nnm_pid=$! sleep 10 -python -m locust -H http://${NYM_NETWORK_MONITOR_HOST}:${NYM_NETWORK_MONITOR_PORT} --processes "${processes}" --autostart --autoquit 60 -u "${users}" -t "${timeout}"s & +python -m locust -H http://"${NYM_NETWORK_MONITOR_HOST}":"${NYM_NETWORK_MONITOR_PORT}" --processes "${processes}" --autostart --autoquit 60 -u "${users}" -t "${timeout}"s & locust_pid=$! wait $locust_pid diff --git a/nym-network-monitor/src/accounting.rs b/nym-network-monitor/src/accounting.rs index b87f795762..2fcacee75a 100644 --- a/nym-network-monitor/src/accounting.rs +++ b/nym-network-monitor/src/accounting.rs @@ -1,7 +1,10 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use anyhow::Result; -use futures::{stream::FuturesUnordered, StreamExt}; +use futures::{pin_mut, stream::FuturesUnordered, StreamExt}; use log::{debug, info}; use nym_sphinx::chunking::{monitoring, SentFragment}; use nym_topology::{gateway, mix, NymTopology}; @@ -10,6 +13,7 @@ use nym_validator_client::nym_api::routes::{API_VERSION, STATUS, SUBMIT_GATEWAY, use rand::SeedableRng; use rand_chacha::ChaCha8Rng; use serde::{Deserialize, Serialize}; +use tokio_postgres::{binary_copy::BinaryCopyInWriter, types::Type, Client}; use utoipa::ToSchema; use crate::{NYM_API_URL, PRIVATE_KEY, TOPOLOGY}; @@ -23,20 +27,20 @@ struct HydratedRoute { struct GatewayStats(u32, u32); impl GatewayStats { - fn new(sent: u32, recv: u32) -> Self { - GatewayStats(sent, recv) + fn new(success: u32, failure: u32) -> Self { + GatewayStats(success, failure) } fn success(&self) -> u32 { self.0 } - fn failed(&self) -> u32 { + fn failure(&self) -> u32 { self.1 } fn reliability(&self) -> f64 { - self.success() as f64 / (self.success() + self.failed()) as f64 + self.success() as f64 / (self.success() + self.failure()) as f64 } fn incr_success(&mut self) { @@ -321,48 +325,125 @@ pub async fn monitor_mixnode_results() -> anyhow::Result> { .collect()) } -pub async fn submit_metrics() -> anyhow::Result<()> { - let node_stats = monitor_mixnode_results().await?; - let gateway_stats = monitor_gateway_results().await?; +async fn submit_node_stats_to_db(client: Arc) -> anyhow::Result<()> { + let client = Arc::clone(&client); + let node_stats = all_node_stats().await?; - info!("Submitting metrics to {}", *NYM_API_URL); - let client = reqwest::Client::new(); + let sink = client + .copy_in("COPY node_stats (node_id, identity, reliability, complete_routes, incomplete_routes) FROM STDIN BINARY") + .await?; - let node_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", &*NYM_API_URL); - let gateway_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}", &*NYM_API_URL); + let writer = BinaryCopyInWriter::new( + sink, + &[Type::INT4, Type::TEXT, Type::FLOAT8, Type::INT8, Type::INT8], + ); + pin_mut!(writer); - info!("Submitting {} mixnode measurements", node_stats.len()); + for stat in node_stats { + writer + .as_mut() + .write(&[ + &(stat.mix_id as i32), + &stat.identity, + &stat.reliability, + &(stat.complete_routes as i64), + &(stat.incomplete_routes as i64), + ]) + .await?; + } - node_stats - .chunks(10) - .map(|chunk| { - let monitor_message = - MonitorMessage::new(chunk.to_vec(), PRIVATE_KEY.get().expect("We've set this!")); - client.post(&node_submit_url).json(&monitor_message).send() - }) - .collect::>() - .collect::>>() - .await - .into_iter() - .collect::, _>>()?; + writer.finish().await?; - info!("Submitting {} gateway measurements", gateway_stats.len()); + Ok(()) +} - gateway_stats - .chunks(10) - .map(|chunk| { - let monitor_message = - MonitorMessage::new(chunk.to_vec(), PRIVATE_KEY.get().expect("We've set this!")); - client - .post(&gateway_submit_url) - .json(&monitor_message) - .send() - }) - .collect::>() - .collect::>>() - .await - .into_iter() - .collect::, _>>()?; +async fn submit_gateway_stats_to_db(client: Arc) -> anyhow::Result<()> { + let client = Arc::clone(&client); + let network_account = NetworkAccount::finalize()?; + let gateway_stats = network_account.gateway_stats; + + let sink = client + .copy_in("COPY gateway_stats (identity, reliability, success, failure) FROM STDIN BINARY") + .await?; + + let writer = BinaryCopyInWriter::new(sink, &[Type::TEXT, Type::FLOAT8, Type::INT8, Type::INT8]); + pin_mut!(writer); + + for (key, stats) in gateway_stats { + writer + .as_mut() + .write(&[ + &key, + &stats.reliability(), + &(stats.success() as i64), + &(stats.failure() as i64), + ]) + .await?; + } + + writer.finish().await?; + + Ok(()) +} + +pub async fn submit_metrics_to_db(client: Arc) -> anyhow::Result<()> { + let client = Arc::clone(&client); + let client2 = Arc::clone(&client); + submit_node_stats_to_db(client).await?; + submit_gateway_stats_to_db(client2).await?; + Ok(()) +} + +pub async fn submit_metrics(client: Option>) -> anyhow::Result<()> { + if let Some(client) = client { + submit_metrics_to_db(client).await?; + } + + if let Some(private_key) = PRIVATE_KEY.get() { + let node_stats = monitor_mixnode_results().await?; + let gateway_stats = monitor_gateway_results().await?; + + info!("Submitting metrics to {}", *NYM_API_URL); + let client = reqwest::Client::new(); + + let node_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", &*NYM_API_URL); + let gateway_submit_url = + format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}", &*NYM_API_URL); + + info!("Submitting {} mixnode measurements", node_stats.len()); + + node_stats + .chunks(10) + .map(|chunk| { + let monitor_message = MonitorMessage::new(chunk.to_vec(), private_key); + client.post(&node_submit_url).json(&monitor_message).send() + }) + .collect::>() + .collect::>>() + .await + .into_iter() + .collect::, _>>()?; + + info!("Submitting {} gateway measurements", gateway_stats.len()); + + gateway_stats + .chunks(10) + .map(|chunk| { + let monitor_message = MonitorMessage::new( + chunk.to_vec(), + PRIVATE_KEY.get().expect("We've set this!"), + ); + client + .post(&gateway_submit_url) + .json(&monitor_message) + .send() + }) + .collect::>() + .collect::>>() + .await + .into_iter() + .collect::, _>>()?; + } NetworkAccount::empty_buffers(); diff --git a/nym-network-monitor/src/main.rs b/nym-network-monitor/src/main.rs index 76ec69440d..89fee6df6f 100644 --- a/nym-network-monitor/src/main.rs +++ b/nym-network-monitor/src/main.rs @@ -2,7 +2,7 @@ use crate::http::HttpServer; use accounting::submit_metrics; use anyhow::Result; use clap::Parser; -use log::{info, warn}; +use log::{error, info, warn}; use nym_client_core::ForgetMe; use nym_crypto::asymmetric::ed25519::PrivateKey; use nym_network_defaults::setup_env; @@ -22,6 +22,7 @@ use std::{ }; use tokio::sync::OnceCell; use tokio::{signal::ctrl_c, sync::RwLock}; +use tokio_postgres::NoTls; use tokio_util::sync::CancellationToken; static NYM_API_URL: LazyLock = LazyLock::new(|| { @@ -136,7 +137,10 @@ struct Args { generate_key_pair: bool, #[arg(long)] - private_key: String, + private_key: Option, + + #[arg(long, env = "DATABASE_URL")] + database_url: Option, } fn generate_key_pair() -> Result<()> { @@ -174,8 +178,10 @@ async fn main() -> Result<()> { std::process::exit(0); } - let pk = PrivateKey::from_base58_string(&args.private_key)?; - PRIVATE_KEY.set(pk).ok(); + if let Some(private_key) = args.private_key { + let pk = PrivateKey::from_base58_string(&private_key)?; + PRIVATE_KEY.set(pk).ok(); + } TOPOLOGY .set(if let Some(topology_file) = args.topology { @@ -203,16 +209,31 @@ async fn main() -> Result<()> { info!("Waiting for message (ctrl-c to exit)"); + let client = if let Some(database_url) = args.database_url { + let (client, connection) = tokio_postgres::connect(&database_url, NoTls).await?; + + tokio::spawn(async move { + if let Err(e) = connection.await { + error!("Postgres connection error: {}", e); + } + }); + + Some(Arc::new(client)) + } else { + None + }; + loop { + let client = client.as_ref().map(Arc::clone); match tokio::time::timeout(Duration::from_secs(600), ctrl_c()).await { Ok(_) => { info!("Received kill signal, shutting down, submitting final batch of metrics"); - submit_metrics().await?; + submit_metrics(client).await?; break; } Err(_) => { info!("Submitting metrics, cleaning metric buffers"); - submit_metrics().await?; + submit_metrics(client).await?; } }; } From b4f51baf94a1b3df2834df1b1fdd364601ad4c80 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:40:02 +0100 Subject: [PATCH 59/80] Change sqlite journal mode to WAL (#5213) * Change sqlite journal mode to WAL * Synchronous mode & auto vacuum * Bump probe git ref to 1.1.0 --- .../src/backend/fs_backend/manager.rs | 12 +++++++++--- .../surb-storage/src/backend/fs_backend/manager.rs | 8 +++++++- .../credential-storage/src/persistent_storage/mod.rs | 8 +++++++- common/gateway-stats-storage/src/lib.rs | 8 +++++++- common/gateway-storage/src/lib.rs | 8 +++++++- common/nyxd-scraper/src/storage/mod.rs | 9 ++++++++- nym-api/src/support/storage/mod.rs | 4 ++++ .../nym-credential-proxy/src/storage/mod.rs | 4 ++++ nym-node-status-api/nym-node-status-agent/run.sh | 4 ++-- .../nym-node-status-api/launch_node_status_api.sh | 4 +++- nym-validator-rewarder/src/rewarder/storage/mod.rs | 4 ++++ .../testnet-manager/src/manager/storage/mod.rs | 8 +++++++- 12 files changed, 69 insertions(+), 12 deletions(-) diff --git a/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs b/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs index 4b32c60936..d2d1943101 100644 --- a/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs +++ b/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs @@ -8,7 +8,10 @@ use crate::{ }, }; use log::{debug, error}; -use sqlx::ConnectOptions; +use sqlx::{ + sqlite::{SqliteAutoVacuum, SqliteSynchronous}, + ConnectOptions, +}; use std::path::Path; #[derive(Debug, Clone)] @@ -30,6 +33,9 @@ impl StorageManager { } let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); @@ -110,7 +116,7 @@ impl StorageManager { ) -> Result<(), sqlx::Error> { sqlx::query!( r#" - INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type) + INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type) VALUES (?, ?, ?) "#, registered_gateway.gateway_id_bs58, @@ -224,7 +230,7 @@ impl StorageManager { ) -> Result<(), sqlx::Error> { sqlx::query!( r#" - INSERT INTO custom_gateway_details(gateway_id_bs58, data) + INSERT INTO custom_gateway_details(gateway_id_bs58, data) VALUES (?, ?) "#, custom.gateway_id_bs58, diff --git a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs index b6adadd3d5..02316ddb7f 100644 --- a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs +++ b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs @@ -9,7 +9,10 @@ use crate::backend::fs_backend::{ }, }; use log::{error, info}; -use sqlx::ConnectOptions; +use sqlx::{ + sqlite::{SqliteAutoVacuum, SqliteSynchronous}, + ConnectOptions, +}; use std::path::Path; #[derive(Debug, Clone)] @@ -31,6 +34,9 @@ impl StorageManager { } let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(fresh) .disable_statement_logging(); diff --git a/common/credential-storage/src/persistent_storage/mod.rs b/common/credential-storage/src/persistent_storage/mod.rs index 80e746b482..e8c9eca5aa 100644 --- a/common/credential-storage/src/persistent_storage/mod.rs +++ b/common/credential-storage/src/persistent_storage/mod.rs @@ -33,7 +33,10 @@ use nym_credentials::{ IssuanceTicketBook, IssuedTicketBook, }; use nym_ecash_time::{ecash_today, Date, EcashTime}; -use sqlx::ConnectOptions; +use sqlx::{ + sqlite::{SqliteAutoVacuum, SqliteSynchronous}, + ConnectOptions, +}; use std::path::Path; use zeroize::Zeroizing; @@ -56,6 +59,9 @@ impl PersistentStorage { ); let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); diff --git a/common/gateway-stats-storage/src/lib.rs b/common/gateway-stats-storage/src/lib.rs index e57f7452cd..5bfc658a93 100644 --- a/common/gateway-stats-storage/src/lib.rs +++ b/common/gateway-stats-storage/src/lib.rs @@ -6,7 +6,10 @@ use models::StoredFinishedSession; use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType}; use nym_sphinx::DestinationAddressBytes; use sessions::SessionManager; -use sqlx::ConnectOptions; +use sqlx::{ + sqlite::{SqliteAutoVacuum, SqliteSynchronous}, + ConnectOptions, +}; use std::path::Path; use time::Date; use tracing::{debug, error}; @@ -36,6 +39,9 @@ impl PersistentStatsStorage { // TODO: we can inject here more stuff based on our gateway global config // struct. Maybe different pool size or timeout intervals? let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 9d574dbe91..d95581f88a 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -12,7 +12,10 @@ use nym_credentials_interface::ClientTicket; use nym_gateway_requests::shared_key::SharedGatewayKey; use nym_sphinx::DestinationAddressBytes; use shared_keys::SharedKeysManager; -use sqlx::ConnectOptions; +use sqlx::{ + sqlite::{SqliteAutoVacuum, SqliteSynchronous}, + ConnectOptions, +}; use std::path::Path; use tickets::TicketStorageManager; use time::OffsetDateTime; @@ -86,6 +89,9 @@ impl GatewayStorage { // TODO: we can inject here more stuff based on our gateway global config // struct. Maybe different pool size or timeout intervals? let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); diff --git a/common/nyxd-scraper/src/storage/mod.rs b/common/nyxd-scraper/src/storage/mod.rs index 8ac0f07775..a3b0550677 100644 --- a/common/nyxd-scraper/src/storage/mod.rs +++ b/common/nyxd-scraper/src/storage/mod.rs @@ -13,7 +13,11 @@ use crate::{ models::{CommitSignature, Validator}, }, }; -use sqlx::{types::time::OffsetDateTime, ConnectOptions, Sqlite, Transaction}; +use sqlx::{ + sqlite::{SqliteAutoVacuum, SqliteSynchronous}, + types::time::OffsetDateTime, + ConnectOptions, Sqlite, Transaction, +}; use std::{fmt::Debug, path::Path}; use tendermint::{ block::{Commit, CommitSig}, @@ -51,6 +55,9 @@ impl ScraperStorage { #[instrument] pub async fn init + Debug>(database_path: P) -> Result { let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); diff --git a/nym-api/src/support/storage/mod.rs b/nym-api/src/support/storage/mod.rs index 72ff9aea1d..dc5e4c2560 100644 --- a/nym-api/src/support/storage/mod.rs +++ b/nym-api/src/support/storage/mod.rs @@ -18,6 +18,7 @@ use crate::support::storage::models::{ use dashmap::DashMap; use nym_mixnet_contract_common::NodeId; use nym_types::monitoring::NodeResult; +use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous}; use sqlx::ConnectOptions; use std::path::Path; use std::sync::Arc; @@ -67,6 +68,9 @@ impl NymApiStorage { // TODO: we can inject here more stuff based on our nym-api global config // struct. Maybe different pool size or timeout intervals? let connect_opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .log_statements(LevelFilter::Trace) diff --git a/nym-credential-proxy/nym-credential-proxy/src/storage/mod.rs b/nym-credential-proxy/nym-credential-proxy/src/storage/mod.rs index 6133d1acbc..5480f96d5b 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/storage/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/storage/mod.rs @@ -16,6 +16,7 @@ use nym_validator_client::ecash::BlindedSignatureResponse; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::ecash_query_client::DepositId; use nym_validator_client::nyxd::Coin; +use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous}; use sqlx::ConnectOptions; use std::fmt::Debug; use std::path::Path; @@ -40,6 +41,9 @@ impl VpnApiStorage { debug!("Attempting to connect to database"); let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .log_statements(LevelFilter::Trace) diff --git a/nym-node-status-api/nym-node-status-agent/run.sh b/nym-node-status-api/nym-node-status-agent/run.sh index 680c87db8b..f3900ec227 100755 --- a/nym-node-status-api/nym-node-status-agent/run.sh +++ b/nym-node-status-api/nym-node-status-agent/run.sh @@ -3,7 +3,7 @@ set -eu export ENVIRONMENT=${ENVIRONMENT:-"sandbox"} -probe_git_ref="nym-vpn-core-v1.0.0-rc.14" +probe_git_ref="nym-vpn-core-v1.1.0" crate_root=$(dirname $(realpath "$0")) monorepo_root=$(realpath "${crate_root}/../..") @@ -54,7 +54,7 @@ function swarm() { echo "All agents completed" } -copy_gw_probe +# copy_gw_probe build_agent swarm $workers diff --git a/nym-node-status-api/nym-node-status-api/launch_node_status_api.sh b/nym-node-status-api/nym-node-status-api/launch_node_status_api.sh index 24b9a68ae8..9735f873d7 100755 --- a/nym-node-status-api/nym-node-status-api/launch_node_status_api.sh +++ b/nym-node-status-api/nym-node-status-api/launch_node_status_api.sh @@ -2,6 +2,7 @@ set -e +user_rust_log_preference=$RUST_LOG export NYM_API_CLIENT_TIMEOUT=60 export EXPLORER_CLIENT_TIMEOUT=60 export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120 @@ -20,7 +21,8 @@ function run_bare() { set -a source "${monorepo_root}/envs/${ENVIRONMENT}.env" set +a - export RUST_LOG=debug + export RUST_LOG=${user_rust_log_preference:-debug} + echo "RUST_LOG=${RUST_LOG}" # --conection-url is provided in build.rs cargo run --package nym-node-status-api diff --git a/nym-validator-rewarder/src/rewarder/storage/mod.rs b/nym-validator-rewarder/src/rewarder/storage/mod.rs index 1d58da9b11..876cb6d98d 100644 --- a/nym-validator-rewarder/src/rewarder/storage/mod.rs +++ b/nym-validator-rewarder/src/rewarder/storage/mod.rs @@ -7,6 +7,7 @@ use crate::{ rewarder::{epoch::Epoch, storage::manager::StorageManager, RewardingResult}, }; use nym_contracts_common::types::NaiveFloat; +use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous}; use sqlx::ConnectOptions; use std::{fmt::Debug, path::Path}; use time::{Date, OffsetDateTime}; @@ -23,6 +24,9 @@ impl RewarderStorage { #[instrument] pub async fn init + Debug>(database_path: P) -> Result { let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); diff --git a/tools/internal/testnet-manager/src/manager/storage/mod.rs b/tools/internal/testnet-manager/src/manager/storage/mod.rs index c61f52070e..7eb3df7cb1 100644 --- a/tools/internal/testnet-manager/src/manager/storage/mod.rs +++ b/tools/internal/testnet-manager/src/manager/storage/mod.rs @@ -10,7 +10,10 @@ use crate::{ storage::manager::StorageManager, }, }; -use sqlx::ConnectOptions; +use sqlx::{ + sqlite::{SqliteAutoVacuum, SqliteSynchronous}, + ConnectOptions, +}; use std::path::Path; use tracing::{error, info}; use url::Url; @@ -39,6 +42,9 @@ impl NetworkManagerStorage { // TODO: we can inject here more stuff based on our nym-api global config // struct. Maybe different pool size or timeout intervals? let opts = sqlx::sqlite::SqliteConnectOptions::new() + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .synchronous(SqliteSynchronous::Normal) + .auto_vacuum(SqliteAutoVacuum::Incremental) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); From caa17d933cf882cd735629d32385e3a98fbb0d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 17 Dec 2024 15:18:11 +0200 Subject: [PATCH 60/80] Add windows to CI builds (#5269) * Add windows to CI builds * Fix win build for node status api * Fix win build for sdk * Fix win build for cred proxy --- .github/workflows/ci-build.yml | 2 +- nym-api/src/network_monitor/monitor/sender.rs | 1 + .../nym-credential-proxy/src/helpers.rs | 101 ++++++++++++- .../nym-credential-proxy/src/main.rs | 136 ++++-------------- .../nym-node-status-api/build.rs | 7 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 10 +- 6 files changed, 145 insertions(+), 112 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 56fd0d15eb..d07f7edcbd 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ arc-ubuntu-20.04, custom-runner-mac-m1 ] + os: [ arc-ubuntu-20.04, custom-windows-11, custom-runner-mac-m1 ] runs-on: ${{ matrix.os }} env: CARGO_TERM_COLOR: always diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index 172d6b3680..c20feadac1 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -183,6 +183,7 @@ impl PacketSender { gateway_packet_router, Some(fresh_gateway_client_data.bandwidth_controller.clone()), nym_statistics_common::clients::ClientStatsSender::new(None), + #[cfg(unix)] None, task_client, ); diff --git a/nym-credential-proxy/nym-credential-proxy/src/helpers.rs b/nym-credential-proxy/nym-credential-proxy/src/helpers.rs index 12ba427a70..8a528ff575 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/helpers.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/helpers.rs @@ -1,8 +1,22 @@ // Copyright 2024 Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_bin_common::bin_info; use time::OffsetDateTime; -use tracing::{debug, info, warn}; +use tokio_util::sync::CancellationToken; +use tracing::{debug, error, info, warn}; + +use crate::{ + cli::Cli, + deposit_maker::DepositMaker, + error::VpnApiError, + http::{ + state::{ApiState, ChainClient}, + HttpServer, + }, + storage::VpnApiStorage, + tasks::StoragePruner, +}; pub struct LockTimer { created: OffsetDateTime, @@ -40,3 +54,88 @@ impl Default for LockTimer { } } } + +pub async fn wait_for_signal() { + use tokio::signal::unix::{signal, SignalKind}; + + // if we fail to setup the signals, we should just blow up + #[allow(clippy::expect_used)] + let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); + #[allow(clippy::expect_used)] + let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel"); + + tokio::select! { + _ = tokio::signal::ctrl_c() => { + info!("Received SIGINT"); + }, + _ = sigterm.recv() => { + info!("Received SIGTERM"); + } + _ = sigquit.recv() => { + info!("Received SIGQUIT"); + } + } +} + +fn build_sha_short() -> &'static str { + let bin_info = bin_info!(); + if bin_info.commit_sha.len() < 7 { + panic!("unavailable build commit sha") + } + + if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" { + error!("the binary hasn't been built correctly. it doesn't have a commit sha information"); + return "unknown"; + } + + &bin_info.commit_sha[..7] +} + +pub(crate) async fn run_api(cli: Cli) -> Result<(), VpnApiError> { + // create the tasks + let bind_address = cli.bind_address(); + + let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?; + let mnemonic = cli.mnemonic; + let auth_token = cli.http_auth_token; + let webhook_cfg = cli.webhook; + let chain_client = ChainClient::new(mnemonic)?; + let cancellation_token = CancellationToken::new(); + + let deposit_maker = DepositMaker::new( + build_sha_short(), + chain_client.clone(), + cli.max_concurrent_deposits, + cancellation_token.clone(), + ); + + let deposit_request_sender = deposit_maker.deposit_request_sender(); + let api_state = ApiState::new( + storage.clone(), + webhook_cfg, + chain_client, + deposit_request_sender, + cancellation_token.clone(), + ) + .await?; + let http_server = HttpServer::new( + bind_address, + api_state.clone(), + auth_token, + cancellation_token.clone(), + ); + let storage_pruner = StoragePruner::new(cancellation_token, storage); + + // spawn all the tasks + api_state.try_spawn(http_server.run_forever()); + api_state.try_spawn(storage_pruner.run_forever()); + api_state.try_spawn(deposit_maker.run_forever()); + + // wait for cancel signal (SIGINT, SIGTERM or SIGQUIT) + wait_for_signal().await; + + // cancel all the tasks and wait for all task to terminate + api_state.cancel_and_wait().await; + + Ok(()) +} diff --git a/nym-credential-proxy/nym-credential-proxy/src/main.rs b/nym-credential-proxy/nym-credential-proxy/src/main.rs index 71dd082b10..1ca9ccf316 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/main.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/main.rs @@ -6,117 +6,30 @@ #![warn(clippy::todo)] #![warn(clippy::dbg_macro)] -use crate::cli::Cli; -use crate::deposit_maker::DepositMaker; -use crate::error::VpnApiError; -use crate::http::state::{ApiState, ChainClient}; -use crate::http::HttpServer; -use crate::storage::VpnApiStorage; -use crate::tasks::StoragePruner; -use clap::Parser; -use nym_bin_common::logging::setup_tracing_logger; -use nym_bin_common::{bin_info, bin_info_owned}; -use nym_network_defaults::setup_env; -use tokio_util::sync::CancellationToken; -use tracing::{error, info, trace}; +cfg_if::cfg_if! { + if #[cfg(unix)] { + use crate::cli::Cli; + use clap::Parser; + use nym_bin_common::bin_info_owned; + use nym_bin_common::logging::setup_tracing_logger; + use nym_network_defaults::setup_env; + use tracing::{info, trace}; -pub mod cli; -pub mod config; -pub mod credentials; -mod deposit_maker; -pub mod error; -pub mod helpers; -pub mod http; -pub mod nym_api_helpers; -pub mod storage; -pub mod tasks; -mod webhook; - -pub async fn wait_for_signal() { - use tokio::signal::unix::{signal, SignalKind}; - - // if we fail to setup the signals, we should just blow up - #[allow(clippy::expect_used)] - let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); - #[allow(clippy::expect_used)] - let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel"); - - tokio::select! { - _ = tokio::signal::ctrl_c() => { - info!("Received SIGINT"); - }, - _ = sigterm.recv() => { - info!("Received SIGTERM"); - } - _ = sigquit.recv() => { - info!("Received SIGQUIT"); - } + pub mod cli; + pub mod config; + pub mod credentials; + mod deposit_maker; + pub mod error; + pub mod helpers; + pub mod http; + pub mod nym_api_helpers; + pub mod storage; + pub mod tasks; + mod webhook; } } -fn build_sha_short() -> &'static str { - let bin_info = bin_info!(); - if bin_info.commit_sha.len() < 7 { - panic!("unavailable build commit sha") - } - - if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" { - error!("the binary hasn't been built correctly. it doesn't have a commit sha information"); - return "unknown"; - } - - &bin_info.commit_sha[..7] -} - -async fn run_api(cli: Cli) -> Result<(), VpnApiError> { - // create the tasks - let bind_address = cli.bind_address(); - - let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?; - let mnemonic = cli.mnemonic; - let auth_token = cli.http_auth_token; - let webhook_cfg = cli.webhook; - let chain_client = ChainClient::new(mnemonic)?; - let cancellation_token = CancellationToken::new(); - - let deposit_maker = DepositMaker::new( - build_sha_short(), - chain_client.clone(), - cli.max_concurrent_deposits, - cancellation_token.clone(), - ); - - let deposit_request_sender = deposit_maker.deposit_request_sender(); - let api_state = ApiState::new( - storage.clone(), - webhook_cfg, - chain_client, - deposit_request_sender, - cancellation_token.clone(), - ) - .await?; - let http_server = HttpServer::new( - bind_address, - api_state.clone(), - auth_token, - cancellation_token.clone(), - ); - let storage_pruner = StoragePruner::new(cancellation_token, storage); - - // spawn all the tasks - api_state.try_spawn(http_server.run_forever()); - api_state.try_spawn(storage_pruner.run_forever()); - api_state.try_spawn(deposit_maker.run_forever()); - - // wait for cancel signal (SIGINT, SIGTERM or SIGQUIT) - wait_for_signal().await; - - // cancel all the tasks and wait for all task to terminate - api_state.cancel_and_wait().await; - - Ok(()) -} - +#[cfg(unix)] #[tokio::main] async fn main() -> anyhow::Result<()> { // std::env::set_var( @@ -134,6 +47,13 @@ async fn main() -> anyhow::Result<()> { let bin_info = bin_info_owned!(); info!("using the following version: {bin_info}"); - run_api(cli).await?; + helpers::run_api(cli).await?; Ok(()) } + +#[cfg(not(unix))] +#[tokio::main] +async fn main() -> anyhow::Result<()> { + eprintln!("This tool is only supported on Unix systems"); + std::process::exit(1) +} diff --git a/nym-node-status-api/nym-node-status-api/build.rs b/nym-node-status-api/nym-node-status-api/build.rs index 025e755088..3a1b933bc4 100644 --- a/nym-node-status-api/nym-node-status-api/build.rs +++ b/nym-node-status-api/nym-node-status-api/build.rs @@ -1,6 +1,8 @@ use anyhow::{anyhow, Result}; use sqlx::{Connection, SqliteConnection}; +#[cfg(target_family = "unix")] use std::fs::Permissions; +#[cfg(target_family = "unix")] use std::os::unix::fs::PermissionsExt; use tokio::{fs::File, io::AsyncWriteExt}; @@ -39,7 +41,10 @@ async fn write_db_path_to_file(out_dir: &str, db_filename: &str) -> anyhow::Resu file.write_all(format!("sqlite3 {}/{}", out_dir, db_filename).as_bytes()) .await?; + #[cfg(target_family = "unix")] file.set_permissions(Permissions::from_mode(0o755)) .await - .map_err(From::from) + .map_err(anyhow::Error::from)?; + + Ok(()) } diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 598bbd4808..249bbd182c 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -37,6 +37,7 @@ use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient, UserAgent}; use rand::rngs::OsRng; use std::path::Path; use std::path::PathBuf; +#[cfg(unix)] use std::sync::Arc; use url::Url; use zeroize::Zeroizing; @@ -56,6 +57,7 @@ pub struct MixnetClientBuilder { custom_shutdown: Option, force_tls: bool, user_agent: Option, + #[cfg(unix)] connection_fd_callback: Option>, // TODO: incorporate it properly into `MixnetClientStorage` (I will need it in wasm anyway) @@ -256,6 +258,7 @@ where self } + #[cfg(unix)] #[must_use] pub fn with_connection_fd_callback( mut self, @@ -293,7 +296,10 @@ where client.wait_for_gateway = self.wait_for_gateway; client.force_tls = self.force_tls; client.user_agent = self.user_agent; - client.connection_fd_callback = self.connection_fd_callback; + #[cfg(unix)] + if self.connection_fd_callback.is_some() { + client.connection_fd_callback = self.connection_fd_callback; + } client.forget_me = self.forget_me; Ok(client) } @@ -345,6 +351,7 @@ where user_agent: Option, /// Callback on the websocket fd as soon as the connection has been established + #[cfg(unix)] connection_fd_callback: Option>, forget_me: ForgetMe, @@ -397,6 +404,7 @@ where force_tls: false, custom_shutdown: None, user_agent: None, + #[cfg(unix)] connection_fd_callback: None, forget_me: Default::default(), }) From acd068e5abd13bc74baf434bdb23ca394392bbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 18 Dec 2024 12:37:16 +0100 Subject: [PATCH 61/80] Add close to credential storage (#5283) * Add close method to credential storage * wip --- common/credential-storage/src/backends/sqlite.rs | 5 +++++ common/credential-storage/src/ephemeral_storage.rs | 4 ++++ common/credential-storage/src/persistent_storage/mod.rs | 4 ++++ common/credential-storage/src/storage.rs | 2 ++ 4 files changed, 15 insertions(+) diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index 9267bbddb3..dec0899064 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -23,6 +23,11 @@ impl SqliteEcashTicketbookManager { SqliteEcashTicketbookManager { connection_pool } } + /// Closes the connection pool. + pub async fn close(&self) { + self.connection_pool.close().await + } + pub(crate) async fn cleanup_expired(&self, deadline: Date) -> Result<(), sqlx::Error> { sqlx::query!( "DELETE FROM ecash_ticketbook WHERE expiration_date <= ?", diff --git a/common/credential-storage/src/ephemeral_storage.rs b/common/credential-storage/src/ephemeral_storage.rs index 91436d4d8c..b6a113f414 100644 --- a/common/credential-storage/src/ephemeral_storage.rs +++ b/common/credential-storage/src/ephemeral_storage.rs @@ -43,6 +43,10 @@ impl Debug for EphemeralStorage { impl Storage for EphemeralStorage { type StorageError = StorageError; + async fn close(&self) { + // nothing to do here + } + async fn cleanup_expired(&self) -> Result<(), Self::StorageError> { self.storage_manager.cleanup_expired().await; Ok(()) diff --git a/common/credential-storage/src/persistent_storage/mod.rs b/common/credential-storage/src/persistent_storage/mod.rs index e8c9eca5aa..32b6f581de 100644 --- a/common/credential-storage/src/persistent_storage/mod.rs +++ b/common/credential-storage/src/persistent_storage/mod.rs @@ -89,6 +89,10 @@ impl PersistentStorage { impl Storage for PersistentStorage { type StorageError = StorageError; + async fn close(&self) { + self.storage_manager.close().await + } + /// remove all expired ticketbooks and expiration date signatures async fn cleanup_expired(&self) -> Result<(), Self::StorageError> { let ecash_yesterday = ecash_today().date().previous_day().unwrap(); diff --git a/common/credential-storage/src/storage.rs b/common/credential-storage/src/storage.rs index 19ddc44e86..4c0602ea85 100644 --- a/common/credential-storage/src/storage.rs +++ b/common/credential-storage/src/storage.rs @@ -22,6 +22,8 @@ use std::error::Error; pub trait Storage: Send + Sync { type StorageError: Error; + async fn close(&self); + /// remove all expired ticketbooks and expiration date signatures async fn cleanup_expired(&self) -> Result<(), Self::StorageError>; From f7a7a8072fb74e53837ae9cb29a7471021549b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Wed, 18 Dec 2024 16:23:18 +0200 Subject: [PATCH 62/80] Move tun constants to network defaults (#5286) (#5287) --- common/network-defaults/src/constants.rs | 11 +++++++++++ service-providers/ip-packet-router/src/constants.rs | 9 --------- .../ip-packet-router/src/ip_packet_router.rs | 13 +++++++------ .../ip-packet-router/src/util/generate_new_ip.rs | 7 ++++--- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/common/network-defaults/src/constants.rs b/common/network-defaults/src/constants.rs index 6723b45e41..e9b70f54e3 100644 --- a/common/network-defaults/src/constants.rs +++ b/common/network-defaults/src/constants.rs @@ -57,3 +57,14 @@ pub mod wireguard { pub const WG_TUN_DEVICE_IP_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1); // fc01::1 pub const WG_TUN_DEVICE_NETMASK_V6: u8 = 112; } + +pub mod mixnet_vpn { + use std::net::{Ipv4Addr, Ipv6Addr}; + + // The interface used to route traffic + pub const NYM_TUN_BASE_NAME: &str = "nymtun"; + pub const NYM_TUN_DEVICE_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1); + pub const NYM_TUN_DEVICE_NETMASK_V4: Ipv4Addr = Ipv4Addr::new(255, 255, 0, 0); + pub const NYM_TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0x1); // fc00::1 + pub const NYM_TUN_DEVICE_NETMASK_V6: &str = "112"; +} diff --git a/service-providers/ip-packet-router/src/constants.rs b/service-providers/ip-packet-router/src/constants.rs index 73e744890d..bfee848df2 100644 --- a/service-providers/ip-packet-router/src/constants.rs +++ b/service-providers/ip-packet-router/src/constants.rs @@ -1,14 +1,5 @@ -use std::net::{Ipv4Addr, Ipv6Addr}; use std::time::Duration; -// The interface used to route traffic -pub const TUN_BASE_NAME: &str = "nymtun"; -pub const TUN_DEVICE_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1); -pub const TUN_DEVICE_NETMASK_V4: Ipv4Addr = Ipv4Addr::new(255, 255, 0, 0); -pub const TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0x1); // fc00::1 - -pub const TUN_DEVICE_NETMASK_V6: &str = "112"; - // We routinely check if any clients needs to be disconnected at this interval pub(crate) const DISCONNECT_TIMER_INTERVAL: Duration = Duration::from_secs(10); diff --git a/service-providers/ip-packet-router/src/ip_packet_router.rs b/service-providers/ip-packet-router/src/ip_packet_router.rs index cff86d6fb0..a0f185cc76 100644 --- a/service-providers/ip-packet-router/src/ip_packet_router.rs +++ b/service-providers/ip-packet-router/src/ip_packet_router.rs @@ -119,7 +119,7 @@ impl IpPacketRouter { log::error!("ip packet router service provider is not yet supported on this platform"); Ok(()) } else { - unimplemented!("service provider is not yet supported on this platform") + todo!("service provider is not yet supported on this platform") } } @@ -145,11 +145,12 @@ impl IpPacketRouter { // Create the TUN device that we interact with the rest of the world with let config = nym_tun::tun_device::TunDeviceConfig { - base_name: crate::constants::TUN_BASE_NAME.to_string(), - ipv4: crate::constants::TUN_DEVICE_ADDRESS_V4, - netmaskv4: crate::constants::TUN_DEVICE_NETMASK_V4, - ipv6: crate::constants::TUN_DEVICE_ADDRESS_V6, - netmaskv6: crate::constants::TUN_DEVICE_NETMASK_V6.to_string(), + base_name: nym_network_defaults::constants::mixnet_vpn::NYM_TUN_BASE_NAME.to_string(), + ipv4: nym_network_defaults::constants::mixnet_vpn::NYM_TUN_DEVICE_ADDRESS_V4, + netmaskv4: nym_network_defaults::constants::mixnet_vpn::NYM_TUN_DEVICE_NETMASK_V4, + ipv6: nym_network_defaults::constants::mixnet_vpn::NYM_TUN_DEVICE_ADDRESS_V6, + netmaskv6: nym_network_defaults::constants::mixnet_vpn::NYM_TUN_DEVICE_NETMASK_V6 + .to_string(), }; let (tun_reader, tun_writer) = tokio::io::split(nym_tun::tun_device::TunDevice::new_device_only(config)?); diff --git a/service-providers/ip-packet-router/src/util/generate_new_ip.rs b/service-providers/ip-packet-router/src/util/generate_new_ip.rs index ea5325f237..2743be3ef6 100644 --- a/service-providers/ip-packet-router/src/util/generate_new_ip.rs +++ b/service-providers/ip-packet-router/src/util/generate_new_ip.rs @@ -1,9 +1,10 @@ use nym_ip_packet_requests::IpPair; +use nym_network_defaults::constants::mixnet_vpn::{ + NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6, +}; use std::net::Ipv6Addr; use std::{collections::HashMap, net::Ipv4Addr}; -use crate::constants::{TUN_DEVICE_ADDRESS_V4, TUN_DEVICE_ADDRESS_V6}; - // Find an available IP address in self.connected_clients // TODO: make this nicer fn generate_random_ips_within_subnet(rng: &mut R) -> IpPair { @@ -36,7 +37,7 @@ pub(crate) fn find_new_ips( let mut rng = rand::thread_rng(); let mut new_ips = generate_random_ips_within_subnet(&mut rng); let mut tries = 0; - let tun_ips = IpPair::new(TUN_DEVICE_ADDRESS_V4, TUN_DEVICE_ADDRESS_V6); + let tun_ips = IpPair::new(NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6); while is_ip_taken( connected_clients_ipv4, From c482350ec6c9c5477893e19446be68b18113b1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 19 Dec 2024 10:49:56 +0000 Subject: [PATCH 63/80] feature: wireguard metrics (#5278) * experimental log * introduce wireguard metrics updates * add wireguard traffic rates to console logger * missing import * changed order of displayed values * expose bytes information via rest endpoint * clippy --- Cargo.lock | 1 + common/wireguard/Cargo.toml | 1 + common/wireguard/src/lib.rs | 2 + common/wireguard/src/peer_controller.rs | 52 +++++++++++++++++++ gateway/src/node/mod.rs | 8 +++ nym-node/nym-node-metrics/src/lib.rs | 3 ++ nym-node/nym-node-metrics/src/wireguard.rs | 44 ++++++++++++++++ .../src/api/v1/metrics/models.rs | 13 +++++ nym-node/nym-node-requests/src/lib.rs | 6 +++ .../node/http/router/api/v1/metrics/mod.rs | 3 ++ .../router/api/v1/metrics/packets_stats.rs | 2 +- .../http/router/api/v1/metrics/wireguard.rs | 40 ++++++++++++++ nym-node/src/node/metrics/console_logger.rs | 24 +++++++++ nym-node/src/node/mod.rs | 1 + 14 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 nym-node/nym-node-metrics/src/wireguard.rs create mode 100644 nym-node/src/node/http/router/api/v1/metrics/wireguard.rs diff --git a/Cargo.lock b/Cargo.lock index d84ab9916c..cc9ed04a83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6807,6 +6807,7 @@ dependencies = [ "nym-crypto", "nym-gateway-storage", "nym-network-defaults", + "nym-node-metrics", "nym-task", "nym-wireguard-types", "thiserror", diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index df333cab82..c999861cd6 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -36,3 +36,4 @@ nym-gateway-storage = { path = "../gateway-storage" } nym-network-defaults = { path = "../network-defaults" } nym-task = { path = "../task" } nym-wireguard-types = { path = "../wireguard-types" } +nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 7b5f190193..f397a3bbea 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -85,6 +85,7 @@ pub struct WireguardData { #[cfg(target_os = "linux")] pub async fn start_wireguard( storage: nym_gateway_storage::GatewayStorage, + metrics: nym_node_metrics::NymNodeMetrics, all_peers: Vec, task_client: nym_task::TaskClient, wireguard_data: WireguardData, @@ -175,6 +176,7 @@ pub async fn start_wireguard( let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api)); let mut controller = PeerController::new( storage, + metrics, wg_api.clone(), host, peer_bandwidth_managers, diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index 5f2cf6399f..b002db761b 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -16,7 +16,9 @@ use nym_credential_verification::{ ClientBandwidth, }; use nym_gateway_storage::GatewayStorage; +use nym_node_metrics::NymNodeMetrics; use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK; +use std::time::{Duration, SystemTime}; use std::{collections::HashMap, sync::Arc}; use tokio::sync::{mpsc, RwLock}; use tokio_stream::{wrappers::IntervalStream, StreamExt}; @@ -65,6 +67,11 @@ pub struct QueryBandwidthControlResponse { pub struct PeerController { storage: GatewayStorage, + + // we have "all" metrics of a node, but they're behind a single Arc pointer, + // so the overhead is minimal + metrics: NymNodeMetrics, + // used to receive commands from individual handles too request_tx: mpsc::Sender, request_rx: mpsc::Receiver, @@ -76,8 +83,10 @@ pub struct PeerController { } impl PeerController { + #[allow(clippy::too_many_arguments)] pub fn new( storage: GatewayStorage, + metrics: NymNodeMetrics, wg_api: Arc, initial_host_information: Host, bw_storage_managers: HashMap, Peer)>, @@ -123,6 +132,7 @@ impl PeerController { request_rx, timeout_check_interval, task_client, + metrics, } } @@ -257,6 +267,46 @@ impl PeerController { })) } + fn update_metrics(&self, new_host: &Host) { + let now = SystemTime::now(); + const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60); + + let total_peers = new_host.peers.len(); + let mut active_peers = 0; + let mut total_rx = 0; + let mut total_tx = 0; + + for peer in new_host.peers.values() { + total_rx += peer.rx_bytes; + total_tx += peer.tx_bytes; + + // if a peer hasn't performed a handshake in last minute, + // I think it's reasonable to assume it's no longer active + let Some(last_handshake) = peer.last_handshake else { + continue; + }; + let Ok(elapsed) = now.duration_since(last_handshake) else { + continue; + }; + if elapsed < ACTIVITY_THRESHOLD { + active_peers += 1; + } + } + + self.metrics.wireguard.update( + // if the conversion fails it means we're running not running on a 64bit system + // and that's a reason enough for this failure. + total_rx.try_into().expect( + "failed to convert bytes from u64 to usize - are you running on non 64bit system?", + ), + total_tx.try_into().expect( + "failed to convert bytes from u64 to usize - are you running on non 64bit system?", + ), + total_peers, + active_peers, + ); + } + pub async fn run(&mut self) { info!("started wireguard peer controller"); loop { @@ -266,6 +316,8 @@ impl PeerController { log::error!("Can't read wireguard kernel data"); continue; }; + self.update_metrics(&host); + *self.host_information.write().await = host; } _ = self.task_client.recv() => { diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 84b1990351..b3b41961a1 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -37,6 +37,7 @@ mod internal_service_providers; pub use client_handling::active_clients::ActiveClientsStore; pub use nym_gateway_stats_storage::PersistentStatsStorage; pub use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage}; +use nym_node_metrics::NymNodeMetrics; pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; #[derive(Debug, Clone)] @@ -81,6 +82,8 @@ pub struct GatewayTasksBuilder { metrics_sender: MetricEventsSender, + metrics: NymNodeMetrics, + mnemonic: Arc>, shutdown: TaskClient, @@ -102,12 +105,14 @@ impl Drop for GatewayTasksBuilder { } impl GatewayTasksBuilder { + #[allow(clippy::too_many_arguments)] pub fn new( config: Config, identity: Arc, storage: GatewayStorage, mix_packet_sender: MixForwardingSender, metrics_sender: MetricEventsSender, + metrics: NymNodeMetrics, mnemonic: Arc>, shutdown: TaskClient, ) -> GatewayTasksBuilder { @@ -121,6 +126,7 @@ impl GatewayTasksBuilder { storage, mix_packet_sender, metrics_sender, + metrics, mnemonic, shutdown, ecash_manager: None, @@ -443,6 +449,7 @@ impl GatewayTasksBuilder { pub async fn try_start_wireguard( &mut self, ) -> Result, Box> { + let _ = self.metrics.clone(); unimplemented!("wireguard is not supported on this platform") } @@ -460,6 +467,7 @@ impl GatewayTasksBuilder { let wg_handle = nym_wireguard::start_wireguard( self.storage.clone(), + self.metrics.clone(), all_peers, self.shutdown.fork("wireguard"), wireguard_data, diff --git a/nym-node/nym-node-metrics/src/lib.rs b/nym-node/nym-node-metrics/src/lib.rs index 58ab0f77f7..57a8c74bb3 100644 --- a/nym-node/nym-node-metrics/src/lib.rs +++ b/nym-node/nym-node-metrics/src/lib.rs @@ -4,6 +4,7 @@ use crate::entry::EntryStats; use crate::mixnet::MixingStats; use crate::network::NetworkStats; +use crate::wireguard::WireguardStats; use std::ops::Deref; use std::sync::Arc; @@ -11,6 +12,7 @@ pub mod entry; pub mod events; pub mod mixnet; pub mod network; +pub mod wireguard; #[derive(Clone, Default)] pub struct NymNodeMetrics { @@ -34,6 +36,7 @@ impl Deref for NymNodeMetrics { pub struct NymNodeMetricsInner { pub mixnet: MixingStats, pub entry: EntryStats, + pub wireguard: WireguardStats, pub network: NetworkStats, } diff --git a/nym-node/nym-node-metrics/src/wireguard.rs b/nym-node/nym-node-metrics/src/wireguard.rs new file mode 100644 index 0000000000..8aee90d34b --- /dev/null +++ b/nym-node/nym-node-metrics/src/wireguard.rs @@ -0,0 +1,44 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[derive(Default)] +pub struct WireguardStats { + bytes_rx: AtomicUsize, + bytes_tx: AtomicUsize, + + total_peers: AtomicUsize, + active_peers: AtomicUsize, +} + +impl WireguardStats { + pub fn bytes_rx(&self) -> usize { + self.bytes_rx.load(Ordering::Relaxed) + } + + pub fn bytes_tx(&self) -> usize { + self.bytes_tx.load(Ordering::Relaxed) + } + + pub fn total_peers(&self) -> usize { + self.total_peers.load(Ordering::Relaxed) + } + + pub fn active_peers(&self) -> usize { + self.active_peers.load(Ordering::Relaxed) + } + + pub fn update( + &self, + bytes_rx: usize, + bytes_tx: usize, + total_peers: usize, + active_peers: usize, + ) { + self.bytes_rx.store(bytes_rx, Ordering::Relaxed); + self.bytes_tx.store(bytes_tx, Ordering::Relaxed); + self.total_peers.store(total_peers, Ordering::Relaxed); + self.active_peers.store(active_peers, Ordering::Relaxed); + } +} diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs index 8e430534c5..0ee4170dfe 100644 --- a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs @@ -4,6 +4,19 @@ pub use mixing::*; pub use session::*; pub use verloc::*; +pub use wireguard::*; + +pub mod wireguard { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Debug, Clone, Copy)] + #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] + pub struct WireguardStats { + pub bytes_tx: usize, + + pub bytes_rx: usize, + } +} pub mod packets { use serde::{Deserialize, Serialize}; diff --git a/nym-node/nym-node-requests/src/lib.rs b/nym-node/nym-node-requests/src/lib.rs index 661936797a..1d7cd01846 100644 --- a/nym-node/nym-node-requests/src/lib.rs +++ b/nym-node/nym-node-requests/src/lib.rs @@ -66,12 +66,18 @@ pub mod routes { pub const LEGACY_MIXING: &str = "/mixing"; pub const PACKETS_STATS: &str = "/packets-stats"; + pub const WIREGUARD_STATS: &str = "/wireguard-stats"; pub const SESSIONS: &str = "/sessions"; pub const VERLOC: &str = "/verloc"; pub const PROMETHEUS: &str = "/prometheus"; absolute_route!(legacy_mixing_absolute, metrics_absolute(), LEGACY_MIXING); absolute_route!(packets_stats_absolute, metrics_absolute(), PACKETS_STATS); + absolute_route!( + wireguard_stats_absolute, + metrics_absolute(), + WIREGUARD_STATS + ); absolute_route!(sessions_absolute, metrics_absolute(), SESSIONS); absolute_route!(verloc_absolute, metrics_absolute(), VERLOC); absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS); diff --git a/nym-node/src/node/http/router/api/v1/metrics/mod.rs b/nym-node/src/node/http/router/api/v1/metrics/mod.rs index 201dbc7a26..71a9760273 100644 --- a/nym-node/src/node/http/router/api/v1/metrics/mod.rs +++ b/nym-node/src/node/http/router/api/v1/metrics/mod.rs @@ -5,6 +5,7 @@ use crate::node::http::api::v1::metrics::packets_stats::packets_stats; use crate::node::http::api::v1::metrics::prometheus::prometheus_metrics; use crate::node::http::api::v1::metrics::sessions::sessions_stats; use crate::node::http::api::v1::metrics::verloc::verloc_stats; +use crate::node::http::api::v1::metrics::wireguard::wireguard_stats; use crate::node::http::state::metrics::MetricsAppState; use axum::extract::FromRef; use axum::routing::get; @@ -16,6 +17,7 @@ pub mod packets_stats; pub mod prometheus; pub mod sessions; pub mod verloc; +pub mod wireguard; #[derive(Debug, Clone, Default)] pub struct Config { @@ -34,6 +36,7 @@ where get(legacy_mixing::legacy_mixing_stats), ) .route(metrics::PACKETS_STATS, get(packets_stats)) + .route(metrics::WIREGUARD_STATS, get(wireguard_stats)) .route(metrics::SESSIONS, get(sessions_stats)) .route(metrics::VERLOC, get(verloc_stats)) .route(metrics::PROMETHEUS, get(prometheus_metrics)) diff --git a/nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs b/nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs index d1a8e27f55..490bcffb57 100644 --- a/nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs +++ b/nym-node/src/node/http/router/api/v1/metrics/packets_stats.rs @@ -1,5 +1,5 @@ // Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::http::state::metrics::MetricsAppState; use axum::extract::{Query, State}; diff --git a/nym-node/src/node/http/router/api/v1/metrics/wireguard.rs b/nym-node/src/node/http/router/api/v1/metrics/wireguard.rs new file mode 100644 index 0000000000..3519ff30dd --- /dev/null +++ b/nym-node/src/node/http/router/api/v1/metrics/wireguard.rs @@ -0,0 +1,40 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::http::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_metrics::NymNodeMetrics; +use nym_node_requests::api::v1::metrics::models::WireguardStats; + +/// If applicable, returns wireguard statistics information of this node. +/// This information is **PURELY** self-reported and in no way validated. +#[utoipa::path( + get, + path = "/wireguard-stats", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = WireguardStats), + ("application/yaml" = WireguardStats) + )) + ), + params(OutputParams), +)] +pub(crate) async fn wireguard_stats( + Query(output): Query, + State(metrics_state): State, +) -> WireguardStatsResponse { + let output = output.output.unwrap_or_default(); + output.to_response(build_response(&metrics_state.metrics)) +} + +fn build_response(metrics: &NymNodeMetrics) -> WireguardStats { + WireguardStats { + bytes_tx: metrics.wireguard.bytes_tx(), + bytes_rx: metrics.wireguard.bytes_rx(), + } +} + +pub type WireguardStatsResponse = FormattedResponse; diff --git a/nym-node/src/node/metrics/console_logger.rs b/nym-node/src/node/metrics/console_logger.rs index ea3d11e94c..37ad46b7f9 100644 --- a/nym-node/src/node/metrics/console_logger.rs +++ b/nym-node/src/node/metrics/console_logger.rs @@ -25,6 +25,9 @@ struct AtLastUpdate { // EGRESS ack_packets_sent: usize, + + wg_tx: usize, + wg_rx: usize, } impl AtLastUpdate { @@ -35,6 +38,8 @@ impl AtLastUpdate { final_hop_packets_received: 0, forward_hop_packets_sent: 0, ack_packets_sent: 0, + wg_tx: 0, + wg_rx: 0, } } } @@ -70,6 +75,9 @@ impl ConsoleLogger { let forward_sent = self.metrics.mixnet.egress.forward_hop_packets_sent(); let acks = self.metrics.mixnet.egress.ack_packets_sent(); + let wg_tx = self.metrics.wireguard.bytes_tx(); + let wg_rx = self.metrics.wireguard.bytes_rx(); + let forward_received_rate = (forward_received - self.at_last_update.forward_hop_packets_received) as f64 / delta_secs; @@ -79,6 +87,9 @@ impl ConsoleLogger { (forward_sent - self.at_last_update.forward_hop_packets_sent) as f64 / delta_secs; let acks_rate = (acks - self.at_last_update.ack_packets_sent) as f64 / delta_secs; + let wg_tx_rate = (wg_tx - self.at_last_update.wg_tx) as f64 / delta_secs; + let wg_rx_rate = (wg_rx - self.at_last_update.wg_rx) as f64 / delta_secs; + info!("↑↓ Packets sent [total] / sent [acks] / received [mix] / received [gw]: {} ({}) / {} ({}) / {} ({}) / {} ({})", forward_sent.human_count_bare(), forward_sent_rate.human_throughput_bare(), @@ -90,11 +101,24 @@ impl ConsoleLogger { final_rate.human_throughput_bare(), ); + // only log wireguard if we have transmitted ANY bytes + if self.at_last_update.wg_rx != 0 { + info!( + "↑↓ Wireguard tx/rx: {} ({}) / {} ({})", + wg_tx.human_count_bytes(), + wg_tx_rate.human_throughput_bytes(), + wg_rx.human_count_bytes(), + wg_rx_rate.human_throughput_bytes() + ) + } + self.at_last_update.time = now; self.at_last_update.forward_hop_packets_received = forward_received; self.at_last_update.final_hop_packets_received = final_received; self.at_last_update.forward_hop_packets_sent = forward_sent; self.at_last_update.ack_packets_sent = acks; + self.at_last_update.wg_tx = wg_tx; + self.at_last_update.wg_rx = wg_rx; // TODO: add websocket-client traffic } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 1311bb4f57..b4e8824eaa 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -581,6 +581,7 @@ impl NymNode { self.entry_gateway.client_storage.clone(), mix_packet_sender, metrics_sender, + self.metrics.clone(), self.entry_gateway.mnemonic.clone(), task_client, ); From 3efeededc519f040de89d0ad8a33e1dc94f0ee48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 20 Dec 2024 10:32:56 +0000 Subject: [PATCH 64/80] feature: expand nym-node prometheus metrics (#5298) * fixed bearer auth for prometheus route * basic prometheus metrics * added rates on global values * improved structure on the prometheus metrics * added additional metrics for ingress websockets and egress mixnet connections * some channel business metrics * fixed metrics registration and added additional variants * added counter for number of disk persisted packets * counter for pending egress packets * counter for pending egress forward packets * clippy --- Cargo.lock | 4 +- common/client-libs/mixnet-client/Cargo.toml | 4 +- .../client-libs/mixnet-client/src/client.rs | 227 +++++---- common/nonexhaustive-delayqueue/src/lib.rs | 8 + common/nym-metrics/Cargo.toml | 2 +- common/nym-metrics/src/lib.rs | 471 +++++++++++++++--- .../node/client_handling/active_clients.rs | 7 + .../client_handling/websocket/common_state.rs | 2 + .../client_handling/websocket/listener.rs | 8 +- gateway/src/node/mod.rs | 1 + nym-node/nym-node-metrics/src/lib.rs | 9 + nym-node/nym-node-metrics/src/mixnet.rs | 17 +- nym-node/nym-node-metrics/src/network.rs | 32 ++ nym-node/nym-node-metrics/src/process.rs | 57 +++ .../src/prometheus_wrapper.rs | 390 +++++++++++++++ nym-node/src/config/metrics.rs | 17 +- nym-node/src/config/mod.rs | 1 + nym-node/src/node/http/error.rs | 2 +- .../node/http/router/api/v1/metrics/mod.rs | 33 +- .../http/router/api/v1/metrics/prometheus.rs | 26 +- nym-node/src/node/http/router/mod.rs | 7 + nym-node/src/node/http/state/metrics.rs | 2 - nym-node/src/node/http/state/mod.rs | 12 +- nym-node/src/node/metrics/aggregator.rs | 2 +- nym-node/src/node/metrics/events_listener.rs | 2 +- .../node/metrics/handler/client_sessions.rs | 11 +- .../at_last_update.rs | 219 ++++++++ .../handler/global_prometheus_updater/mod.rs | 223 +++++++++ .../metrics/handler/legacy_packet_data.rs | 2 +- nym-node/src/node/metrics/handler/mod.rs | 19 +- .../handler/pending_egress_packets_updater.rs | 60 +++ .../handler/prometheus_events_handler.rs | 6 + nym-node/src/node/mixnet/handler.rs | 9 +- nym-node/src/node/mixnet/packet_forwarding.rs | 25 +- nym-node/src/node/mod.rs | 62 ++- nym-node/src/node/shared_topology.rs | 5 + .../ip-packet-router/src/error.rs | 2 +- .../src/request_filter/mod.rs | 11 +- 38 files changed, 1741 insertions(+), 256 deletions(-) create mode 100644 nym-node/nym-node-metrics/src/process.rs create mode 100644 nym-node/nym-node-metrics/src/prometheus_wrapper.rs create mode 100644 nym-node/src/node/metrics/handler/global_prometheus_updater/at_last_update.rs create mode 100644 nym-node/src/node/metrics/handler/global_prometheus_updater/mod.rs create mode 100644 nym-node/src/node/metrics/handler/pending_egress_packets_updater.rs create mode 100644 nym-node/src/node/metrics/handler/prometheus_events_handler.rs diff --git a/Cargo.lock b/Cargo.lock index cc9ed04a83..b0c33bb62b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5679,18 +5679,20 @@ version = "0.1.0" dependencies = [ "dashmap", "lazy_static", - "log", "prometheus", + "tracing", ] [[package]] name = "nym-mixnet-client" version = "0.1.0" dependencies = [ + "dashmap", "futures", "nym-sphinx", "nym-task", "tokio", + "tokio-stream", "tokio-util", "tracing", ] diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 25dd62f702..b240aab513 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -8,10 +8,12 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dashmap = { workspace = true } futures = { workspace = true } tracing = { workspace = true } -tokio = { workspace = true, features = ["time"] } +tokio = { workspace = true, features = ["time", "sync"] } tokio-util = { workspace = true, features = ["codec"], optional = true } +tokio-stream = { workspace = true } # internal nym-sphinx = { path = "../../nymsphinx" } diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs index b8eebcdcc5..94e32f8404 100644 --- a/common/client-libs/mixnet-client/src/client.rs +++ b/common/client-libs/mixnet-client/src/client.rs @@ -1,21 +1,24 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use futures::channel::mpsc; +use dashmap::DashMap; use futures::StreamExt; use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; use nym_sphinx::framing::codec::NymCodec; use nym_sphinx::framing::packet::FramedNymPacket; use nym_sphinx::params::PacketType; use nym_sphinx::NymPacket; -use std::collections::HashMap; use std::io; use std::net::SocketAddr; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::ops::Deref; +use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::net::TcpStream; +use tokio::sync::mpsc; +use tokio::sync::mpsc::error::TrySendError; use tokio::time::sleep; +use tokio_stream::wrappers::ReceiverStream; use tokio_util::codec::Framed; use tracing::*; @@ -55,11 +58,37 @@ pub trait SendWithoutResponse { } pub struct Client { - conn_new: HashMap, + active_connections: ActiveConnections, + connections_count: Arc, config: Config, } -struct ConnectionSender { +#[derive(Default, Clone)] +pub struct ActiveConnections { + inner: Arc>, +} + +impl ActiveConnections { + pub fn pending_packets(&self) -> usize { + self.inner + .iter() + .map(|sender| { + let max_capacity = sender.channel.max_capacity(); + let capacity = sender.channel.capacity(); + max_capacity - capacity + }) + .sum() + } +} + +impl Deref for ActiveConnections { + type Target = DashMap; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +pub struct ConnectionSender { channel: mpsc::Sender, current_reconnection_attempt: Arc, } @@ -73,46 +102,53 @@ impl ConnectionSender { } } -impl Client { - pub fn new(config: Config) -> Client { - Client { - conn_new: HashMap::new(), - config, +struct ManagedConnection { + address: SocketAddr, + message_receiver: ReceiverStream, + connection_timeout: Duration, + current_reconnection: Arc, +} + +impl ManagedConnection { + fn new( + address: SocketAddr, + message_receiver: mpsc::Receiver, + connection_timeout: Duration, + current_reconnection: Arc, + ) -> Self { + ManagedConnection { + address, + message_receiver: ReceiverStream::new(message_receiver), + connection_timeout, + current_reconnection, } } - async fn manage_connection( - address: SocketAddr, - receiver: mpsc::Receiver, - connection_timeout: Duration, - current_reconnection: &AtomicU32, - ) { + async fn run(self) { + let address = self.address; let connection_fut = TcpStream::connect(address); - let conn = match tokio::time::timeout(connection_timeout, connection_fut).await { + let conn = match tokio::time::timeout(self.connection_timeout, connection_fut).await { Ok(stream_res) => match stream_res { Ok(stream) => { - debug!("Managed to establish connection to {}", address); + debug!("Managed to establish connection to {}", self.address); // if we managed to connect, reset the reconnection count (whatever it might have been) - current_reconnection.store(0, Ordering::Release); + self.current_reconnection.store(0, Ordering::Release); Framed::new(stream, NymCodec) } Err(err) => { - debug!( - "failed to establish connection to {} (err: {})", - address, err - ); + debug!("failed to establish connection to {address} (err: {err})",); return; } }, Err(_) => { debug!( - "failed to connect to {} within {:?}", - address, connection_timeout + "failed to connect to {address} within {:?}", + self.connection_timeout ); // we failed to connect - increase reconnection attempt - current_reconnection.fetch_add(1, Ordering::SeqCst); + self.current_reconnection.fetch_add(1, Ordering::SeqCst); return; } }; @@ -120,15 +156,28 @@ impl Client { // Take whatever the receiver channel produces and put it on the connection. // We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care // about neither receiver nor the connection, it doesn't matter which one gets consumed - if let Err(err) = receiver.map(Ok).forward(conn).await { - warn!("Failed to forward packets to {} - {err}", address); + if let Err(err) = self.message_receiver.map(Ok).forward(conn).await { + warn!("Failed to forward packets to {address}: {err}"); } debug!( - "connection manager to {} is finished. Either the connection failed or mixnet client got dropped", - address + "connection manager to {address} is finished. Either the connection failed or mixnet client got dropped", ); } +} + +impl Client { + pub fn new(config: Config, connections_count: Arc) -> Client { + Client { + active_connections: Default::default(), + connections_count, + config, + } + } + + pub fn active_connections(&self) -> ActiveConnections { + self.active_connections.clone() + } /// If we're trying to reconnect, determine how long we should wait. fn determine_backoff(&self, current_attempt: u32) -> Option { @@ -148,7 +197,7 @@ impl Client { } fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) { - let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size); + let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size); // this CAN'T fail because we just created the channel which has a non-zero capacity if self.config.maximum_connection_buffer_size > 0 { @@ -156,15 +205,16 @@ impl Client { } // if we already tried to connect to `address` before, grab the current attempt count - let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) { - existing.channel = sender; - Arc::clone(&existing.current_reconnection_attempt) - } else { - let new_entry = ConnectionSender::new(sender); - let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt); - self.conn_new.insert(address, new_entry); - current_attempt - }; + let current_reconnection_attempt = + if let Some(mut existing) = self.active_connections.get_mut(&address) { + existing.channel = sender; + Arc::clone(&existing.current_reconnection_attempt) + } else { + let new_entry = ConnectionSender::new(sender); + let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt); + self.active_connections.insert(address, new_entry); + current_attempt + }; // load the actual value. let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire); @@ -173,6 +223,7 @@ impl Client { // copy the value before moving into another task let initial_connection_timeout = self.config.initial_connection_timeout; + let connections_count = self.connections_count.clone(); tokio::spawn(async move { // before executing the manager, wait for what was specified, if anything if let Some(backoff) = backoff { @@ -180,13 +231,16 @@ impl Client { sleep(backoff).await; } - Self::manage_connection( + connections_count.fetch_add(1, Ordering::SeqCst); + ManagedConnection::new( address.into(), receiver, initial_connection_timeout, - ¤t_reconnection_attempt, + current_reconnection_attempt, ) - .await + .run() + .await; + connections_count.fetch_sub(1, Ordering::SeqCst); }); } } @@ -201,49 +255,47 @@ impl SendWithoutResponse for Client { trace!("Sending packet to {address:?}"); let framed_packet = FramedNymPacket::new(packet, packet_type); - if let Some(sender) = self.conn_new.get_mut(&address) { - if let Err(err) = sender.channel.try_send(framed_packet) { - if err.is_full() { - debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address); - // it's not a 'big' error, but we did not manage to send the packet - // if the queue is full, we can't really do anything but to drop the packet - Err(io::Error::new( - io::ErrorKind::WouldBlock, - "connection queue is full", - )) - } else if err.is_disconnected() { - debug!( - "Connection to {} seems to be dead. attempting to re-establish it...", - address - ); - // it's not a 'big' error, but we did not manage to send the packet, but queue - // it up to send it as soon as the connection is re-established - self.make_connection(address, err.into_inner()); - Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - "reconnection attempt is in progress", - )) - } else { - // this can't really happen, but let's safe-guard against it in case something changes in futures library - Err(io::Error::new( - io::ErrorKind::Other, - "unknown connection buffer error", - )) - } - } else { - Ok(()) - } - } else { + let Some(sender) = self.active_connections.get_mut(&address) else { // there was never a connection to begin with debug!("establishing initial connection to {}", address); // it's not a 'big' error, but we did not manage to send the packet, but queue the packet // for sending for as soon as the connection is created self.make_connection(address, framed_packet); - Err(io::Error::new( + return Err(io::Error::new( io::ErrorKind::NotConnected, "connection is in progress", - )) - } + )); + }; + + let sending_res = sender.channel.try_send(framed_packet); + drop(sender); + + sending_res.map_err(|err| { + match err { + TrySendError::Full(_) => { + debug!("Connection to {address} seems to not be able to handle all the traffic - dropping the current packet"); + // it's not a 'big' error, but we did not manage to send the packet + // if the queue is full, we can't really do anything but to drop the packet + io::Error::new( + io::ErrorKind::WouldBlock, + "connection queue is full", + ) + } + TrySendError::Closed(dropped) => { + debug!( + "Connection to {address} seems to be dead. attempting to re-establish it...", + ); + + // it's not a 'big' error, but we did not manage to send the packet, but queue + // it up to send it as soon as the connection is re-established + self.make_connection(address, dropped); + io::Error::new( + io::ErrorKind::ConnectionAborted, + "reconnection attempt is in progress", + ) + } + } + } ) } } @@ -252,12 +304,15 @@ mod tests { use super::*; fn dummy_client() -> Client { - Client::new(Config { - initial_reconnection_backoff: Duration::from_millis(10_000), - maximum_reconnection_backoff: Duration::from_millis(300_000), - initial_connection_timeout: Duration::from_millis(1_500), - maximum_connection_buffer_size: 128, - }) + Client::new( + Config { + initial_reconnection_backoff: Duration::from_millis(10_000), + maximum_reconnection_backoff: Duration::from_millis(300_000), + initial_connection_timeout: Duration::from_millis(1_500), + maximum_connection_buffer_size: 128, + }, + Default::default(), + ) } #[test] diff --git a/common/nonexhaustive-delayqueue/src/lib.rs b/common/nonexhaustive-delayqueue/src/lib.rs index ee8c9f214c..6c4853f675 100644 --- a/common/nonexhaustive-delayqueue/src/lib.rs +++ b/common/nonexhaustive-delayqueue/src/lib.rs @@ -65,6 +65,14 @@ impl NonExhaustiveDelayQueue { pub fn remove(&mut self, key: &QueueKey) -> Expired { self.inner.remove(key) } + + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } } impl Default for NonExhaustiveDelayQueue { diff --git a/common/nym-metrics/Cargo.toml b/common/nym-metrics/Cargo.toml index 4310ae28f6..1ac3bce16a 100644 --- a/common/nym-metrics/Cargo.toml +++ b/common/nym-metrics/Cargo.toml @@ -12,6 +12,6 @@ license.workspace = true [dependencies] prometheus = { workspace = true } -log = { workspace = true } +tracing = { workspace = true } dashmap = { workspace = true } lazy_static = { workspace = true } diff --git a/common/nym-metrics/src/lib.rs b/common/nym-metrics/src/lib.rs index 565d62cd5f..7c6373ccc7 100644 --- a/common/nym-metrics/src/lib.rs +++ b/common/nym-metrics/src/lib.rs @@ -1,14 +1,18 @@ use dashmap::DashMap; -pub use log::error; -use log::{debug, warn}; use std::fmt; -pub use std::time::Instant; +use tracing::{debug, error, warn}; -use prometheus::{core::Collector, Encoder as _, IntCounter, IntGauge, Registry, TextEncoder}; +use prometheus::{ + core::Collector, Encoder as _, Gauge, Histogram, HistogramOpts, IntCounter, IntGauge, Registry, + TextEncoder, +}; + +pub use prometheus::HistogramTimer; +pub use std::time::Instant; #[macro_export] macro_rules! prepend_package_name { - ($name: literal) => { + ($name: tt) => { &format!( "{}_{}", std::module_path!() @@ -23,15 +27,29 @@ macro_rules! prepend_package_name { #[macro_export] macro_rules! inc_by { + ($name:literal, $x:expr, $help: expr) => { + $crate::REGISTRY.maybe_register_and_inc_by( + $crate::prepend_package_name!($name), + $x as i64, + $help, + ); + }; ($name:literal, $x:expr) => { - $crate::REGISTRY.inc_by($crate::prepend_package_name!($name), $x as i64); + $crate::REGISTRY.maybe_register_and_inc_by( + $crate::prepend_package_name!($name), + $x as i64, + None, + ); }; } #[macro_export] macro_rules! inc { + ($name:literal, $help: expr) => { + $crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), $help); + }; ($name:literal) => { - $crate::REGISTRY.inc($crate::prepend_package_name!($name)); + $crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), None); }; } @@ -42,6 +60,71 @@ macro_rules! metrics { }; } +#[macro_export] +macro_rules! set_metric { + ($name:literal, $x:expr, $help: expr) => { + $crate::REGISTRY.maybe_register_and_set( + $crate::prepend_package_name!($name), + $x as i64, + $help, + ); + }; + ($name:literal, $x:expr) => { + $crate::REGISTRY.maybe_register_and_set( + $crate::prepend_package_name!($name), + $x as i64, + None, + ); + }; +} + +#[macro_export] +macro_rules! set_metric_float { + ($name:literal, $x:expr, $help: expr) => { + $crate::REGISTRY.maybe_register_and_set_float( + $crate::prepend_package_name!($name), + $x as f64, + $help, + ); + }; + ($name:literal, $x:expr) => { + $crate::REGISTRY.maybe_register_and_set_float( + $crate::prepend_package_name!($name), + $x as f64, + None, + ); + }; +} + +#[macro_export] +macro_rules! add_histogram_obs { + ($name:expr, $x:expr, $b:expr, $help:expr) => { + $crate::REGISTRY.maybe_register_and_add_to_histogram( + $crate::prepend_package_name!($name), + $x as f64, + Some($b), + $help, + ); + }; + + ($name:expr, $x:expr, $b:expr) => { + $crate::REGISTRY.maybe_register_and_add_to_histogram( + $crate::prepend_package_name!($name), + $x as f64, + Some($b), + None, + ); + }; + ($name:expr, $x:expr) => { + $crate::REGISTRY.maybe_register_and_add_to_histogram( + $crate::prepend_package_name!($name), + $x as f64, + None, + None, + ); + }; +} + #[macro_export] macro_rules! nanos { ( $name:literal, $x:expr ) => {{ @@ -50,7 +133,7 @@ macro_rules! nanos { let r = $x; let duration = start.elapsed().as_nanos() as i64; let name = $crate::prepend_package_name!($name); - $crate::REGISTRY.inc_by(&format!("{}_nanos", $name), duration); + $crate::REGISTRY.maybe_register_and_inc_by(&format!("{}_nanos", $name), duration, None); r }}; } @@ -59,15 +142,100 @@ lazy_static::lazy_static! { pub static ref REGISTRY: MetricsController = MetricsController::default(); } +pub fn metrics_registry() -> &'static MetricsController { + ®ISTRY +} + #[derive(Default)] pub struct MetricsController { registry: Registry, registry_index: DashMap, } -enum Metric { - C(Box), - G(Box), +pub enum Metric { + IntCounter(Box), + IntGauge(Box), + FloatGauge(Box), + Histogram(Box), +} + +impl Metric { + pub fn new_int_counter(name: &str, help: &str) -> Option { + match IntCounter::new(sanitize_metric_name(name), help) { + Ok(c) => Some(c.into()), + Err(err) => { + error!("Failed to create counter {name:?}: {err}"); + None + } + } + } + + pub fn new_int_gauge(name: &str, help: &str) -> Option { + match IntGauge::new(sanitize_metric_name(name), help) { + Ok(g) => Some(g.into()), + Err(err) => { + error!("Failed to create gauge {name:?}: {err}"); + None + } + } + } + + pub fn new_float_gauge(name: &str, help: &str) -> Option { + match Gauge::new(sanitize_metric_name(name), help) { + Ok(g) => Some(g.into()), + Err(err) => { + error!("Failed to create gauge {name:?}: {err}"); + None + } + } + } + + pub fn new_histogram(name: &str, help: &str, buckets: Option<&[f64]>) -> Option { + let mut opts = HistogramOpts::new(sanitize_metric_name(name), help); + if let Some(buckets) = buckets { + opts = opts.buckets(buckets.to_vec()) + } + match Histogram::with_opts(opts) { + Ok(h) => Some(Metric::Histogram(Box::new(h))), + Err(err) => { + error!("failed to create histogram {name:?}: {err}"); + None + } + } + } + + fn as_collector(&self) -> Box { + match self { + Metric::IntCounter(c) => c.clone(), + Metric::IntGauge(g) => g.clone(), + Metric::FloatGauge(g) => g.clone(), + Metric::Histogram(h) => h.clone(), + } + } +} + +impl From for Metric { + fn from(v: IntCounter) -> Self { + Metric::IntCounter(Box::new(v)) + } +} + +impl From for Metric { + fn from(v: IntGauge) -> Self { + Metric::IntGauge(Box::new(v)) + } +} + +impl From for Metric { + fn from(v: Gauge) -> Self { + Metric::FloatGauge(Box::new(v)) + } +} + +impl From for Metric { + fn from(v: Histogram) -> Self { + Metric::Histogram(Box::new(v)) + } } fn fq_name(c: &dyn Collector) -> String { @@ -81,34 +249,92 @@ impl Metric { #[inline(always)] fn fq_name(&self) -> String { match self { - Metric::C(c) => fq_name(c.as_ref()), - Metric::G(g) => fq_name(g.as_ref()), + Metric::IntCounter(c) => fq_name(c.as_ref()), + Metric::IntGauge(g) => fq_name(g.as_ref()), + Metric::FloatGauge(g) => fq_name(g.as_ref()), + Metric::Histogram(h) => fq_name(h.as_ref()), } } #[inline(always)] fn inc(&self) { match self { - Metric::C(c) => c.inc(), - Metric::G(g) => g.inc(), + Metric::IntCounter(c) => c.inc(), + Metric::IntGauge(g) => g.inc(), + Metric::FloatGauge(g) => g.inc(), + Metric::Histogram(_) => { + warn!("invalid operation: attempted to call increment on a histogram") + } } } #[inline(always)] fn inc_by(&self, value: i64) { match self { - Metric::C(c) => c.inc_by(value as u64), - Metric::G(g) => g.add(value), + Metric::IntCounter(c) => c.inc_by(value as u64), + Metric::IntGauge(g) => g.add(value), + Metric::FloatGauge(g) => { + warn!("attempted to increment a float gauge ('{}') by an integer - this is most likely a bug", self.fq_name()); + g.add(value as f64) + } + Metric::Histogram(_) => { + warn!("invalid operation: attempted to call increment on a histogram") + } } } #[inline(always)] fn set(&self, value: i64) { match self { - Metric::C(_c) => { + Metric::IntCounter(_c) => { warn!("Cannot set value for counter {:?}", self.fq_name()); } - Metric::G(g) => g.set(value), + Metric::IntGauge(g) => g.set(value), + Metric::FloatGauge(g) => { + warn!("attempted to set a float gauge ('{}') to an integer value - this is most likely a bug", self.fq_name()); + g.set(value as f64) + } + Metric::Histogram(_) => { + warn!("invalid operation: attempted to call set on a histogram") + } + } + } + + #[inline(always)] + fn set_float(&self, value: f64) { + match self { + Metric::IntCounter(_c) => { + warn!("Cannot set value for counter {:?}", self.fq_name()); + } + Metric::IntGauge(g) => { + warn!("attempted to set a integer gauge ('{}') to a float value - this is most likely a bug", self.fq_name()); + g.set(value as i64) + } + Metric::FloatGauge(g) => g.set(value), + Metric::Histogram(_) => { + warn!("invalid operation: attempted to call increment on a histogram") + } + } + } + + #[inline(always)] + fn add_histogram_observation(&self, value: f64) { + match self { + Metric::Histogram(h) => { + h.observe(value); + } + _ => warn!("attempted to add histogram observation on a non-histogram metric"), + } + } + + #[inline(always)] + fn start_timer(&self) -> Option { + match self { + Metric::Histogram(h) => Some(h.start_timer()), + _ => { + warn!("attempted to start histogram observation on a non-histogram metric"); + None + } } } } @@ -145,93 +371,165 @@ impl MetricsController { } } - pub fn set(&self, name: &str, value: i64) { + pub fn register_int_gauge<'a>(&self, name: &str, help: impl Into>) { + let Some(metric) = Metric::new_int_gauge(name, help.into().unwrap_or(name)) else { + return; + }; + self.register_metric(metric); + } + + pub fn register_float_gauge<'a>(&self, name: &str, help: impl Into>) { + let Some(metric) = Metric::new_float_gauge(name, help.into().unwrap_or(name)) else { + return; + }; + self.register_metric(metric); + } + + pub fn register_int_counter<'a>(&self, name: &str, help: impl Into>) { + let Some(metric) = Metric::new_int_counter(name, help.into().unwrap_or(name)) else { + return; + }; + self.register_metric(metric); + } + + pub fn register_histogram<'a>( + &self, + name: &str, + help: impl Into>, + buckets: Option<&[f64]>, + ) { + let Some(metric) = Metric::new_histogram(name, help.into().unwrap_or(name), buckets) else { + return; + }; + self.register_metric(metric); + } + + pub fn set(&self, name: &str, value: i64) -> bool { if let Some(metric) = self.registry_index.get(name) { metric.set(value); + true } else { - let gauge = match IntGauge::new(sanitize_metric_name(name), name) { - Ok(g) => g, - Err(e) => { - debug!("Failed to create gauge {:?}:\n{}", name, e); - return; - } - }; - self.register_gauge(Box::new(gauge)); - self.set(name, value) + false } } - pub fn inc(&self, name: &str) { + pub fn set_float(&self, name: &str, value: f64) -> bool { + if let Some(metric) = self.registry_index.get(name) { + metric.set_float(value); + true + } else { + false + } + } + + pub fn add_to_histogram(&self, name: &str, value: f64) -> bool { + if let Some(metric) = self.registry_index.get(name) { + metric.add_histogram_observation(value); + true + } else { + false + } + } + + pub fn start_timer(&self, name: &str) -> Option { + self.registry_index + .get(name) + .and_then(|metric| metric.start_timer()) + } + + pub fn inc(&self, name: &str) -> bool { if let Some(metric) = self.registry_index.get(name) { metric.inc(); + true } else { - let counter = match IntCounter::new(sanitize_metric_name(name), name) { - Ok(c) => c, - Err(e) => { - debug!("Failed to create counter {:?}:\n{}", name, e); - return; - } - }; - self.register_counter(Box::new(counter)); - self.inc(name) + false } } - pub fn inc_by(&self, name: &str, value: i64) { + pub fn inc_by(&self, name: &str, value: i64) -> bool { if let Some(metric) = self.registry_index.get(name) { metric.inc_by(value); + true } else { - let counter = match IntCounter::new(sanitize_metric_name(name), name) { - Ok(c) => c, - Err(e) => { - debug!("Failed to create counter {:?}:\n{}", name, e); - return; - } - }; - self.register_counter(Box::new(counter)); - self.inc_by(name, value) + false } } - fn register_gauge(&self, metric: Box) { - let fq_name = metric - .desc() - .first() - .map(|d| d.fq_name.clone()) - .unwrap_or_default(); + pub fn maybe_register_and_set<'a>( + &self, + name: &str, + value: i64, + help: impl Into>, + ) { + if !self.set(name, value) { + let help = help.into(); + self.register_int_gauge(name, help); + self.set(name, value); + } + } + + pub fn maybe_register_and_set_float<'a>( + &self, + name: &str, + value: f64, + help: impl Into>, + ) { + if !self.set_float(name, value) { + let help = help.into(); + self.register_float_gauge(name, help); + self.set_float(name, value); + } + } + + pub fn maybe_register_and_add_to_histogram<'a>( + &self, + name: &str, + value: f64, + buckets: Option<&[f64]>, + help: impl Into>, + ) { + if !self.add_to_histogram(name, value) { + let help = help.into(); + self.register_histogram(name, help, buckets); + self.add_to_histogram(name, value); + } + } + + pub fn maybe_register_and_inc<'a>(&self, name: &str, help: impl Into>) { + if !self.inc(name) { + let help = help.into(); + self.register_int_counter(name, help); + self.inc(name); + } + } + + pub fn maybe_register_and_inc_by<'a>( + &self, + name: &str, + value: i64, + help: impl Into>, + ) { + if !self.inc_by(name, value) { + let help = help.into(); + self.register_int_counter(name, help); + self.inc_by(name, value); + } + } + + pub fn register_metric(&self, metric: impl Into) { + let m = metric.into(); + let fq_name = m.fq_name(); if self.registry_index.contains_key(&fq_name) { return; } - match self.registry.register(metric.clone()) { + match self.registry.register(m.as_collector()) { Ok(_) => { - self.registry_index - .insert(fq_name, Metric::G(metric.clone())); + self.registry_index.insert(fq_name, m); } - Err(e) => { - debug!("Failed to register {:?}:\n{}", fq_name, e) - } - } - } - - fn register_counter(&self, metric: Box) { - let fq_name = metric - .desc() - .first() - .map(|d| d.fq_name.clone()) - .unwrap_or_default(); - - if self.registry_index.contains_key(&fq_name) { - return; - } - match self.registry.register(metric.clone()) { - Ok(_) => { - self.registry_index - .insert(fq_name, Metric::C(metric.clone())); - } - Err(e) => { - debug!("Failed to register {:?}:\n{}", fq_name, e) + Err(err) => { + debug!("Failed to register '{fq_name}': {err}") } } } @@ -275,4 +573,15 @@ mod tests { "packets_sent_34_242_65_133:1789" ) } + + #[test] + fn prepend_package_name() { + let literal = prepend_package_name!("foo"); + assert_eq!(literal, "nym_metrics_foo"); + + let bar = "bar"; + let format = format!("foomp_{}", bar); + let formatted = prepend_package_name!(format); + assert_eq!(formatted, "nym_metrics_foomp_bar"); + } } diff --git a/gateway/src/node/client_handling/active_clients.rs b/gateway/src/node/client_handling/active_clients.rs index 3dacfd3f63..4a3e55bef5 100644 --- a/gateway/src/node/client_handling/active_clients.rs +++ b/gateway/src/node/client_handling/active_clients.rs @@ -163,4 +163,11 @@ impl ActiveClientsStore { pub(crate) fn size(&self) -> usize { self.inner.len() } + + pub fn pending_packets(&self) -> usize { + self.inner + .iter() + .map(|client| client.get_sender_ref().len()) + .sum() + } } diff --git a/gateway/src/node/client_handling/websocket/common_state.rs b/gateway/src/node/client_handling/websocket/common_state.rs index 19a5943c53..223c534b26 100644 --- a/gateway/src/node/client_handling/websocket/common_state.rs +++ b/gateway/src/node/client_handling/websocket/common_state.rs @@ -7,6 +7,7 @@ use nym_crypto::asymmetric::identity; use nym_gateway_storage::GatewayStorage; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_node_metrics::events::MetricEventsSender; +use nym_node_metrics::NymNodeMetrics; use std::sync::Arc; // I can see this being possible expanded with say storage or client store @@ -17,6 +18,7 @@ pub(crate) struct CommonHandlerState { pub(crate) local_identity: Arc, pub(crate) only_coconut_credentials: bool, pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig, + pub(crate) metrics: NymNodeMetrics, pub(crate) metrics_sender: MetricEventsSender, pub(crate) outbound_mix_sender: MixForwardingSender, pub(crate) active_clients_store: ActiveClientsStore, diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index ed484d17b6..faa659f0fa 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -61,7 +61,13 @@ impl Listener { remote_addr, shutdown, ); - tokio::spawn(handle.start_handling()); + tokio::spawn(async move { + // TODO: refactor it similarly to the mixnet listener on the nym-node + let metrics_ref = handle.shared_state.metrics.clone(); + metrics_ref.network.new_ingress_websocket_client(); + handle.start_handling().await; + metrics_ref.network.disconnected_ingress_websocket_client(); + }); } Err(err) => warn!("failed to get client: {err}"), } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index b3b41961a1..3304f223b3 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -252,6 +252,7 @@ impl GatewayTasksBuilder { local_identity: Arc::clone(&self.identity_keypair), only_coconut_credentials: self.config.gateway.enforce_zk_nyms, bandwidth_cfg: (&self.config).into(), + metrics: self.metrics.clone(), metrics_sender: self.metrics_sender.clone(), outbound_mix_sender: self.mix_packet_sender.clone(), active_clients_store: active_clients_store.clone(), diff --git a/nym-node/nym-node-metrics/src/lib.rs b/nym-node/nym-node-metrics/src/lib.rs index 57a8c74bb3..904cdc1407 100644 --- a/nym-node/nym-node-metrics/src/lib.rs +++ b/nym-node/nym-node-metrics/src/lib.rs @@ -1,9 +1,15 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] +#![warn(clippy::todo)] +#![warn(clippy::dbg_macro)] + use crate::entry::EntryStats; use crate::mixnet::MixingStats; use crate::network::NetworkStats; +use crate::process::NodeStats; use crate::wireguard::WireguardStats; use std::ops::Deref; use std::sync::Arc; @@ -12,6 +18,8 @@ pub mod entry; pub mod events; pub mod mixnet; pub mod network; +pub mod process; +pub mod prometheus_wrapper; pub mod wireguard; #[derive(Clone, Default)] @@ -39,4 +47,5 @@ pub struct NymNodeMetricsInner { pub wireguard: WireguardStats, pub network: NetworkStats, + pub process: NodeStats, } diff --git a/nym-node/nym-node-metrics/src/mixnet.rs b/nym-node/nym-node-metrics/src/mixnet.rs index 043d2d1e7c..32d84e509b 100644 --- a/nym-node/nym-node-metrics/src/mixnet.rs +++ b/nym-node/nym-node-metrics/src/mixnet.rs @@ -131,13 +131,6 @@ impl MixingStats { .or_default() .dropped += 1; } - - pub fn egress_dropped_final_hop_packet(&self) { - todo!() - // self.egress - // .final_hop_packets_dropped - // .fetch_add(1, Ordering::Relaxed); - } } #[derive(Clone, Copy, Default, PartialEq)] @@ -148,6 +141,8 @@ pub struct EgressRecipientStats { #[derive(Default)] pub struct EgressMixingStats { + disk_persisted_packets: AtomicUsize, + // this includes ACKS! forward_hop_packets_sent: AtomicUsize, @@ -159,6 +154,14 @@ pub struct EgressMixingStats { } impl EgressMixingStats { + pub fn add_disk_persisted_packet(&self) { + self.disk_persisted_packets.fetch_add(1, Ordering::Relaxed); + } + + pub fn disk_persisted_packets(&self) -> usize { + self.disk_persisted_packets.load(Ordering::Relaxed) + } + pub fn forward_hop_packets_sent(&self) -> usize { self.forward_hop_packets_sent.load(Ordering::Relaxed) } diff --git a/nym-node/nym-node-metrics/src/network.rs b/nym-node/nym-node-metrics/src/network.rs index de00d78560..7de912fc3c 100644 --- a/nym-node/nym-node-metrics/src/network.rs +++ b/nym-node/nym-node-metrics/src/network.rs @@ -2,11 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; #[derive(Default)] pub struct NetworkStats { // for now just experiment with basic data, we could always extend it active_ingress_mixnet_connections: AtomicUsize, + + active_ingress_websocket_connections: AtomicUsize, + + // the reason for additional `Arc` on this one is that the handler wasn't + // designed with metrics in mind and this single counter has been woven through + // the call stack + active_egress_mixnet_connections: Arc, } impl NetworkStats { @@ -20,8 +28,32 @@ impl NetworkStats { .fetch_sub(1, Ordering::Relaxed); } + pub fn new_ingress_websocket_client(&self) { + self.active_ingress_websocket_connections + .fetch_add(1, Ordering::Relaxed); + } + + pub fn disconnected_ingress_websocket_client(&self) { + self.active_ingress_websocket_connections + .fetch_sub(1, Ordering::Relaxed); + } + pub fn active_ingress_mixnet_connections_count(&self) -> usize { self.active_ingress_mixnet_connections .load(Ordering::Relaxed) } + + pub fn active_ingress_websocket_connections_count(&self) -> usize { + self.active_ingress_websocket_connections + .load(Ordering::Relaxed) + } + + pub fn active_egress_mixnet_connections_counter(&self) -> Arc { + self.active_egress_mixnet_connections.clone() + } + + pub fn active_egress_mixnet_connections_count(&self) -> usize { + self.active_egress_mixnet_connections + .load(Ordering::Relaxed) + } } diff --git a/nym-node/nym-node-metrics/src/process.rs b/nym-node/nym-node-metrics/src/process.rs new file mode 100644 index 0000000000..8e19688249 --- /dev/null +++ b/nym-node/nym-node-metrics/src/process.rs @@ -0,0 +1,57 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[derive(Default)] +pub struct NodeStats { + pub final_hop_packets_pending_delivery: AtomicUsize, + + pub forward_hop_packets_pending_delivery: AtomicUsize, + + pub forward_hop_packets_being_delayed: AtomicUsize, + + // packets that haven't yet been delayed and are waiting for their chance + pub packet_forwarder_queue_size: AtomicUsize, +} + +impl NodeStats { + pub fn update_final_hop_packets_pending_delivery(&self, current: usize) { + self.final_hop_packets_pending_delivery + .store(current, Ordering::Relaxed); + } + + pub fn final_hop_packets_pending_delivery_count(&self) -> usize { + self.final_hop_packets_pending_delivery + .load(Ordering::Relaxed) + } + + pub fn update_forward_hop_packets_pending_delivery(&self, current: usize) { + self.forward_hop_packets_pending_delivery + .store(current, Ordering::Relaxed); + } + + pub fn forward_hop_packets_pending_delivery_count(&self) -> usize { + self.forward_hop_packets_pending_delivery + .load(Ordering::Relaxed) + } + + pub fn update_forward_hop_packets_being_delayed(&self, current: usize) { + self.forward_hop_packets_being_delayed + .store(current, Ordering::Relaxed); + } + + pub fn forward_hop_packets_being_delayed_count(&self) -> usize { + self.forward_hop_packets_being_delayed + .load(Ordering::Relaxed) + } + + pub fn update_packet_forwarder_queue_size(&self, current: usize) { + self.packet_forwarder_queue_size + .store(current, Ordering::Relaxed); + } + + pub fn packet_forwarder_queue_size(&self) -> usize { + self.packet_forwarder_queue_size.load(Ordering::Relaxed) + } +} diff --git a/nym-node/nym-node-metrics/src/prometheus_wrapper.rs b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs new file mode 100644 index 0000000000..2d61ee55c4 --- /dev/null +++ b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs @@ -0,0 +1,390 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_metrics::{metrics_registry, HistogramTimer, Metric}; +use std::sync::LazyLock; +use strum::{Display, EnumCount, EnumIter, EnumProperty, IntoEnumIterator}; + +pub static PROMETHEUS_METRICS: LazyLock = + LazyLock::new(NymNodePrometheusMetrics::initialise); + +const CLIENT_SESSION_DURATION_BUCKETS: &[f64] = &[ + // sub 3s (implicitly) + 3., // 3s - 15s + 15., // 15s - 70s + 70., // 70s - 2min + 120., // 2 min - 5 min + 300., // 5min - 15min + 900., // 15min - 1h + 3600., // 1h - 12h + 43200., // 12h - 23.5h + 88200., // 23.5h - 24.5h + 86400., // 24.5h - 72h + 259200., // 72h+ (implicitly) +]; + +#[derive(Clone, Debug, EnumIter, Display, EnumProperty, EnumCount, Eq, Hash, PartialEq)] +#[strum(serialize_all = "snake_case", prefix = "nym_node_")] +pub enum PrometheusMetric { + // # MIXNET + // ## INGRESS + #[strum(props(help = "The number of ingress forward hop sphinx packets received"))] + MixnetIngressForwardPacketsReceived, + + #[strum(props(help = "The number of ingress final hop sphinx packets received"))] + MixnetIngressFinalHopPacketsReceived, + + #[strum(props(help = "The number of ingress malformed sphinx packets received"))] + MixnetIngressMalformedPacketsReceived, + + #[strum(props( + help = "The number of ingress forward sphinx packets that specified excessive delay received" + ))] + MixnetIngressExcessiveDelayPacketsReceived, + + #[strum(props(help = "The number of ingress forward hop sphinx packets dropped"))] + MixnetIngressForwardPacketsDropped, + + #[strum(props(help = "The number of ingress final hop sphinx packets dropped"))] + MixnetIngressFinalHopPacketsDropped, + + #[strum(props(help = "The current rate of receiving ingress forward hop sphinx packets"))] + MixnetIngressForwardPacketsReceivedRate, + + #[strum(props(help = "The current rate of receiving ingress final hop sphinx packets"))] + MixnetIngressFinalHopPacketsReceivedRate, + + #[strum(props(help = "The current rate of receiving ingress malformed sphinx packets"))] + MixnetIngressMalformedPacketsReceivedRate, + + #[strum(props( + help = "The current rate of receiving ingress sphinx packets that specified excessive delay" + ))] + MixnetIngressExcessiveDelayPacketsReceivedRate, + + #[strum(props(help = "The current rate of dropping ingress forward hop sphinx packets"))] + MixnetIngressForwardPacketsDroppedRate, + + #[strum(props(help = "The current rate of dropping ingress final hop sphinx packets"))] + MixnetIngressFinalHopPacketsDroppedRate, + + // ## EGRESS + #[strum(props( + help = "The number of unwrapped final hop packets stored on disk for offline clients" + ))] + MixnetEgressStoredOnDiskFinalHopPackets, + + #[strum(props(help = "The number of egress forward hop sphinx packets sent/forwarded"))] + MixnetEgressForwardPacketsSent, + + #[strum(props( + help = "The number of egress forward hop sphinx packets sent/forwarded (acks only)" + ))] + MixnetEgressAckSent, + + #[strum(props(help = "The number of egress forward hop sphinx packets dropped"))] + MixnetEgressForwardPacketsDropped, + + #[strum(props( + help = "The current rate of sending/forwarding egress forward hop sphinx packets" + ))] + MixnetEgressForwardPacketsSendRate, + + #[strum(props( + help = "The current rate of sending/forwarding egress forward hop sphinx packets (acks only)" + ))] + MixnetEgressAckSendRate, + + #[strum(props(help = "The current rate of dropping egress forward hop sphinx packets"))] + MixnetEgressForwardPacketsDroppedRate, + + // # ENTRY + #[strum(props(help = "The number of unique users"))] + EntryClientUniqueUsers, + + #[strum(props(help = "The number of client sessions started"))] + EntryClientSessionsStarted, + + #[strum(props(help = "The number of client sessions finished"))] + EntryClientSessionsFinished, + + #[strum(to_string = "entry_client_sessions_durations_{typ}")] + #[strum(props(help = "The distribution of client sessions duration of the specified type"))] + EntryClientSessionsDurations { typ: String }, + + // # WIREGUARD + #[strum(props(help = "The amount of bytes transmitted via wireguard"))] + WireguardBytesTx, + + #[strum(props(help = "The amount of bytes received via wireguard"))] + WireguardBytesRx, + + #[strum(props(help = "The current number of all registered wireguard peers"))] + WireguardTotalPeers, + + #[strum(props(help = "The current number of active wireguard peers"))] + WireguardActivePeers, + + #[strum(props(help = "The current sending rate of wireguard"))] + WireguardBytesTxRate, + + #[strum(props(help = "The current receiving rate of wireguard"))] + WireguardBytesRxRate, + + // # NETWORK + #[strum(props(help = "The number of active ingress mixnet connections"))] + NetworkActiveIngressMixnetConnections, + + #[strum(props(help = "The number of active ingress websocket connections"))] + NetworkActiveIngressWebSocketConnections, + + #[strum(props(help = "The number of active egress mixnet connections"))] + NetworkActiveEgressMixnetConnections, + + // # PROCESS + #[strum(props(help = "The current number of packets being delayed"))] + ProcessForwardHopPacketsBeingDelayed, + + #[strum(props( + help = "The current number of packets waiting in the queue to get delayed and sent into the mixnet" + ))] + ProcessPacketForwarderQueueSize, + + #[strum(props( + help = "The latency distribution of attempting to retrieve network topology (from nym-api)" + ))] + ProcessTopologyQueryResolutionLatency, + + #[strum(props( + help = "The current number of final hop packets stuck in channels waiting to get delivered to appropriate websocket connections" + ))] + ProcessFinalHopPacketsPendingDelivery, + + #[strum(props( + help = "The current number of forward hop packets stuck in channels waiting to get delivered to appropriate TCP connections" + ))] + ProcessForwardHopPacketsPendingDelivery, +} + +impl PrometheusMetric { + fn name(&self) -> String { + self.to_string() + } + + fn help(&self) -> &'static str { + // SAFETY: every variant has a `help` prop defined (and there's a unit test is checking for that) + #[allow(clippy::unwrap_used)] + self.get_str("help").unwrap() + } + + fn is_complex(&self) -> bool { + matches!(self, PrometheusMetric::EntryClientSessionsDurations { .. }) + // match self { + // PrometheusMetric::EntryClientSessionsDurations { .. } => true, + // _ => false, + // } + } + + fn to_registrable_metric(&self) -> Option { + let name = self.name(); + let help = self.help(); + + match self { + PrometheusMetric::MixnetIngressForwardPacketsReceived => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetIngressFinalHopPacketsReceived => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetIngressMalformedPacketsReceived => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetIngressExcessiveDelayPacketsReceived => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetIngressForwardPacketsDropped => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetIngressFinalHopPacketsDropped => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetIngressForwardPacketsReceivedRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::MixnetIngressFinalHopPacketsReceivedRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::MixnetIngressMalformedPacketsReceivedRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::MixnetIngressExcessiveDelayPacketsReceivedRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::MixnetIngressForwardPacketsDroppedRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::MixnetIngressFinalHopPacketsDroppedRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::MixnetEgressStoredOnDiskFinalHopPackets => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetEgressForwardPacketsSent => Metric::new_int_gauge(&name, help), + PrometheusMetric::MixnetEgressAckSent => Metric::new_int_gauge(&name, help), + PrometheusMetric::MixnetEgressForwardPacketsDropped => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::MixnetEgressForwardPacketsSendRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::MixnetEgressAckSendRate => Metric::new_float_gauge(&name, help), + PrometheusMetric::MixnetEgressForwardPacketsDroppedRate => { + Metric::new_float_gauge(&name, help) + } + PrometheusMetric::EntryClientUniqueUsers => Metric::new_int_gauge(&name, help), + PrometheusMetric::EntryClientSessionsStarted => Metric::new_int_gauge(&name, help), + PrometheusMetric::EntryClientSessionsFinished => Metric::new_int_gauge(&name, help), + PrometheusMetric::EntryClientSessionsDurations { .. } => { + Metric::new_histogram(&name, help, Some(CLIENT_SESSION_DURATION_BUCKETS)) + } + PrometheusMetric::WireguardBytesTx => Metric::new_int_gauge(&name, help), + PrometheusMetric::WireguardBytesRx => Metric::new_int_gauge(&name, help), + PrometheusMetric::WireguardTotalPeers => Metric::new_int_gauge(&name, help), + PrometheusMetric::WireguardActivePeers => Metric::new_int_gauge(&name, help), + PrometheusMetric::WireguardBytesTxRate => Metric::new_float_gauge(&name, help), + PrometheusMetric::WireguardBytesRxRate => Metric::new_float_gauge(&name, help), + PrometheusMetric::NetworkActiveIngressMixnetConnections => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::NetworkActiveIngressWebSocketConnections => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::NetworkActiveEgressMixnetConnections => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::ProcessForwardHopPacketsBeingDelayed => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::ProcessPacketForwarderQueueSize => Metric::new_int_gauge(&name, help), + PrometheusMetric::ProcessTopologyQueryResolutionLatency => { + Metric::new_histogram(&name, help, None) + } + PrometheusMetric::ProcessFinalHopPacketsPendingDelivery => { + Metric::new_int_gauge(&name, help) + } + PrometheusMetric::ProcessForwardHopPacketsPendingDelivery => { + Metric::new_int_gauge(&name, help) + } + } + } + + fn set(&self, value: i64) { + metrics_registry().set(&self.name(), value); + } + + fn set_float(&self, value: f64) { + metrics_registry().set_float(&self.name(), value); + } + + fn inc(&self) { + metrics_registry().inc(&self.name()); + } + + fn inc_by(&self, value: i64) { + metrics_registry().inc_by(&self.name(), value); + } + + fn observe_histogram(&self, value: f64) { + let reg = metrics_registry(); + if !reg.add_to_histogram(&self.name(), value) { + if let Some(registrable) = self.to_registrable_metric() { + reg.register_metric(registrable); + reg.add_to_histogram(&self.name(), value); + } + } + } + + fn start_timer(&self) -> Option { + metrics_registry().start_timer(&self.name()) + } +} + +#[non_exhaustive] +pub struct NymNodePrometheusMetrics {} + +impl NymNodePrometheusMetrics { + // initialise all fields on startup with default values so that they'd be immediately available for query + pub(crate) fn initialise() -> Self { + let registry = metrics_registry(); + + // we can't initialise complex metrics as their names will only be fully known at runtime + for kind in PrometheusMetric::iter() { + if !kind.is_complex() { + if let Some(metric) = kind.to_registrable_metric() { + registry.register_metric(metric); + } + } + } + + NymNodePrometheusMetrics {} + } + + pub fn set(&self, metric: PrometheusMetric, value: i64) { + metric.set(value) + } + + pub fn set_float(&self, metric: PrometheusMetric, value: f64) { + metric.set_float(value) + } + + pub fn inc(&self, metric: PrometheusMetric) { + metric.inc() + } + + pub fn inc_by(&self, metric: PrometheusMetric, value: i64) { + metric.inc_by(value) + } + + pub fn observe_histogram(&self, metric: PrometheusMetric, value: f64) { + metric.observe_histogram(value) + } + + pub fn start_timer(&self, metric: PrometheusMetric) -> Option { + metric.start_timer() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use strum::IntoEnumIterator; + + #[test] + fn prometheus_metrics() { + // a sanity check for anyone adding new metrics. if this test fails, + // make sure any methods on `PrometheusMetric` enum don't need updating + // or require custom Display impl + assert_eq!(37, PrometheusMetric::COUNT) + } + + #[test] + fn every_variant_has_help_property() { + for variant in PrometheusMetric::iter() { + assert!(variant.get_str("help").is_some()) + } + } + + #[test] + fn prometheus_metrics_names() { + // make sure nothing changed in our serialisation + let simple = PrometheusMetric::MixnetIngressForwardPacketsReceived.to_string(); + assert_eq!("nym_node_mixnet_ingress_forward_packets_received", simple); + + let parameterised = + PrometheusMetric::EntryClientSessionsDurations { typ: "vpn".into() }.to_string(); + assert_eq!( + "nym_node_entry_client_sessions_durations_vpn", + parameterised + ) + } +} diff --git a/nym-node/src/config/metrics.rs b/nym-node/src/config/metrics.rs index 80f9dcea6d..5bdec5bdbf 100644 --- a/nym-node/src/config/metrics.rs +++ b/nym-node/src/config/metrics.rs @@ -25,6 +25,14 @@ pub struct Debug { #[serde(with = "humantime_serde")] pub stale_mixnet_metrics_cleaner_rate: Duration, + /// Specify the target rate of updating global prometheus counters. + #[serde(with = "humantime_serde")] + pub global_prometheus_counters_update_rate: Duration, + + /// Specify the target rate of updating egress packets pending delivery counter. + #[serde(with = "humantime_serde")] + pub pending_egress_packets_update_rate: Duration, + /// Specify the rate of updating clients sessions #[serde(with = "humantime_serde")] pub clients_sessions_update_rate: Duration, @@ -42,8 +50,10 @@ impl Debug { const DEFAULT_CONSOLE_LOGGING_INTERVAL: Duration = Duration::from_millis(60_000); const DEFAULT_LEGACY_MIXING_UPDATE_RATE: Duration = Duration::from_millis(30_000); const DEFAULT_AGGREGATOR_UPDATE_RATE: Duration = Duration::from_secs(5); - const DEFAULT_STALE_MIXNET_ETRICS_UPDATE_RATE: Duration = Duration::from_secs(3600); + const DEFAULT_STALE_MIXNET_METRICS_UPDATE_RATE: Duration = Duration::from_secs(3600); const DEFAULT_CLIENT_SESSIONS_UPDATE_RATE: Duration = Duration::from_secs(3600); + const GLOBAL_PROMETHEUS_COUNTERS_UPDATE_INTERVAL: Duration = Duration::from_secs(30); + const DEFAULT_PENDING_EGRESS_PACKETS_UPDATE_RATE: Duration = Duration::from_secs(30); } impl Default for Debug { @@ -53,7 +63,10 @@ impl Default for Debug { console_logging_update_interval: Self::DEFAULT_CONSOLE_LOGGING_INTERVAL, legacy_mixing_metrics_update_rate: Self::DEFAULT_LEGACY_MIXING_UPDATE_RATE, aggregator_update_rate: Self::DEFAULT_AGGREGATOR_UPDATE_RATE, - stale_mixnet_metrics_cleaner_rate: Self::DEFAULT_STALE_MIXNET_ETRICS_UPDATE_RATE, + stale_mixnet_metrics_cleaner_rate: Self::DEFAULT_STALE_MIXNET_METRICS_UPDATE_RATE, + global_prometheus_counters_update_rate: + Self::GLOBAL_PROMETHEUS_COUNTERS_UPDATE_INTERVAL, + pending_egress_packets_update_rate: Self::DEFAULT_PENDING_EGRESS_PACKETS_UPDATE_RATE, clients_sessions_update_rate: Self::DEFAULT_CLIENT_SESSIONS_UPDATE_RATE, } } diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 6b996f24be..8f0cb9f220 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -444,6 +444,7 @@ pub struct Http { /// An optional bearer token for accessing certain http endpoints. /// Currently only used for obtaining mixnode's stats. #[serde(default)] + #[serde(deserialize_with = "de_maybe_stringified")] pub access_token: Option, /// Specify whether basic system information should be exposed. diff --git a/nym-node/src/node/http/error.rs b/nym-node/src/node/http/error.rs index 8019c5fd58..251705f520 100644 --- a/nym-node/src/node/http/error.rs +++ b/nym-node/src/node/http/error.rs @@ -1,5 +1,5 @@ // Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use std::io; use std::net::SocketAddr; diff --git a/nym-node/src/node/http/router/api/v1/metrics/mod.rs b/nym-node/src/node/http/router/api/v1/metrics/mod.rs index 71a9760273..51af714434 100644 --- a/nym-node/src/node/http/router/api/v1/metrics/mod.rs +++ b/nym-node/src/node/http/router/api/v1/metrics/mod.rs @@ -10,7 +10,12 @@ use crate::node::http::state::metrics::MetricsAppState; use axum::extract::FromRef; use axum::routing::get; use axum::Router; +use nym_http_api_common::middleware::bearer_auth::AuthLayer; use nym_node_requests::routes::api::v1::metrics; +use nym_node_requests::routes::api::v1::metrics::prometheus_absolute; +use std::sync::Arc; +use tracing::info; +use zeroize::Zeroizing; pub mod legacy_mixing; pub mod packets_stats; @@ -21,16 +26,23 @@ pub mod wireguard; #[derive(Debug, Clone, Default)] pub struct Config { - // + pub bearer_token: Option>>, } #[allow(deprecated)] -pub(super) fn routes(_config: Config) -> Router +pub(super) fn routes(config: Config) -> Router where S: Send + Sync + 'static + Clone, MetricsAppState: FromRef, { - Router::new() + if config.bearer_token.is_none() { + info!( + "bearer token hasn't been set. '{}' route will not be exposed", + prometheus_absolute() + ) + } + + let router = Router::new() .route( metrics::LEGACY_MIXING, get(legacy_mixing::legacy_mixing_stats), @@ -38,6 +50,17 @@ where .route(metrics::PACKETS_STATS, get(packets_stats)) .route(metrics::WIREGUARD_STATS, get(wireguard_stats)) .route(metrics::SESSIONS, get(sessions_stats)) - .route(metrics::VERLOC, get(verloc_stats)) - .route(metrics::PROMETHEUS, get(prometheus_metrics)) + .route(metrics::VERLOC, get(verloc_stats)); + + let auth_middleware = config.bearer_token.map(AuthLayer::new); + + // don't expose prometheus route without bearer token set + if let Some(auth_middleware) = auth_middleware { + router.route( + metrics::PROMETHEUS, + get(prometheus_metrics).route_layer(auth_middleware), + ) + } else { + router + } } diff --git a/nym-node/src/node/http/router/api/v1/metrics/prometheus.rs b/nym-node/src/node/http/router/api/v1/metrics/prometheus.rs index 197cc5a1c8..2bd8a3356b 100644 --- a/nym-node/src/node/http/router/api/v1/metrics/prometheus.rs +++ b/nym-node/src/node/http/router/api/v1/metrics/prometheus.rs @@ -1,12 +1,6 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::http::state::metrics::MetricsAppState; -use axum::extract::State; -use axum::http::StatusCode; -use axum_extra::TypedHeader; -use headers::authorization::Bearer; -use headers::Authorization; use nym_metrics::metrics; /// Returns `prometheus` compatible metrics @@ -25,21 +19,7 @@ use nym_metrics::metrics; ("prometheus_token" = []) ) )] -pub(crate) async fn prometheus_metrics<'a>( - TypedHeader(authorization): TypedHeader>, - State(state): State, -) -> Result { - if authorization.token().is_empty() { - return Err(StatusCode::UNAUTHORIZED); - } - // TODO: is 500 the correct error code here? - let Some(metrics_key) = state.prometheus_access_token else { - return Err(StatusCode::INTERNAL_SERVER_ERROR); - }; - - if metrics_key != authorization.token() { - return Err(StatusCode::UNAUTHORIZED); - } - - Ok(metrics!()) +// the AuthLayer is protecting access to this endpoint +pub(crate) async fn prometheus_metrics() -> String { + metrics!() } diff --git a/nym-node/src/node/http/router/mod.rs b/nym-node/src/node/http/router/mod.rs index 449638c8d6..3d40e9a67e 100644 --- a/nym-node/src/node/http/router/mod.rs +++ b/nym-node/src/node/http/router/mod.rs @@ -20,6 +20,8 @@ use nym_node_requests::api::SignedHostInformation; use nym_node_requests::routes; use std::net::SocketAddr; use std::path::Path; +use std::sync::Arc; +use zeroize::Zeroizing; pub mod api; pub mod landing_page; @@ -115,6 +117,11 @@ impl HttpServerConfig { self.api.v1_config.authenticator.details = Some(authenticator); self } + + pub fn with_prometheus_bearer_token(mut self, bearer_token: Option) -> Self { + self.api.v1_config.metrics.bearer_token = bearer_token.map(|b| Arc::new(Zeroizing::new(b))); + self + } } pub struct NymNodeRouter { diff --git a/nym-node/src/node/http/state/metrics.rs b/nym-node/src/node/http/state/metrics.rs index c9156c021e..8b10fb0a30 100644 --- a/nym-node/src/node/http/state/metrics.rs +++ b/nym-node/src/node/http/state/metrics.rs @@ -9,8 +9,6 @@ pub use nym_verloc::measurements::metrics::SharedVerlocStats; #[derive(Clone)] pub struct MetricsAppState { - pub(crate) prometheus_access_token: Option, - pub(crate) metrics: NymNodeMetrics, pub(crate) verloc: SharedVerlocStats, diff --git a/nym-node/src/node/http/state/mod.rs b/nym-node/src/node/http/state/mod.rs index b9b58c0cce..0e2fa98786 100644 --- a/nym-node/src/node/http/state/mod.rs +++ b/nym-node/src/node/http/state/mod.rs @@ -24,17 +24,7 @@ impl AppState { // does it have to be? // also no. startup_time: Instant::now(), - metrics: MetricsAppState { - prometheus_access_token: None, - metrics, - verloc, - }, + metrics: MetricsAppState { metrics, verloc }, } } - - #[must_use] - pub fn with_metrics_key(mut self, bearer_token: impl Into>) -> Self { - self.metrics.prometheus_access_token = bearer_token.into(); - self - } } diff --git a/nym-node/src/node/metrics/aggregator.rs b/nym-node/src/node/metrics/aggregator.rs index c166eb0d3c..8f76b14edb 100644 --- a/nym-node/src/node/metrics/aggregator.rs +++ b/nym-node/src/node/metrics/aggregator.rs @@ -44,7 +44,7 @@ impl MetricsAggregator { self.event_sender.clone() } - pub fn register_handler(&mut self, handler: H, update_interval: Duration) + pub fn register_handler(&mut self, handler: H, update_interval: impl Into>) where H: MetricsHandler, { diff --git a/nym-node/src/node/metrics/events_listener.rs b/nym-node/src/node/metrics/events_listener.rs index 755fb6cc8b..939f19b3a9 100644 --- a/nym-node/src/node/metrics/events_listener.rs +++ b/nym-node/src/node/metrics/events_listener.rs @@ -1,2 +1,2 @@ // Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only diff --git a/nym-node/src/node/metrics/handler/client_sessions.rs b/nym-node/src/node/metrics/handler/client_sessions.rs index 8394eb1d89..2f2326c541 100644 --- a/nym-node/src/node/metrics/handler/client_sessions.rs +++ b/nym-node/src/node/metrics/handler/client_sessions.rs @@ -1,5 +1,5 @@ // Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::metrics::handler::{ MetricsHandler, OnStartMetricsHandler, OnUpdateMetricsHandler, @@ -10,6 +10,8 @@ use nym_gateway_stats_storage::error::StatsStorageError; use nym_gateway_stats_storage::models::{TicketType, ToSessionType}; use nym_node_metrics::entry::{ActiveSession, ClientSessions, FinishedSession}; use nym_node_metrics::events::GatewaySessionEvent; +use nym_node_metrics::prometheus_wrapper::PrometheusMetric::EntryClientSessionsDurations; +use nym_node_metrics::prometheus_wrapper::PROMETHEUS_METRICS; use nym_node_metrics::NymNodeMetrics; use nym_sphinx_types::DestinationAddressBytes; use time::{Date, Duration, OffsetDateTime}; @@ -53,6 +55,13 @@ impl GatewaySessionStatsHandler { ) -> Result<(), StatsStorageError> { if let Some(session) = self.storage.get_active_session(client).await? { if let Some(finished_session) = session.end_at(stop_time) { + PROMETHEUS_METRICS.observe_histogram( + EntryClientSessionsDurations { + typ: finished_session.typ.to_string(), + }, + finished_session.duration.as_secs_f64(), + ); + self.storage .insert_finished_session(self.current_day, finished_session) .await?; diff --git a/nym-node/src/node/metrics/handler/global_prometheus_updater/at_last_update.rs b/nym-node/src/node/metrics/handler/global_prometheus_updater/at_last_update.rs new file mode 100644 index 0000000000..af16be7ecb --- /dev/null +++ b/nym-node/src/node/metrics/handler/global_prometheus_updater/at_last_update.rs @@ -0,0 +1,219 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_node_metrics::mixnet::{EgressMixingStats, IngressMixingStats, MixingStats}; +use nym_node_metrics::wireguard::WireguardStats; +use nym_node_metrics::NymNodeMetrics; +use time::OffsetDateTime; + +// used to calculate traffic rates +#[derive(Debug)] +pub(crate) struct AtLastUpdate { + time: OffsetDateTime, + + mixnet: LastMixnet, + wireguard: LastWireguard, +} + +impl AtLastUpdate { + pub(crate) fn is_initial(&self) -> bool { + self.time == OffsetDateTime::UNIX_EPOCH + } + + pub(crate) fn rates(&self, previous: &Self) -> RateSinceUpdate { + let delta_secs = (self.time - previous.time).as_seconds_f64(); + + RateSinceUpdate { + mixnet: self.mixnet.rates(&previous.mixnet, delta_secs), + wireguard: self.wireguard.rates(&previous.wireguard, delta_secs), + } + } +} + +impl Default for AtLastUpdate { + fn default() -> Self { + AtLastUpdate { + time: OffsetDateTime::now_utc(), + mixnet: Default::default(), + wireguard: Default::default(), + } + } +} + +impl From<&NymNodeMetrics> for AtLastUpdate { + fn from(metrics: &NymNodeMetrics) -> Self { + AtLastUpdate { + time: OffsetDateTime::now_utc(), + mixnet: (&metrics.mixnet).into(), + wireguard: (&metrics.wireguard).into(), + } + } +} + +#[derive(Debug, Default)] +struct LastMixnet { + ingres: LastMixnetIngress, + egress: LastMixnetEgress, +} + +impl LastMixnet { + fn rates(&self, previous: &Self, time_delta_secs: f64) -> MixnetRateSinceUpdate { + MixnetRateSinceUpdate { + ingress: self.ingres.rates(&previous.ingres, time_delta_secs), + egress: self.egress.rates(&previous.egress, time_delta_secs), + } + } +} + +impl From<&MixingStats> for LastMixnet { + fn from(value: &MixingStats) -> Self { + LastMixnet { + ingres: (&value.ingress).into(), + egress: (&value.egress).into(), + } + } +} + +#[derive(Debug, Default)] +struct LastMixnetIngress { + forward_hop_packets_received: usize, + final_hop_packets_received: usize, + malformed_packets_received: usize, + excessive_delay_packets: usize, + forward_hop_packets_dropped: usize, + final_hop_packets_dropped: usize, +} + +impl LastMixnetIngress { + fn rates(&self, previous: &Self, time_delta_secs: f64) -> MixnetIngressRateSinceUpdate { + let forward_hop_packets_received_delta = + self.forward_hop_packets_received - previous.forward_hop_packets_received; + let final_hop_packets_received_delta = + self.final_hop_packets_received - previous.final_hop_packets_received; + let malformed_packets_received_delta = + self.malformed_packets_received - previous.malformed_packets_received; + let excessive_delay_packets_delta = + self.excessive_delay_packets - previous.excessive_delay_packets; + let forward_hop_packets_dropped_delta = + self.forward_hop_packets_dropped - previous.forward_hop_packets_dropped; + let final_hop_packets_dropped_delta = + self.final_hop_packets_dropped - previous.final_hop_packets_dropped; + + MixnetIngressRateSinceUpdate { + forward_hop_packets_received_sec: forward_hop_packets_received_delta as f64 + / time_delta_secs, + final_hop_packets_received_sec: final_hop_packets_received_delta as f64 + / time_delta_secs, + malformed_packets_received_sec: malformed_packets_received_delta as f64 + / time_delta_secs, + excessive_delay_packets_sec: excessive_delay_packets_delta as f64 / time_delta_secs, + forward_hop_packets_dropped_sec: forward_hop_packets_dropped_delta as f64 + / time_delta_secs, + final_hop_packets_dropped_sec: final_hop_packets_dropped_delta as f64 / time_delta_secs, + } + } +} + +impl From<&IngressMixingStats> for LastMixnetIngress { + fn from(value: &IngressMixingStats) -> Self { + LastMixnetIngress { + forward_hop_packets_received: value.forward_hop_packets_received(), + final_hop_packets_received: value.final_hop_packets_received(), + malformed_packets_received: value.malformed_packets_received(), + excessive_delay_packets: value.excessive_delay_packets(), + forward_hop_packets_dropped: value.forward_hop_packets_dropped(), + final_hop_packets_dropped: value.final_hop_packets_dropped(), + } + } +} + +#[derive(Debug, Default)] +struct LastMixnetEgress { + forward_hop_packets_sent: usize, + ack_packets_sent: usize, + forward_hop_packets_dropped: usize, +} + +impl LastMixnetEgress { + fn rates(&self, previous: &Self, time_delta_secs: f64) -> MixnetEgressRateSinceUpdate { + let forward_hop_packets_sent_delta = + self.forward_hop_packets_sent - previous.forward_hop_packets_sent; + let ack_packets_sent_delta = self.ack_packets_sent - previous.ack_packets_sent; + let forward_hop_packets_dropped_delta = + self.forward_hop_packets_dropped - previous.forward_hop_packets_dropped; + + MixnetEgressRateSinceUpdate { + forward_hop_packets_sent_sec: forward_hop_packets_sent_delta as f64 / time_delta_secs, + ack_packets_sent_sec: ack_packets_sent_delta as f64 / time_delta_secs, + forward_hop_packets_dropped_sec: forward_hop_packets_dropped_delta as f64 + / time_delta_secs, + } + } +} + +impl From<&EgressMixingStats> for LastMixnetEgress { + fn from(value: &EgressMixingStats) -> Self { + LastMixnetEgress { + forward_hop_packets_sent: value.forward_hop_packets_sent(), + ack_packets_sent: value.ack_packets_sent(), + forward_hop_packets_dropped: value.forward_hop_packets_dropped(), + } + } +} + +#[derive(Debug, Default)] +struct LastWireguard { + bytes_tx: usize, + bytes_rx: usize, +} + +impl LastWireguard { + fn rates(&self, previous: &Self, time_delta_secs: f64) -> WireguardRateSinceUpdate { + let bytes_tx_delta = self.bytes_tx - previous.bytes_tx; + let bytes_rx_delta = self.bytes_rx - previous.bytes_rx; + + WireguardRateSinceUpdate { + bytes_tx_sec: bytes_tx_delta as f64 / time_delta_secs, + bytes_rx_sec: bytes_rx_delta as f64 / time_delta_secs, + } + } +} + +impl From<&WireguardStats> for LastWireguard { + fn from(value: &WireguardStats) -> Self { + LastWireguard { + bytes_tx: value.bytes_tx(), + bytes_rx: value.bytes_rx(), + } + } +} + +pub(crate) struct RateSinceUpdate { + pub(crate) mixnet: MixnetRateSinceUpdate, + pub(crate) wireguard: WireguardRateSinceUpdate, +} + +pub(crate) struct MixnetRateSinceUpdate { + pub(crate) ingress: MixnetIngressRateSinceUpdate, + pub(crate) egress: MixnetEgressRateSinceUpdate, +} + +pub(crate) struct MixnetIngressRateSinceUpdate { + pub(crate) forward_hop_packets_received_sec: f64, + pub(crate) final_hop_packets_received_sec: f64, + pub(crate) malformed_packets_received_sec: f64, + pub(crate) excessive_delay_packets_sec: f64, + pub(crate) forward_hop_packets_dropped_sec: f64, + pub(crate) final_hop_packets_dropped_sec: f64, +} + +pub(crate) struct MixnetEgressRateSinceUpdate { + pub(crate) forward_hop_packets_sent_sec: f64, + pub(crate) ack_packets_sent_sec: f64, + pub(crate) forward_hop_packets_dropped_sec: f64, +} + +pub(crate) struct WireguardRateSinceUpdate { + pub(crate) bytes_tx_sec: f64, + pub(crate) bytes_rx_sec: f64, +} diff --git a/nym-node/src/node/metrics/handler/global_prometheus_updater/mod.rs b/nym-node/src/node/metrics/handler/global_prometheus_updater/mod.rs new file mode 100644 index 0000000000..81c61d9db8 --- /dev/null +++ b/nym-node/src/node/metrics/handler/global_prometheus_updater/mod.rs @@ -0,0 +1,223 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::metrics::handler::global_prometheus_updater::at_last_update::AtLastUpdate; +use crate::node::metrics::handler::{ + MetricsHandler, OnStartMetricsHandler, OnUpdateMetricsHandler, +}; +use async_trait::async_trait; +use nym_node_metrics::prometheus_wrapper::{ + NymNodePrometheusMetrics, PrometheusMetric, PROMETHEUS_METRICS, +}; +use nym_node_metrics::NymNodeMetrics; + +mod at_last_update; + +// it can be anything, we just need a unique type_id to register our handler +pub struct GlobalPrometheusData; + +pub struct PrometheusGlobalNodeMetricsRegistryUpdater { + metrics: NymNodeMetrics, + prometheus_wrapper: &'static NymNodePrometheusMetrics, + at_last_update: AtLastUpdate, +} + +impl PrometheusGlobalNodeMetricsRegistryUpdater { + pub(crate) fn new(metrics: NymNodeMetrics) -> Self { + Self { + metrics, + prometheus_wrapper: &PROMETHEUS_METRICS, + at_last_update: Default::default(), + } + } +} + +#[async_trait] +impl OnStartMetricsHandler for PrometheusGlobalNodeMetricsRegistryUpdater {} + +#[async_trait] +impl OnUpdateMetricsHandler for PrometheusGlobalNodeMetricsRegistryUpdater { + async fn on_update(&mut self) { + let entry_guard = self.metrics.entry.client_sessions().await; + use PrometheusMetric::*; + + // # MIXNET + // ## INGRESS + self.prometheus_wrapper.set( + MixnetIngressForwardPacketsReceived, + self.metrics.mixnet.ingress.forward_hop_packets_received() as i64, + ); + self.prometheus_wrapper.set( + MixnetIngressFinalHopPacketsReceived, + self.metrics.mixnet.ingress.final_hop_packets_received() as i64, + ); + self.prometheus_wrapper.set( + MixnetIngressMalformedPacketsReceived, + self.metrics.mixnet.ingress.malformed_packets_received() as i64, + ); + self.prometheus_wrapper.set( + MixnetIngressExcessiveDelayPacketsReceived, + self.metrics.mixnet.ingress.excessive_delay_packets() as i64, + ); + self.prometheus_wrapper.set( + MixnetEgressForwardPacketsDropped, + self.metrics.mixnet.ingress.forward_hop_packets_dropped() as i64, + ); + self.prometheus_wrapper.set( + MixnetIngressFinalHopPacketsDropped, + self.metrics.mixnet.ingress.final_hop_packets_dropped() as i64, + ); + + // ## EGRESS + self.prometheus_wrapper.set( + MixnetEgressStoredOnDiskFinalHopPackets, + self.metrics.mixnet.egress.disk_persisted_packets() as i64, + ); + self.prometheus_wrapper.set( + MixnetEgressForwardPacketsSent, + self.metrics.mixnet.egress.forward_hop_packets_sent() as i64, + ); + self.prometheus_wrapper.set( + MixnetEgressAckSent, + self.metrics.mixnet.egress.ack_packets_sent() as i64, + ); + self.prometheus_wrapper.set( + MixnetEgressForwardPacketsDropped, + self.metrics.mixnet.egress.forward_hop_packets_dropped() as i64, + ); + + // # ENTRY + self.prometheus_wrapper.set( + EntryClientUniqueUsers, + entry_guard.unique_users.len() as i64, + ); + self.prometheus_wrapper.set( + EntryClientSessionsStarted, + entry_guard.sessions_started as i64, + ); + self.prometheus_wrapper.set( + EntryClientSessionsFinished, + entry_guard.finished_sessions.len() as i64, + ); + + // # WIREGUARD + self.prometheus_wrapper + .set(WireguardBytesRx, self.metrics.wireguard.bytes_rx() as i64); + self.prometheus_wrapper + .set(WireguardBytesTx, self.metrics.wireguard.bytes_tx() as i64); + self.prometheus_wrapper.set( + WireguardTotalPeers, + self.metrics.wireguard.total_peers() as i64, + ); + self.prometheus_wrapper.set( + WireguardActivePeers, + self.metrics.wireguard.active_peers() as i64, + ); + + // # NETWORK + self.prometheus_wrapper.set( + NetworkActiveIngressMixnetConnections, + self.metrics + .network + .active_ingress_mixnet_connections_count() as i64, + ); + self.prometheus_wrapper.set( + NetworkActiveIngressWebSocketConnections, + self.metrics + .network + .active_ingress_websocket_connections_count() as i64, + ); + self.prometheus_wrapper.set( + NetworkActiveIngressWebSocketConnections, + self.metrics + .network + .active_egress_mixnet_connections_count() as i64, + ); + + // # PROCESS + self.prometheus_wrapper.set( + ProcessForwardHopPacketsBeingDelayed, + self.metrics + .process + .forward_hop_packets_being_delayed_count() as i64, + ); + self.prometheus_wrapper.set( + ProcessPacketForwarderQueueSize, + self.metrics.process.packet_forwarder_queue_size() as i64, + ); + self.prometheus_wrapper.set( + ProcessFinalHopPacketsPendingDelivery, + self.metrics + .process + .final_hop_packets_pending_delivery_count() as i64, + ); + self.prometheus_wrapper.set( + ProcessForwardHopPacketsPendingDelivery, + self.metrics + .process + .forward_hop_packets_pending_delivery_count() as i64, + ); + + let updated = AtLastUpdate::from(&self.metrics); + + // # RATES + if !self.at_last_update.is_initial() { + let diff = updated.rates(&self.at_last_update); + + self.prometheus_wrapper.set_float( + MixnetIngressForwardPacketsReceivedRate, + diff.mixnet.ingress.forward_hop_packets_received_sec, + ); + self.prometheus_wrapper.set_float( + MixnetIngressFinalHopPacketsReceivedRate, + diff.mixnet.ingress.final_hop_packets_received_sec, + ); + self.prometheus_wrapper.set_float( + MixnetIngressMalformedPacketsReceivedRate, + diff.mixnet.ingress.malformed_packets_received_sec, + ); + self.prometheus_wrapper.set_float( + MixnetIngressExcessiveDelayPacketsReceivedRate, + diff.mixnet.ingress.excessive_delay_packets_sec, + ); + self.prometheus_wrapper.set_float( + MixnetIngressForwardPacketsDroppedRate, + diff.mixnet.ingress.forward_hop_packets_dropped_sec, + ); + self.prometheus_wrapper.set_float( + MixnetIngressFinalHopPacketsDroppedRate, + diff.mixnet.ingress.final_hop_packets_dropped_sec, + ); + + // ## EGRESS + self.prometheus_wrapper.set_float( + MixnetEgressForwardPacketsSendRate, + diff.mixnet.egress.forward_hop_packets_sent_sec, + ); + self.prometheus_wrapper.set_float( + MixnetEgressAckSendRate, + diff.mixnet.egress.ack_packets_sent_sec, + ); + self.prometheus_wrapper.set_float( + MixnetEgressForwardPacketsDroppedRate, + diff.mixnet.egress.forward_hop_packets_dropped_sec, + ); + + // # WIREGUARD + self.prometheus_wrapper + .set_float(WireguardBytesRxRate, diff.wireguard.bytes_rx_sec); + self.prometheus_wrapper + .set_float(WireguardBytesTxRate, diff.wireguard.bytes_tx_sec); + } + self.at_last_update = updated; + } +} + +#[async_trait] +impl MetricsHandler for PrometheusGlobalNodeMetricsRegistryUpdater { + type Events = GlobalPrometheusData; + + async fn handle_event(&mut self, _event: Self::Events) { + panic!("this should have never been called! MetricsHandler has been incorrectly called on PrometheusNodeMetricsRegistryUpdater") + } +} diff --git a/nym-node/src/node/metrics/handler/legacy_packet_data.rs b/nym-node/src/node/metrics/handler/legacy_packet_data.rs index 4b649e831a..6240993797 100644 --- a/nym-node/src/node/metrics/handler/legacy_packet_data.rs +++ b/nym-node/src/node/metrics/handler/legacy_packet_data.rs @@ -1,5 +1,5 @@ // Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::metrics::handler::{ MetricsHandler, OnStartMetricsHandler, OnUpdateMetricsHandler, diff --git a/nym-node/src/node/metrics/handler/mod.rs b/nym-node/src/node/metrics/handler/mod.rs index 025b1419b7..764acc0ff9 100644 --- a/nym-node/src/node/metrics/handler/mod.rs +++ b/nym-node/src/node/metrics/handler/mod.rs @@ -1,5 +1,5 @@ // Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use async_trait::async_trait; use std::any; @@ -9,8 +9,11 @@ use tokio::time::Instant; use tracing::trace; pub(crate) mod client_sessions; +pub(crate) mod global_prometheus_updater; pub(crate) mod legacy_packet_data; pub(crate) mod mixnet_data_cleaner; +pub(crate) mod pending_egress_packets_updater; +pub(crate) mod prometheus_events_handler; pub(crate) trait RegistrableHandler: Downcast + OnStartMetricsHandler + OnUpdateMetricsHandler + Send + Sync + 'static @@ -63,23 +66,23 @@ pub(crate) trait OnStartMetricsHandler { #[async_trait] pub(crate) trait OnUpdateMetricsHandler { - async fn on_update(&mut self); + async fn on_update(&mut self) {} } pub(crate) struct HandlerWrapper { handler: Box>, - update_interval: Duration, + update_interval: Option, last_updated: Instant, } impl HandlerWrapper { - pub fn new(update_interval: Duration, handler: U) -> Self + pub fn new(update_interval: impl Into>, handler: U) -> Self where U: MetricsHandler, { HandlerWrapper { handler: Box::new(handler), - update_interval, + update_interval: update_interval.into(), last_updated: Instant::now(), } } @@ -107,11 +110,15 @@ impl OnStartMetricsHandler for HandlerWrapper { #[async_trait] impl OnUpdateMetricsHandler for HandlerWrapper { async fn on_update(&mut self) { + let Some(update_interval) = self.update_interval else { + return; + }; + let name = any::type_name::(); trace!("on update for handler for events of type {name}"); let elapsed = self.last_updated.elapsed(); - if elapsed < self.update_interval { + if elapsed < update_interval { trace!("too soon for updates"); return; } diff --git a/nym-node/src/node/metrics/handler/pending_egress_packets_updater.rs b/nym-node/src/node/metrics/handler/pending_egress_packets_updater.rs new file mode 100644 index 0000000000..d1fde40f70 --- /dev/null +++ b/nym-node/src/node/metrics/handler/pending_egress_packets_updater.rs @@ -0,0 +1,60 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::metrics::handler::{ + MetricsHandler, OnStartMetricsHandler, OnUpdateMetricsHandler, +}; +use async_trait::async_trait; +use nym_gateway::node::ActiveClientsStore; +use nym_mixnet_client::client::ActiveConnections; +use nym_node_metrics::NymNodeMetrics; + +// it can be anything, we just need a unique type_id to register our handler +pub struct PendingEgressPackets; + +pub struct PendingEgressPacketsUpdater { + metrics: NymNodeMetrics, + active_websocket_clients: ActiveClientsStore, + active_mixnet_connections: ActiveConnections, +} + +impl PendingEgressPacketsUpdater { + pub(crate) fn new( + metrics: NymNodeMetrics, + active_clients: ActiveClientsStore, + active_mixnet_connections: ActiveConnections, + ) -> Self { + PendingEgressPacketsUpdater { + metrics, + active_websocket_clients: active_clients, + active_mixnet_connections, + } + } +} + +#[async_trait] +impl OnStartMetricsHandler for PendingEgressPacketsUpdater {} + +#[async_trait] +impl OnUpdateMetricsHandler for PendingEgressPacketsUpdater { + async fn on_update(&mut self) { + let pending_final = self.active_websocket_clients.pending_packets(); + self.metrics + .process + .update_final_hop_packets_pending_delivery(pending_final); + + let pending_forward = self.active_mixnet_connections.pending_packets(); + self.metrics + .process + .update_forward_hop_packets_pending_delivery(pending_forward) + } +} + +#[async_trait] +impl MetricsHandler for PendingEgressPacketsUpdater { + type Events = PendingEgressPackets; + + async fn handle_event(&mut self, _event: Self::Events) { + panic!("this should have never been called! MetricsHandler has been incorrectly called on PendingEgressPacketsUpdater") + } +} diff --git a/nym-node/src/node/metrics/handler/prometheus_events_handler.rs b/nym-node/src/node/metrics/handler/prometheus_events_handler.rs new file mode 100644 index 0000000000..28a1696278 --- /dev/null +++ b/nym-node/src/node/metrics/handler/prometheus_events_handler.rs @@ -0,0 +1,6 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +// pub struct PrometheusEventsHandler { +// // +// } diff --git a/nym-node/src/node/mixnet/handler.rs b/nym-node/src/node/mixnet/handler.rs index 6cfb7d50d1..e645636c96 100644 --- a/nym-node/src/node/mixnet/handler.rs +++ b/nym-node/src/node/mixnet/handler.rs @@ -112,7 +112,14 @@ impl ConnectionHandler { .await { Err(err) => error!("Failed to store client data - {err}"), - Ok(_) => trace!("Stored packet for {client}"), + Ok(_) => { + self.shared + .metrics + .mixnet + .egress + .add_disk_persisted_packet(); + trace!("Stored packet for {client}") + } } } Ok(_) => trace!("Pushed received packet to {client}"), diff --git a/nym-node/src/node/mixnet/packet_forwarding.rs b/nym-node/src/node/mixnet/packet_forwarding.rs index bcd51a52a9..5e70841c86 100644 --- a/nym-node/src/node/mixnet/packet_forwarding.rs +++ b/nym-node/src/node/mixnet/packet_forwarding.rs @@ -1,5 +1,5 @@ // Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use futures::StreamExt; use nym_mixnet_client::forwarder::{ @@ -80,7 +80,7 @@ impl PacketForwarder { C: SendWithoutResponse, { let delayed_packet = packet.into_inner(); - self.forward_packet(delayed_packet) + self.forward_packet(delayed_packet); } fn handle_new_packet(&mut self, new_packet: PacketToForward) @@ -102,6 +102,18 @@ impl PacketForwarder { } } + fn update_queue_len_metric(&self) { + self.metrics + .process + .update_forward_hop_packets_being_delayed(self.delay_queue.len()); + } + + fn update_channel_size_metric(&self, channel_size: usize) { + self.metrics + .process + .update_packet_forwarder_queue_size(channel_size) + } + pub async fn run(&mut self) where C: SendWithoutResponse, @@ -125,18 +137,21 @@ impl PacketForwarder { // and hence it can't happen that ALL senders are dropped #[allow(clippy::unwrap_used)] self.handle_new_packet(new_packet.unwrap()); + let channel_len = self.packet_sender.len(); if processed % 1000 == 0 { - let queue_len = self.packet_sender.len(); - match queue_len { + match channel_len { n if n > 200 => error!("there are currently {n} mix packets waiting to get forwarded!"), n if n > 50 => warn!("there are currently {n} mix packets waiting to get forwarded"), n => trace!("there are currently {n} mix packets waiting to get forwarded"), } } - + self.update_channel_size_metric(channel_len); processed += 1; } } + + // update the metrics on either new packet being inserted or packet being removed + self.update_queue_len_metric(); } trace!("PacketForwarder: Exiting"); } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index b4e8824eaa..09d349dd5a 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -21,8 +21,10 @@ use crate::node::http::{HttpServerConfig, NymNodeHttpServer, NymNodeRouter}; use crate::node::metrics::aggregator::MetricsAggregator; use crate::node::metrics::console_logger::ConsoleLogger; use crate::node::metrics::handler::client_sessions::GatewaySessionStatsHandler; +use crate::node::metrics::handler::global_prometheus_updater::PrometheusGlobalNodeMetricsRegistryUpdater; use crate::node::metrics::handler::legacy_packet_data::LegacyMixingStatsUpdater; use crate::node::metrics::handler::mixnet_data_cleaner::MixnetMetricsCleaner; +use crate::node::metrics::handler::pending_egress_packets_updater::PendingEgressPacketsUpdater; use crate::node::mixnet::packet_forwarding::PacketForwarder; use crate::node::mixnet::shared::ProcessingConfig; use crate::node::mixnet::SharedFinalHopData; @@ -30,6 +32,7 @@ use crate::node::shared_topology::NymNodeTopologyProvider; use nym_bin_common::bin_info; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder}; +use nym_mixnet_client::client::ActiveConnections; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_requester::{ set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, @@ -743,7 +746,8 @@ impl NymNode { .with_authenticator_details(auth_details) .with_used_exit_policy(exit_policy_details) .with_description(self.description.clone()) - .with_auxiliary_details(auxiliary_details); + .with_auxiliary_details(auxiliary_details) + .with_prometheus_bearer_token(self.config.http.access_token.clone()); if self.config.http.expose_system_info { config = config.with_system_info(get_system_info( @@ -764,8 +768,7 @@ impl NymNode { config.api.v1_config.node.roles.ip_packet_router_enabled = true; } - let app_state = AppState::new(self.metrics.clone(), self.verloc_stats.clone()) - .with_metrics_key(self.config.http.access_token.clone()); + let app_state = AppState::new(self.metrics.clone(), self.verloc_stats.clone()); Ok(NymNodeRouter::new(config, app_state) .build_server(&self.config.http.bind_address) @@ -836,7 +839,12 @@ impl NymNode { tokio::spawn(async move { verloc_measurer.run().await }); } - pub(crate) fn setup_metrics_backend(&self, shutdown: TaskClient) -> MetricEventsSender { + pub(crate) fn setup_metrics_backend( + &self, + active_clients_store: ActiveClientsStore, + active_egress_mixnet_connections: ActiveConnections, + shutdown: TaskClient, + ) -> MetricEventsSender { info!("setting up node metrics..."); // aggregator (to listen for any metrics events) @@ -862,12 +870,35 @@ impl NymNode { self.config.metrics.debug.clients_sessions_update_rate, ); - // handler for periodically cleaning up stale recipient/sender darta + // handler for periodically cleaning up stale recipient/sender data metrics_aggregator.register_handler( MixnetMetricsCleaner::new(self.metrics.clone()), self.config.metrics.debug.stale_mixnet_metrics_cleaner_rate, ); + // handler for updating the value of forward/final hop packets pending delivery + metrics_aggregator.register_handler( + PendingEgressPacketsUpdater::new( + self.metrics.clone(), + active_clients_store, + active_egress_mixnet_connections, + ), + self.config.metrics.debug.pending_egress_packets_update_rate, + ); + + // handler for updating the prometheus registry from the global atomic metrics counters + // such as number of packets received + metrics_aggregator.register_handler( + PrometheusGlobalNodeMetricsRegistryUpdater::new(self.metrics.clone()), + self.config + .metrics + .debug + .global_prometheus_counters_update_rate, + ); + + // handler for handling prometheus metrics events + // metrics_aggregator.register_handler(PrometheusEventsHandler{}, None); + // note: we're still measuring things such as number of mixed packets, // but since they're stored as atomic integers, they are incremented directly at source // rather than going through event pipeline @@ -901,7 +932,7 @@ impl NymNode { &self, active_clients_store: &ActiveClientsStore, shutdown: TaskClient, - ) -> MixForwardingSender { + ) -> (MixForwardingSender, ActiveConnections) { let processing_config = ProcessingConfig::new(&self.config); // we're ALWAYS listening for mixnet packets, either for forward or final hops (or both) @@ -918,7 +949,13 @@ impl NymNode { self.config.mixnet.debug.initial_connection_timeout, self.config.mixnet.debug.maximum_connection_buffer_size, ); - let mixnet_client = nym_mixnet_client::Client::new(mixnet_client_config); + let mixnet_client = nym_mixnet_client::Client::new( + mixnet_client_config, + self.metrics + .network + .active_egress_mixnet_connections_counter(), + ); + let active_connections = mixnet_client.active_connections(); let mut packet_forwarder = PacketForwarder::new( mixnet_client, @@ -943,7 +980,7 @@ impl NymNode { ); mixnet::Listener::new(self.config.mixnet.bind_address, shared).start(); - mix_packet_sender + (mix_packet_sender, active_connections) } pub(crate) async fn run(mut self) -> Result<(), NymNodeError> { @@ -973,14 +1010,19 @@ impl NymNode { self.start_verloc_measurements(task_manager.subscribe_named("verloc-measurements")); - let metrics_sender = self.setup_metrics_backend(task_manager.subscribe_named("metrics")); let active_clients_store = ActiveClientsStore::new(); - let mix_packet_sender = self.start_mixnet_listener( + let (mix_packet_sender, active_egress_mixnet_connections) = self.start_mixnet_listener( &active_clients_store, task_manager.subscribe_named("mixnet-traffic"), ); + let metrics_sender = self.setup_metrics_backend( + active_clients_store.clone(), + active_egress_mixnet_connections, + task_manager.subscribe_named("metrics"), + ); + self.start_gateway_tasks( metrics_sender, active_clients_store, diff --git a/nym-node/src/node/shared_topology.rs b/nym-node/src/node/shared_topology.rs index 594fb5f4fb..756a4895c4 100644 --- a/nym-node/src/node/shared_topology.rs +++ b/nym-node/src/node/shared_topology.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use nym_gateway::node::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; +use nym_node_metrics::prometheus_wrapper::{PrometheusMetric, PROMETHEUS_METRICS}; use nym_topology::{gateway, NymTopology, TopologyProvider}; use std::sync::Arc; use std::time::Duration; @@ -93,6 +94,10 @@ impl TopologyProvider for NymNodeTopologyProvider { if let Some(cached) = guard.cached_topology() { return Some(cached); } + + // the observation will be included on drop + let _timer = + PROMETHEUS_METRICS.start_timer(PrometheusMetric::ProcessTopologyQueryResolutionLatency); guard.update_cache().await } } diff --git a/service-providers/ip-packet-router/src/error.rs b/service-providers/ip-packet-router/src/error.rs index 28e21e6edd..7c8a4590d5 100644 --- a/service-providers/ip-packet-router/src/error.rs +++ b/service-providers/ip-packet-router/src/error.rs @@ -29,7 +29,7 @@ pub enum IpPacketRouterError { #[error("failed to connect to mixnet: {source}")] FailedToConnectToMixnet { source: nym_sdk::Error }, - #[error("the entity wrapping the network requester has disconnected")] + #[error("the entity wrapping the ip packet router has disconnected")] DisconnectedParent, #[error("received packet has an invalid version: {0}")] diff --git a/service-providers/ip-packet-router/src/request_filter/mod.rs b/service-providers/ip-packet-router/src/request_filter/mod.rs index c11fb6c9d6..599a00a1d2 100644 --- a/service-providers/ip-packet-router/src/request_filter/mod.rs +++ b/service-providers/ip-packet-router/src/request_filter/mod.rs @@ -56,13 +56,10 @@ impl RequestFilter { pub(crate) async fn check_address(&self, address: &SocketAddr) -> bool { match &*self.inner { RequestFilterInner::ExitPolicy { policy_filter } => { - match policy_filter.check(address).await { - Err(err) => { - warn!("failed to validate '{address}' against the exit policy: {err}"); - false - } - Ok(res) => res, - } + policy_filter.check(address).await.unwrap_or_else(|err| { + warn!("failed to validate '{address}' against the exit policy: {err}"); + false + }) } } } From f6a2f62ea9fefc5204852df259edde9193f39e6c Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Wed, 8 Jan 2025 09:28:48 +0100 Subject: [PATCH 65/80] bump versions of binaries --- Cargo.lock | 16 ++++++++-------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-node/Cargo.toml | 2 +- service-providers/network-requester/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- tools/nymvisor/Cargo.toml | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0c33bb62b..521303658e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2416,7 +2416,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.43" +version = "1.1.44" dependencies = [ "chrono", "clap 4.5.20", @@ -4393,7 +4393,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.47" +version = "1.1.48" dependencies = [ "anyhow", "async-trait", @@ -4642,7 +4642,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.45" +version = "1.1.46" dependencies = [ "anyhow", "base64 0.22.1", @@ -4725,7 +4725,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.45" +version = "1.1.46" dependencies = [ "bs58", "clap 4.5.20", @@ -5812,7 +5812,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.46" +version = "1.1.47" dependencies = [ "addr", "anyhow", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.2.0" +version = "1.2.1" dependencies = [ "anyhow", "async-trait", @@ -6235,7 +6235,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.45" +version = "1.1.46" dependencies = [ "bs58", "clap 4.5.20", @@ -6837,7 +6837,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.10" +version = "0.1.11" dependencies = [ "anyhow", "bytes", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 03eb55186e..5f95bbb088 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.45" +version = "1.1.46" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 4363c56387..d538cd5b75 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.45" +version = "1.1.46" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 6fe9830dae..fd2880c4fd 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.43" +version = "1.1.44" edition = "2021" license.workspace = true diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 27814af1db..e28fea6309 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.47" +version = "1.1.48" authors.workspace = true edition = "2021" rust-version.workspace = true diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 75cfde1b15..8d78e6a3dc 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.2.0" +version = "1.2.1" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 1508c7d775..2f14ad8021 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.46" +version = "1.1.47" authors.workspace = true edition.workspace = true rust-version = "1.70" diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index c156e28679..8b4583dcbc 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.45" +version = "1.1.46" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index d44e9fc12b..d1cde0c60d 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.10" +version = "0.1.11" authors.workspace = true repository.workspace = true homepage.workspace = true From 78f45012dbabec35b69eb686c94216e5011ef292 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Wed, 8 Jan 2025 09:44:14 +0100 Subject: [PATCH 66/80] amend 250gb limit --- common/authenticator-requests/src/v4/registration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/authenticator-requests/src/v4/registration.rs b/common/authenticator-requests/src/v4/registration.rs index 2595922f00..d6e2ee9682 100644 --- a/common/authenticator-requests/src/v4/registration.rs +++ b/common/authenticator-requests/src/v4/registration.rs @@ -28,7 +28,7 @@ pub type HmacSha256 = Hmac; pub type Nonce = u64; pub type Taken = Option; -pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB +pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct IpPair { From b47a742dd09f8e2b887dcfc736f5c1585afd561a Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Wed, 8 Jan 2025 10:37:48 +0100 Subject: [PATCH 67/80] update nym-node binary version --- Cargo.lock | 2 +- nym-node/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 521303658e..c2e25e992f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.2.1" +version = "1.2.2" dependencies = [ "anyhow", "async-trait", diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 8d78e6a3dc..8b04ce3387 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.2.1" +version = "1.2.2" authors.workspace = true repository.workspace = true homepage.workspace = true From 836a93cd967ea198085acf07af17c9a34beadbcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 8 Jan 2025 11:26:40 +0100 Subject: [PATCH 68/80] fixed client session histogram buckets (#5316) --- nym-node/nym-node-metrics/src/prometheus_wrapper.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-node/nym-node-metrics/src/prometheus_wrapper.rs b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs index 2d61ee55c4..cfee39ee3e 100644 --- a/nym-node/nym-node-metrics/src/prometheus_wrapper.rs +++ b/nym-node/nym-node-metrics/src/prometheus_wrapper.rs @@ -18,8 +18,8 @@ const CLIENT_SESSION_DURATION_BUCKETS: &[f64] = &[ 900., // 15min - 1h 3600., // 1h - 12h 43200., // 12h - 23.5h - 88200., // 23.5h - 24.5h - 86400., // 24.5h - 72h + 84600., // 23.5h - 24.5h + 88200., // 24.5h - 72h 259200., // 72h+ (implicitly) ]; From 7c1c13e139dc055b593885fdfaa3f1a2d1209a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 9 Jan 2025 10:02:37 +0100 Subject: [PATCH 69/80] reduce log severity for number of packets being delayed (#5321) --- nym-node/src/node/mixnet/packet_forwarding.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-node/src/node/mixnet/packet_forwarding.rs b/nym-node/src/node/mixnet/packet_forwarding.rs index 5e70841c86..1c0c38d3a7 100644 --- a/nym-node/src/node/mixnet/packet_forwarding.rs +++ b/nym-node/src/node/mixnet/packet_forwarding.rs @@ -140,8 +140,8 @@ impl PacketForwarder { let channel_len = self.packet_sender.len(); if processed % 1000 == 0 { match channel_len { - n if n > 200 => error!("there are currently {n} mix packets waiting to get forwarded!"), - n if n > 50 => warn!("there are currently {n} mix packets waiting to get forwarded"), + n if n > 1000 => error!("there are currently {n} mix packets waiting to get forwarded - the node seems to be significantly overloaded!"), + n if n > 500 => warn!("there are currently {n} mix packets waiting to get forwarded - is the node overloaded?"), n => trace!("there are currently {n} mix packets waiting to get forwarded"), } } From a46245ffe3f788f752aa0bd8c5aefb63bfe57ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 9 Jan 2025 10:02:52 +0100 Subject: [PATCH 70/80] feat: warn users if node is run in exit mode only (#5320) * added 'full-gateway' nymnode mode to enable both entry and exit at the same time * warning for running node in exit mode only --- nym-node/src/cli/commands/run/mod.rs | 4 ++++ nym-node/src/config/mod.rs | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/nym-node/src/cli/commands/run/mod.rs b/nym-node/src/cli/commands/run/mod.rs index bcaa1e5106..9dfd1061f0 100644 --- a/nym-node/src/cli/commands/run/mod.rs +++ b/nym-node/src/cli/commands/run/mod.rs @@ -83,6 +83,10 @@ pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> { warn!("this node is going to run without mixnode or gateway support! consider providing `mode` value"); } + if config.modes.standalone_exit() { + warn!("this node is going to run in EXIT gateway mode only - it will not be able to accept client traffic and thus will NOT be eligible for any rewards. consider running it alongside `entry` (or `full-gateway`) mode") + } + if config.host.public_ips.is_empty() { return Err(NymNodeError::NoPublicIps); } diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 8f0cb9f220..c9d614e6b1 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -77,6 +77,9 @@ pub enum NodeMode { #[clap(alias = "exit")] ExitGateway, + + // entry + exit + FullGateway, } impl Display for NodeMode { @@ -85,6 +88,7 @@ impl Display for NodeMode { NodeMode::Mixnode => "mixnode".fmt(f), NodeMode::EntryGateway => "entry-gateway".fmt(f), NodeMode::ExitGateway => "exit-gateway".fmt(f), + NodeMode::FullGateway => "full-gateway".fmt(f), } } } @@ -117,11 +121,16 @@ impl NodeModes { self.mixnode || self.entry || self.exit } + pub fn standalone_exit(&self) -> bool { + !self.mixnode && !self.entry && self.exit + } + pub fn with_mode(&mut self, mode: NodeMode) -> &mut Self { match mode { NodeMode::Mixnode => self.with_mixnode(), NodeMode::EntryGateway => self.with_entry(), NodeMode::ExitGateway => self.with_exit(), + NodeMode::FullGateway => self.with_entry().with_exit(), } } From 24480418f0184009de4f1c32684f6371f013faa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 9 Jan 2025 11:00:37 +0100 Subject: [PATCH 71/80] Bugfix/contract version assignment (#5318) * fixed contract version being overwritten * introduced migration to fix existing [mainnet] state * updated contract schema * updated testnet manager migrate msg code --- .../mixnet-contract/src/msg.rs | 7 - .../src/support/setup.rs | 3 - .../mixnet/schema/nym-mixnet-contract.json | 88 +--- contracts/mixnet/schema/raw/migrate.json | 88 +--- contracts/mixnet/src/contract.rs | 4 +- .../src/mixnet_contract_settings/storage.rs | 233 ++++++++- .../mixnet_contract_settings/transactions.rs | 442 ++++++++++++++++++ contracts/mixnet/src/queued_migrations.rs | 203 +++++--- contracts/mixnet/src/support/tests/mod.rs | 18 +- .../src/manager/network_init.rs | 3 - 10 files changed, 824 insertions(+), 265 deletions(-) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 214e7e95df..1f32e71441 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -863,11 +863,4 @@ pub enum QueryMsg { pub struct MigrateMsg { pub unsafe_skip_state_updates: Option, pub vesting_contract_address: Option, - pub current_nym_node_semver: String, - - #[serde(default)] - pub version_score_weights: OutdatedVersionWeights, - - #[serde(default)] - pub version_score_params: VersionScoreFormulaParams, } diff --git a/contracts/mixnet-vesting-integration-tests/src/support/setup.rs b/contracts/mixnet-vesting-integration-tests/src/support/setup.rs index 7193097aa4..f1d0328f69 100644 --- a/contracts/mixnet-vesting-integration-tests/src/support/setup.rs +++ b/contracts/mixnet-vesting-integration-tests/src/support/setup.rs @@ -464,10 +464,7 @@ pub fn instantiate_contracts( mixnet_contract_address.clone(), &nym_mixnet_contract_common::MigrateMsg { vesting_contract_address: Some(vesting_contract_address.to_string()), - current_nym_node_semver: "1.1.10".to_string(), - version_score_weights: Default::default(), unsafe_skip_state_updates: Some(true), - version_score_params: Default::default(), }, mixnet_code_id, ) diff --git a/contracts/mixnet/schema/nym-mixnet-contract.json b/contracts/mixnet/schema/nym-mixnet-contract.json index 3549011058..2cf7b7675f 100644 --- a/contracts/mixnet/schema/nym-mixnet-contract.json +++ b/contracts/mixnet/schema/nym-mixnet-contract.json @@ -3470,43 +3470,13 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MigrateMsg", "type": "object", - "required": [ - "current_nym_node_semver" - ], "properties": { - "current_nym_node_semver": { - "type": "string" - }, "unsafe_skip_state_updates": { "type": [ "boolean", "null" ] }, - "version_score_params": { - "default": { - "penalty": "0.995", - "penalty_scaling": "1.65" - }, - "allOf": [ - { - "$ref": "#/definitions/VersionScoreFormulaParams" - } - ] - }, - "version_score_weights": { - "default": { - "major": 100, - "minor": 10, - "patch": 1, - "prerelease": 1 - }, - "allOf": [ - { - "$ref": "#/definitions/OutdatedVersionWeights" - } - ] - }, "vesting_contract_address": { "type": [ "string", @@ -3514,63 +3484,7 @@ ] } }, - "additionalProperties": false, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "OutdatedVersionWeights": { - "description": "Defines weights for calculating numbers of versions behind the current release.", - "type": "object", - "required": [ - "major", - "minor", - "patch", - "prerelease" - ], - "properties": { - "major": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "minor": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "patch": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "prerelease": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "VersionScoreFormulaParams": { - "description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters", - "type": "object", - "required": [ - "penalty", - "penalty_scaling" - ], - "properties": { - "penalty": { - "$ref": "#/definitions/Decimal" - }, - "penalty_scaling": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - } + "additionalProperties": false }, "sudo": null, "responses": { diff --git a/contracts/mixnet/schema/raw/migrate.json b/contracts/mixnet/schema/raw/migrate.json index 3844bce308..57f0d2acdb 100644 --- a/contracts/mixnet/schema/raw/migrate.json +++ b/contracts/mixnet/schema/raw/migrate.json @@ -2,43 +2,13 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MigrateMsg", "type": "object", - "required": [ - "current_nym_node_semver" - ], "properties": { - "current_nym_node_semver": { - "type": "string" - }, "unsafe_skip_state_updates": { "type": [ "boolean", "null" ] }, - "version_score_params": { - "default": { - "penalty": "0.995", - "penalty_scaling": "1.65" - }, - "allOf": [ - { - "$ref": "#/definitions/VersionScoreFormulaParams" - } - ] - }, - "version_score_weights": { - "default": { - "major": 100, - "minor": 10, - "patch": 1, - "prerelease": 1 - }, - "allOf": [ - { - "$ref": "#/definitions/OutdatedVersionWeights" - } - ] - }, "vesting_contract_address": { "type": [ "string", @@ -46,61 +16,5 @@ ] } }, - "additionalProperties": false, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "OutdatedVersionWeights": { - "description": "Defines weights for calculating numbers of versions behind the current release.", - "type": "object", - "required": [ - "major", - "minor", - "patch", - "prerelease" - ], - "properties": { - "major": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "minor": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "patch": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "prerelease": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "VersionScoreFormulaParams": { - "description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters", - "type": "object", - "required": [ - "penalty", - "penalty_scaling" - ], - "properties": { - "penalty": { - "$ref": "#/definitions/Decimal" - }, - "penalty_scaling": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - } + "additionalProperties": false } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 3526e58636..60dd9e4fe3 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -603,7 +603,7 @@ pub fn query( #[entry_point] pub fn migrate( mut deps: DepsMut<'_>, - env: Env, + _env: Env, msg: MigrateMsg, ) -> Result { set_build_information!(deps.storage)?; @@ -612,7 +612,7 @@ pub fn migrate( let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false); if !skip_state_updates { - crate::queued_migrations::add_config_score_params(deps.branch(), env, &msg)?; + crate::queued_migrations::restore_node_version_history(deps.branch())?; } // due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address diff --git a/contracts/mixnet/src/mixnet_contract_settings/storage.rs b/contracts/mixnet/src/mixnet_contract_settings/storage.rs index 6201972e51..992d15ab8a 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/storage.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/storage.rs @@ -34,9 +34,13 @@ impl NymNodeVersionHistory<'_> { } fn next_id(&self, storage: &mut dyn Storage) -> Result { - let next = self.id_counter.may_load(storage)?.unwrap_or_default(); - self.id_counter.save(storage, &next)?; - Ok(next) + let id = self + .id_counter + .may_load(storage)? + .map(|current| current + 1) + .unwrap_or_default(); + self.id_counter.save(storage, &id)?; + Ok(id) } pub fn current_version( @@ -56,10 +60,10 @@ impl NymNodeVersionHistory<'_> { pub fn insert_new( &self, storage: &mut dyn Storage, - entry: HistoricalNymNodeVersion, + entry: &HistoricalNymNodeVersion, ) -> Result { let next_id = self.next_id(storage)?; - self.version_history.save(storage, next_id, &entry)?; + self.version_history.save(storage, next_id, entry)?; Ok(next_id) } @@ -79,7 +83,7 @@ impl NymNodeVersionHistory<'_> { // treat this as genesis let genesis = HistoricalNymNodeVersion::genesis(raw_semver.to_string(), env.block.height); - return self.insert_new(storage, genesis); + return self.insert_new(storage, &genesis); }; let current_semver = current.version_information.semver_unchecked(); @@ -99,7 +103,7 @@ impl NymNodeVersionHistory<'_> { introduced_at_height: env.block.height, difference_since_genesis: diff, }; - self.insert_new(storage, entry) + self.insert_new(storage, &entry) } } @@ -170,3 +174,218 @@ pub(crate) fn initialise_storage( ADMIN.set(deps, Some(initial_admin))?; Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(test)] + mod nym_node_version_history { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + + #[test] + fn getting_current() -> anyhow::Result<()> { + // empty storage + let deps = mock_dependencies(); + let storage = NymNodeVersionHistory::new(); + assert!(storage.current_version(&deps.storage)?.is_none()); + + let mut test = TestSetup::new(); + + let zeroth = storage.current_version(test.storage())?.unwrap(); + let manual_zeroth = storage.version_history.load(test.storage(), 0)?; + assert_eq!(zeroth.version_information, manual_zeroth); + + // manually update the counter to make sure data is still read correctly + let dummy = HistoricalNymNodeVersion { + semver: "1.2.3".to_string(), + introduced_at_height: 1234, + difference_since_genesis: Default::default(), + }; + storage.id_counter.save(test.storage_mut(), &123)?; + storage + .version_history + .save(test.storage_mut(), 123, &dummy)?; + + let updated = storage.current_version(test.storage())?.unwrap(); + assert_eq!(updated.version_information, dummy); + + Ok(()) + } + + #[test] + fn inserting_new_entry() -> anyhow::Result<()> { + let mut test = TestSetup::new(); + let storage = NymNodeVersionHistory::new(); + + let first = HistoricalNymNodeVersion { + semver: "1.1.1".to_string(), + introduced_at_height: 12, + difference_since_genesis: Default::default(), + }; + let second = HistoricalNymNodeVersion { + semver: "1.1.2".to_string(), + introduced_at_height: 123, + difference_since_genesis: Default::default(), + }; + let third = HistoricalNymNodeVersion { + semver: "1.1.3".to_string(), + introduced_at_height: 1234, + difference_since_genesis: Default::default(), + }; + + assert_eq!(storage.id_counter.load(test.storage())?, 0); + + // id is correctly incremented for each case and no entry is overwritten + storage.insert_new(test.storage_mut(), &first)?; + assert_eq!(storage.id_counter.load(test.storage())?, 1); + + storage.insert_new(test.storage_mut(), &second)?; + assert_eq!(storage.id_counter.load(test.storage())?, 2); + + storage.insert_new(test.storage_mut(), &third)?; + assert_eq!(storage.id_counter.load(test.storage())?, 3); + + assert_eq!(storage.version_history.load(test.storage(), 1)?, first); + assert_eq!(storage.version_history.load(test.storage(), 2)?, second); + assert_eq!(storage.version_history.load(test.storage(), 3)?, third); + + Ok(()) + } + + #[test] + fn inserting_initial_semver() -> anyhow::Result<()> { + // empty storage + let mut deps = mock_dependencies(); + let env = mock_env(); + let storage = NymNodeVersionHistory::new(); + + assert!(storage + .id_counter + .may_load(deps.as_mut().storage)? + .is_none()); + + storage.try_insert_new(deps.as_mut().storage, &env, "1.1.1")?; + assert_eq!(storage.id_counter.load(deps.as_mut().storage)?, 0); + + assert_eq!( + storage + .version_history + .load(deps.as_ref().storage, 0)? + .semver, + "1.1.1" + ); + assert_eq!( + storage + .current_version(deps.as_ref().storage)? + .unwrap() + .version_information + .semver, + "1.1.1" + ); + + Ok(()) + } + + #[test] + fn inserting_second_semver() -> anyhow::Result<()> { + let mut test = TestSetup::new(); + let env = test.env(); + let storage = NymNodeVersionHistory::new(); + + // lower version + assert!(storage + .try_insert_new(test.storage_mut(), &env, "1.1.9") + .is_err()); + assert!(storage + .try_insert_new(test.storage_mut(), &env, "1.0.1") + .is_err()); + + // malformed + assert!(storage + .try_insert_new(test.storage_mut(), &env, "1.0") + .is_err()); + assert!(storage + .try_insert_new(test.storage_mut(), &env, "1.0bad") + .is_err()); + assert!(storage + .try_insert_new(test.storage_mut(), &env, "foomp") + .is_err()); + + // patch + let mut test = TestSetup::new(); + storage.try_insert_new(test.storage_mut(), &env, "1.1.11")?; + let current = storage + .current_version(test.storage_mut())? + .unwrap() + .version_information; + assert_eq!(current.semver, "1.1.11"); + assert_eq!(current.difference_since_genesis.major, 0); + assert_eq!(current.difference_since_genesis.minor, 0); + assert_eq!(current.difference_since_genesis.patch, 1); + assert_eq!(current.difference_since_genesis.prerelease, 0); + + // minor + let mut test = TestSetup::new(); + storage.try_insert_new(test.storage_mut(), &env, "1.2.0")?; + let current = storage + .current_version(test.storage_mut())? + .unwrap() + .version_information; + assert_eq!(current.semver, "1.2.0"); + assert_eq!(current.difference_since_genesis.major, 0); + assert_eq!(current.difference_since_genesis.minor, 1); + assert_eq!(current.difference_since_genesis.patch, 0); + assert_eq!(current.difference_since_genesis.prerelease, 0); + + // minor alt. + let mut test = TestSetup::new(); + storage.try_insert_new(test.storage_mut(), &env, "1.2.3")?; + let current = storage + .current_version(test.storage_mut())? + .unwrap() + .version_information; + assert_eq!(current.semver, "1.2.3"); + assert_eq!(current.difference_since_genesis.major, 0); + assert_eq!(current.difference_since_genesis.minor, 1); + assert_eq!(current.difference_since_genesis.patch, 0); + assert_eq!(current.difference_since_genesis.prerelease, 0); + + Ok(()) + } + + #[test] + fn inserting_subsequent_semver() -> anyhow::Result<()> { + let mut test = TestSetup::new(); + let env = test.env(); + let storage = NymNodeVersionHistory::new(); + + storage.try_insert_new(test.storage_mut(), &env, "1.2.0")?; + storage.try_insert_new(test.storage_mut(), &env, "1.2.1")?; + storage.try_insert_new(test.storage_mut(), &env, "1.2.3")?; + let current = storage + .current_version(test.storage_mut())? + .unwrap() + .version_information; + assert_eq!(current.semver, "1.2.3"); + assert_eq!(current.difference_since_genesis.major, 0); + assert_eq!(current.difference_since_genesis.minor, 1); + assert_eq!(current.difference_since_genesis.patch, 3); + assert_eq!(current.difference_since_genesis.prerelease, 0); + + storage.try_insert_new(test.storage_mut(), &env, "1.3.0")?; + let current = storage + .current_version(test.storage_mut())? + .unwrap() + .version_information; + assert_eq!(current.semver, "1.3.0"); + assert_eq!(current.difference_since_genesis.major, 0); + assert_eq!(current.difference_since_genesis.minor, 2); + assert_eq!(current.difference_since_genesis.patch, 3); + assert_eq!(current.difference_since_genesis.prerelease, 0); + Ok(()) + } + } +} diff --git a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs index d3c3a211f1..f48f7b401a 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs @@ -238,4 +238,446 @@ pub mod tests { // let res = try_update_contract_settings(deps.as_mut(), info, new_params); // assert_eq!(Err(MixnetContractError::ZeroActiveSet), res); } + + #[cfg(test)] + mod updating_current_nym_node_semver { + use super::*; + use crate::mixnet_contract_settings::queries::query_current_nym_node_version; + use crate::support::tests::test_helpers::TestSetup; + + #[test] + fn is_restricted_to_the_admin() -> anyhow::Result<()> { + let mut test = TestSetup::new(); + + let not_admin = mock_info("not-admin", &[]); + let admin = mock_info(test.admin().as_ref(), &[]); + + let env = test.env(); + let res = try_update_current_nym_node_semver( + test.deps_mut(), + env, + not_admin, + "1.2.1".to_string(), + ); + assert!(res.is_err()); + + let env = test.env(); + let res = try_update_current_nym_node_semver( + test.deps_mut(), + env, + admin, + "1.2.1".to_string(), + ); + assert!(res.is_ok()); + Ok(()) + } + + #[test] + fn updates_current_semver_value() -> anyhow::Result<()> { + let mut test = TestSetup::new(); + + let res = query_current_nym_node_version(test.deps())?; + + let initial = res.version.unwrap().version_information.semver; + // sanity check to make sure our contract init hasn't changed + assert_eq!(initial, "1.1.10"); + + let update = "1.2.0".to_string(); + + let env = test.env(); + let sender = test.admin_sender(); + try_update_current_nym_node_semver(test.deps_mut(), env, sender, update.clone())?; + + let updated = query_current_nym_node_version(test.deps())?; + let version = updated.version.unwrap().version_information.semver; + assert_eq!(version, update); + + Ok(()) + } + + #[cfg(test)] + mod semver_chain_updates { + use super::*; + use crate::mixnet_contract_settings::queries::query_nym_node_version_history_paged; + use mixnet_contract_common::{ + HistoricalNymNodeVersion, HistoricalNymNodeVersionEntry, TotalVersionDifference, + }; + + fn test_setup_with_initial_checks() -> anyhow::Result { + let test = TestSetup::new(); + + let res = query_current_nym_node_version(test.deps())?; + let initial = res.version.unwrap().version_information.semver; + + // sanity check to make sure our contract init hasn't changed + assert_eq!(initial, "1.1.10"); + + let history = query_nym_node_version_history_paged(test.deps(), None, None)?; + assert_eq!(history.history.len(), 1); + + Ok(test) + } + + #[test] + fn single_patch() -> anyhow::Result<()> { + let mut test = test_setup_with_initial_checks()?; + let initial = query_current_nym_node_version(test.deps())? + .version + .unwrap(); + + let env = test.env(); + let sender = test.admin_sender(); + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender, + "1.1.11".to_string(), + )?; + + let history = + query_nym_node_version_history_paged(test.deps(), None, None)?.history; + assert_eq!(history.len(), 2); + assert_eq!(history[0], initial); + assert_eq!( + history[1], + HistoricalNymNodeVersionEntry { + id: 1, + version_information: HistoricalNymNodeVersion { + semver: "1.1.11".to_string(), + introduced_at_height: env.block.height, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 0, + patch: 1, + prerelease: 0, + }, + }, + } + ); + + Ok(()) + } + + #[test] + fn single_minor() -> anyhow::Result<()> { + let mut test = test_setup_with_initial_checks()?; + let initial = query_current_nym_node_version(test.deps())? + .version + .unwrap(); + + let env = test.env(); + let sender = test.admin_sender(); + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender, + "1.2.0".to_string(), + )?; + + let history = + query_nym_node_version_history_paged(test.deps(), None, None)?.history; + assert_eq!(history.len(), 2); + assert_eq!(history[0], initial); + assert_eq!( + history[1], + HistoricalNymNodeVersionEntry { + id: 1, + version_information: HistoricalNymNodeVersion { + semver: "1.2.0".to_string(), + introduced_at_height: env.block.height, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 1, + patch: 0, + prerelease: 0, + }, + }, + } + ); + + Ok(()) + } + + #[test] + fn multiple_patches() -> anyhow::Result<()> { + let mut test = test_setup_with_initial_checks()?; + let initial = query_current_nym_node_version(test.deps())? + .version + .unwrap(); + + let mut env = test.env(); + let sender = test.admin_sender(); + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender.clone(), + "1.1.11".to_string(), + )?; + env.block.height += 1; + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender.clone(), + "1.1.12".to_string(), + )?; + env.block.height += 1; + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender, + "1.1.13".to_string(), + )?; + + let history = + query_nym_node_version_history_paged(test.deps(), None, None)?.history; + assert_eq!(history.len(), 4); + assert_eq!(history[0], initial); + assert_eq!( + history[1], + HistoricalNymNodeVersionEntry { + id: 1, + version_information: HistoricalNymNodeVersion { + semver: "1.1.11".to_string(), + introduced_at_height: env.block.height - 2, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 0, + patch: 1, + prerelease: 0, + }, + }, + } + ); + assert_eq!( + history[2], + HistoricalNymNodeVersionEntry { + id: 2, + version_information: HistoricalNymNodeVersion { + semver: "1.1.12".to_string(), + introduced_at_height: env.block.height - 1, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 0, + patch: 2, + prerelease: 0, + }, + }, + } + ); + assert_eq!( + history[3], + HistoricalNymNodeVersionEntry { + id: 3, + version_information: HistoricalNymNodeVersion { + semver: "1.1.13".to_string(), + introduced_at_height: env.block.height, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 0, + patch: 3, + prerelease: 0, + }, + }, + } + ); + + Ok(()) + } + + #[test] + fn multiple_minors() -> anyhow::Result<()> { + let mut test = test_setup_with_initial_checks()?; + let initial = query_current_nym_node_version(test.deps())? + .version + .unwrap(); + + let mut env = test.env(); + let sender = test.admin_sender(); + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender.clone(), + "1.2.0".to_string(), + )?; + env.block.height += 1; + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender.clone(), + "1.3.0".to_string(), + )?; + env.block.height += 1; + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender, + "1.4.0".to_string(), + )?; + + let history = + query_nym_node_version_history_paged(test.deps(), None, None)?.history; + assert_eq!(history.len(), 4); + assert_eq!(history[0], initial); + assert_eq!( + history[1], + HistoricalNymNodeVersionEntry { + id: 1, + version_information: HistoricalNymNodeVersion { + semver: "1.2.0".to_string(), + introduced_at_height: env.block.height - 2, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 1, + patch: 0, + prerelease: 0, + }, + }, + } + ); + assert_eq!( + history[2], + HistoricalNymNodeVersionEntry { + id: 2, + version_information: HistoricalNymNodeVersion { + semver: "1.3.0".to_string(), + introduced_at_height: env.block.height - 1, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 2, + patch: 0, + prerelease: 0, + }, + }, + } + ); + assert_eq!( + history[3], + HistoricalNymNodeVersionEntry { + id: 3, + version_information: HistoricalNymNodeVersion { + semver: "1.4.0".to_string(), + introduced_at_height: env.block.height, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 3, + patch: 0, + prerelease: 0, + }, + }, + } + ); + + Ok(()) + } + + #[test] + fn mixed_multiple_updates() -> anyhow::Result<()> { + let mut test = test_setup_with_initial_checks()?; + let initial = query_current_nym_node_version(test.deps())? + .version + .unwrap(); + + let mut env = test.env(); + let sender = test.admin_sender(); + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender.clone(), + "1.2.0".to_string(), + )?; + env.block.height += 1; + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender.clone(), + "1.2.1".to_string(), + )?; + env.block.height += 1; + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender.clone(), + "1.2.3".to_string(), + )?; + env.block.height += 1; + try_update_current_nym_node_semver( + test.deps_mut(), + env.clone(), + sender, + "1.3.0".to_string(), + )?; + + let history = + query_nym_node_version_history_paged(test.deps(), None, None)?.history; + assert_eq!(history.len(), 5); + assert_eq!(history[0], initial); + assert_eq!( + history[1], + HistoricalNymNodeVersionEntry { + id: 1, + version_information: HistoricalNymNodeVersion { + semver: "1.2.0".to_string(), + introduced_at_height: env.block.height - 3, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 1, + patch: 0, + prerelease: 0, + }, + }, + } + ); + assert_eq!( + history[2], + HistoricalNymNodeVersionEntry { + id: 2, + version_information: HistoricalNymNodeVersion { + semver: "1.2.1".to_string(), + introduced_at_height: env.block.height - 2, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 1, + patch: 1, + prerelease: 0, + }, + }, + } + ); + assert_eq!( + history[3], + HistoricalNymNodeVersionEntry { + id: 3, + version_information: HistoricalNymNodeVersion { + semver: "1.2.3".to_string(), + introduced_at_height: env.block.height - 1, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 1, + patch: 3, + prerelease: 0, + }, + }, + } + ); + assert_eq!( + history[4], + HistoricalNymNodeVersionEntry { + id: 4, + version_information: HistoricalNymNodeVersion { + semver: "1.3.0".to_string(), + introduced_at_height: env.block.height, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 2, + patch: 3, + prerelease: 0, + }, + }, + } + ); + + Ok(()) + } + } + } } diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index 476d484d80..ebcce35112 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -1,86 +1,153 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -mod config_score_params { - use crate::constants::CONTRACT_STATE_KEY; - use crate::mixnet_contract_settings::storage as mixnet_params_storage; +mod node_version_history { use crate::mixnet_contract_settings::storage::NymNodeVersionHistory; - use cosmwasm_std::{Addr, Coin, DepsMut, Env}; - use cw_storage_plus::Item; + use cosmwasm_std::DepsMut; use mixnet_contract_common::error::MixnetContractError; - use mixnet_contract_common::{ - ConfigScoreParams, ContractState, ContractStateParams, DelegationsParams, MigrateMsg, - OperatingCostRange, OperatorsParams, ProfitMarginRange, - }; - use serde::{Deserialize, Serialize}; - use std::str::FromStr; + use mixnet_contract_common::{HistoricalNymNodeVersion, TotalVersionDifference}; - pub(crate) fn add_config_score_params( + pub(crate) fn restore_node_version_history( deps: DepsMut<'_>, - env: Env, - msg: &MigrateMsg, ) -> Result<(), MixnetContractError> { - if semver::Version::from_str(&msg.current_nym_node_semver).is_err() { - return Err(MixnetContractError::InvalidNymNodeSemver { - provided: msg.current_nym_node_semver.to_string(), + // sanity check: + let storage = NymNodeVersionHistory::new(); + let Some(current) = storage.current_version(deps.storage)? else { + return Err(MixnetContractError::FailedMigration { + comment: "no node version history set".to_string(), + }); + }; + if current.version_information.semver != "1.2.1" + || current.version_information.introduced_at_height != 15902170 + { + return Err(MixnetContractError::FailedMigration { + comment: format!("unexpected current node version history. got: {current:?}"), }); } - - #[derive(Serialize, Deserialize)] - pub struct OldContractState { - pub owner: Option, - pub rewarding_validator_address: Addr, - pub vesting_contract_address: Addr, - pub rewarding_denom: String, - pub params: OldContractStateParams, - } - - #[derive(Serialize, Deserialize)] - pub struct OldContractStateParams { - pub minimum_delegation: Option, - pub minimum_pledge: Coin, - #[serde(default)] - pub profit_margin: ProfitMarginRange, - #[serde(default)] - pub interval_operating_cost: OperatingCostRange, - } - - const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new(CONTRACT_STATE_KEY); - let old_state = OLD_CONTRACT_STATE.load(deps.storage)?; - - #[allow(deprecated)] - let new_state = ContractState { - owner: old_state.owner, - rewarding_validator_address: old_state.rewarding_validator_address, - vesting_contract_address: old_state.vesting_contract_address, - rewarding_denom: old_state.rewarding_denom, - params: ContractStateParams { - delegations_params: DelegationsParams { - minimum_delegation: old_state.params.minimum_delegation, - }, - operators_params: OperatorsParams { - minimum_pledge: old_state.params.minimum_pledge, - profit_margin: old_state.params.profit_margin, - interval_operating_cost: old_state.params.interval_operating_cost, - }, - config_score_params: ConfigScoreParams { - version_weights: msg.version_score_weights, - version_score_formula_params: msg.version_score_params, - }, - }, + let lost = HistoricalNymNodeVersion { + semver: "1.1.12".to_string(), + introduced_at_height: 15779133, + difference_since_genesis: TotalVersionDifference::default(), }; - mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &new_state)?; + #[allow(clippy::unwrap_used)] + // SAFETY: this information was already stored in the contract, so it must be a valid semver + let difference_since_genesis = lost.cumulative_difference_since_genesis( + ¤t.version_information.semver.parse().unwrap(), + ); + let updated_current = HistoricalNymNodeVersion { + semver: current.version_information.semver, + introduced_at_height: current.version_information.introduced_at_height, + difference_since_genesis, + }; - // initialise the version chain - NymNodeVersionHistory::new().try_insert_new( - deps.storage, - &env, - &msg.current_nym_node_semver, - )?; + // restore overwritten entry for 1.1.12 + storage.version_history.save(deps.storage, 0, &lost)?; + + // re-insert 1.2.1 as the current + storage + .version_history + .save(deps.storage, 1, &updated_current)?; + storage.id_counter.save(deps.storage, &1)?; Ok(()) } } -pub(crate) use config_score_params::add_config_score_params; +pub(crate) use node_version_history::restore_node_version_history; + +#[cfg(test)] +mod tests { + use super::*; + use crate::mixnet_contract_settings::queries::query_nym_node_version_history_paged; + use crate::mixnet_contract_settings::storage::NymNodeVersionHistory; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use mixnet_contract_common::{ + HistoricalNymNodeVersion, HistoricalNymNodeVersionEntry, TotalVersionDifference, + }; + + #[test] + fn fixing_history_storage() -> anyhow::Result<()> { + // current state on mainnet: + let mut deps = mock_dependencies(); + let storage = NymNodeVersionHistory::new(); + + storage.id_counter.save(deps.as_mut().storage, &0)?; + storage.version_history.save( + deps.as_mut().storage, + 0, + &HistoricalNymNodeVersion { + semver: "1.2.1".to_string(), + introduced_at_height: 15902170, + difference_since_genesis: Default::default(), + }, + )?; + + // run migration + restore_node_version_history(deps.as_mut())?; + + let current = storage.current_version(deps.as_ref().storage)?.unwrap(); + assert_eq!(current.version_information.semver, "1.2.1"); + assert_eq!(current.version_information.introduced_at_height, 15902170); + assert_eq!( + current.version_information.difference_since_genesis, + TotalVersionDifference { + major: 0, + minor: 1, + patch: 0, + prerelease: 0, + } + ); + + let history = query_nym_node_version_history_paged(deps.as_ref(), None, None)?.history; + assert_eq!(history.len(), 2); + assert_eq!( + history, + vec![ + HistoricalNymNodeVersionEntry { + id: 0, + version_information: HistoricalNymNodeVersion { + semver: "1.1.12".to_string(), + introduced_at_height: 15779133, + difference_since_genesis: Default::default(), + }, + }, + HistoricalNymNodeVersionEntry { + id: 1, + version_information: HistoricalNymNodeVersion { + semver: "1.2.1".to_string(), + introduced_at_height: 15902170, + difference_since_genesis: TotalVersionDifference { + major: 0, + minor: 1, + patch: 0, + prerelease: 0, + }, + }, + } + ] + ); + + let counter = storage.id_counter.load(deps.as_ref().storage)?; + assert_eq!(counter, 1); + + // make sure adding another version doesn't mess anything up + storage.try_insert_new(deps.as_mut().storage, &mock_env(), "1.3.0")?; + + let current = storage.current_version(deps.as_ref().storage)?.unwrap(); + assert_eq!(current.version_information.semver, "1.3.0"); + assert_eq!( + current.version_information.difference_since_genesis, + TotalVersionDifference { + major: 0, + minor: 2, + patch: 0, + prerelease: 0, + } + ); + let counter = storage.id_counter.load(deps.as_ref().storage)?; + assert_eq!(counter, 2); + + Ok(()) + } +} diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index cd40ef7ca4..4c0f2d85a3 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -18,7 +18,7 @@ pub mod test_helpers { }; use crate::interval::{pending_events, storage as interval_storage}; use crate::mixnet_contract_settings::storage::{ - self as mixnet_params_storage, minimum_node_pledge, + self as mixnet_params_storage, minimum_node_pledge, ADMIN, }; use crate::mixnet_contract_settings::storage::{rewarding_denom, rewarding_validator_address}; use crate::mixnodes::helpers::get_mixnode_details_by_id; @@ -318,6 +318,10 @@ pub mod test_helpers { compare_decimals(mix_info.delegates, subtotal, Some(epsilon)) } + pub fn admin(&self) -> Addr { + ADMIN.get(self.deps()).unwrap().unwrap() + } + pub fn random_address(&mut self) -> String { format!("n1foomp{}", self.rng.next_u64()) } @@ -330,6 +334,14 @@ pub mod test_helpers { self.deps.as_mut() } + pub fn storage(&self) -> &dyn Storage { + self.deps().storage + } + + pub fn storage_mut(&mut self) -> &mut dyn Storage { + self.deps_mut().storage + } + pub fn env(&self) -> Env { self.env.clone() } @@ -470,6 +482,10 @@ pub mod test_helpers { .unwrap() } + pub fn admin_sender(&self) -> MessageInfo { + mock_info(self.admin().as_ref(), &[]) + } + pub fn owner(&self) -> MessageInfo { self.owner.clone() } diff --git a/tools/internal/testnet-manager/src/manager/network_init.rs b/tools/internal/testnet-manager/src/manager/network_init.rs index 5b7f36eea8..1a0fc0e41d 100644 --- a/tools/internal/testnet-manager/src/manager/network_init.rs +++ b/tools/internal/testnet-manager/src/manager/network_init.rs @@ -109,10 +109,7 @@ impl NetworkManager { ) -> Result { Ok(nym_mixnet_contract_common::MigrateMsg { vesting_contract_address: Some(ctx.network.contracts.vesting.address()?.to_string()), - current_nym_node_semver: "irrelevant".to_string(), - version_score_weights: Default::default(), unsafe_skip_state_updates: Some(true), - version_score_params: Default::default(), }) } From a94c035c0a6363be80b08cafb7fc228d22e613b3 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Thu, 9 Jan 2025 12:36:05 +0100 Subject: [PATCH 72/80] correct the nym-node bumped version --- Cargo.lock | 2 +- nym-node/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2e25e992f..863266592a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.2.2" +version = "1.3.0" dependencies = [ "anyhow", "async-trait", diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 8b04ce3387..07c67d9961 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.2.2" +version = "1.3.0" authors.workspace = true repository.workspace = true homepage.workspace = true From 529e8d49eef8d67f65d5e5156bc9b6cf044a3255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 10 Jan 2025 14:00:18 +0100 Subject: [PATCH 73/80] chore: apply 1.84 linter suggestions (#5330) * chore: apply 1.84 linter suggestions * updated wasm dependencies to fix the macro issue * second batch of clippy fixes --- Cargo.lock | 52 ++++++++++--------- Cargo.toml | 18 +++---- common/client-core/src/init/helpers.rs | 2 +- .../src/registration/handshake/client.rs | 2 +- .../src/registration/handshake/gateway.rs | 2 +- .../src/scheme/aggregation.rs | 5 +- .../src/clients/packet_statistics.rs | 4 +- explorer-api/src/gateways/models.rs | 4 +- explorer-api/src/mix_nodes/models.rs | 4 +- explorer-api/src/unstable/models.rs | 4 +- .../src/http/state/mod.rs | 2 +- nym-wallet/Cargo.lock | 4 +- .../testnet-manager/src/manager/dkg_skip.rs | 26 +++++----- .../testnet-manager/src/manager/local_apis.rs | 6 +-- .../src/manager/local_client.rs | 17 +++--- .../src/manager/local_nodes.rs | 26 +++++----- 16 files changed, 83 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 863266592a..b3197fd475 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2483,9 +2483,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fancy_constructor" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71f317e4af73b2f8f608fac190c52eac4b1879d2145df1db2fe48881ca69435" +checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2" dependencies = [ "macroific", "proc-macro2", @@ -2846,15 +2846,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gloo-net" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" dependencies = [ "futures-channel", "futures-core", "futures-sink", "gloo-utils 0.2.0", - "http 0.2.12", + "http 1.1.0", "js-sys", "pin-project", "serde", @@ -3432,7 +3432,8 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexed_db_futures" version = "0.4.2" -source = "git+https://github.com/TiemenSch/rust-indexed-db?branch=update-uuid#9745d015707008b0c410115d787014a6d1af2efb" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0704b71f13f81b5933d791abf2de26b33c40935143985220299a357721166706" dependencies = [ "accessory", "cfg-if", @@ -3670,10 +3671,11 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -10762,9 +10764,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -10773,13 +10775,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -10788,21 +10789,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10810,9 +10812,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -10823,9 +10825,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" @@ -10933,9 +10935,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" dependencies = [ "futures", "js-sys", @@ -10947,9 +10949,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 52ef268311..fa53897399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -394,19 +394,17 @@ prost = { version = "0.12", default-features = false } # wasm-related dependencies gloo-utils = "0.2.0" -gloo-net = "0.5.0" +gloo-net = "0.6.0" -# use a separate branch due to feature unification failures -# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage -# indexed_db_futures = "0.4.1" -indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" } -js-sys = "0.3.70" +# TODO: migrate to 0.6+ +indexed_db_futures = "0.4.2" +js-sys = "0.3.76" serde-wasm-bindgen = "0.6.5" tsify = "0.4.5" -wasm-bindgen = "0.2.95" -wasm-bindgen-futures = "0.4.45" -wasmtimer = "0.2.0" -web-sys = "0.3.72" +wasm-bindgen = "0.2.99" +wasm-bindgen-futures = "0.4.49" +wasmtimer = "0.4.1" +web-sys = "0.3.76" # Profile settings for individual crates diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 68b3b8d457..16aa042045 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -190,7 +190,7 @@ where Ok(GatewayWithLatency::new(gateway, avg)) } -pub async fn choose_gateway_by_latency<'a, R: Rng, G: ConnectableGateway + Clone>( +pub async fn choose_gateway_by_latency( rng: &mut R, gateways: &[G], must_use_tls: bool, diff --git a/common/gateway-requests/src/registration/handshake/client.rs b/common/gateway-requests/src/registration/handshake/client.rs index 5bdb239a66..549cddca39 100644 --- a/common/gateway-requests/src/registration/handshake/client.rs +++ b/common/gateway-requests/src/registration/handshake/client.rs @@ -9,7 +9,7 @@ use futures::{Sink, Stream}; use rand::{CryptoRng, RngCore}; use tungstenite::Message as WsMessage; -impl<'a, S, R> State<'a, S, R> { +impl State<'_, S, R> { async fn client_handshake_inner(&mut self) -> Result<(), HandshakeError> where S: Stream + Sink + Unpin, diff --git a/common/gateway-requests/src/registration/handshake/gateway.rs b/common/gateway-requests/src/registration/handshake/gateway.rs index fc439b53c0..5fec717c46 100644 --- a/common/gateway-requests/src/registration/handshake/gateway.rs +++ b/common/gateway-requests/src/registration/handshake/gateway.rs @@ -10,7 +10,7 @@ use crate::registration::handshake::{error::HandshakeError, WsItem}; use futures::{Sink, Stream}; use tungstenite::Message as WsMessage; -impl<'a, S, R> State<'a, S, R> { +impl State<'_, S, R> { async fn gateway_handshake_inner( &mut self, raw_init_message: Vec, diff --git a/common/nym_offline_compact_ecash/src/scheme/aggregation.rs b/common/nym_offline_compact_ecash/src/scheme/aggregation.rs index 9018cde7ea..148a619a41 100644 --- a/common/nym_offline_compact_ecash/src/scheme/aggregation.rs +++ b/common/nym_offline_compact_ecash/src/scheme/aggregation.rs @@ -115,10 +115,7 @@ pub fn aggregate_signatures( let params = ecash_group_parameters(); // aggregate the signature - let signature = match Aggregatable::aggregate(signatures, indices) { - Ok(res) => res, - Err(err) => return Err(err), - }; + let signature = Aggregatable::aggregate(signatures, indices)?; // Ensure the aggregated signature is not an infinity point if bool::from(signature.is_at_infinity()) { diff --git a/common/statistics/src/clients/packet_statistics.rs b/common/statistics/src/clients/packet_statistics.rs index 5d6d1f9c1b..ba335d3627 100644 --- a/common/statistics/src/clients/packet_statistics.rs +++ b/common/statistics/src/clients/packet_statistics.rs @@ -428,7 +428,7 @@ impl PacketStatisticsControl { while self .history .front() - .map_or(false, |&(t, _)| t < recording_window) + .is_some_and(|&(t, _)| t < recording_window) { self.history.pop_front(); } @@ -462,7 +462,7 @@ impl PacketStatisticsControl { while self .rates .front() - .map_or(false, |&(t, _)| t < recording_window) + .is_some_and(|&(t, _)| t < recording_window) { self.rates.pop_front(); } diff --git a/explorer-api/src/gateways/models.rs b/explorer-api/src/gateways/models.rs index d2298b2a0c..9b8a6dfaef 100644 --- a/explorer-api/src/gateways/models.rs +++ b/explorer-api/src/gateways/models.rs @@ -120,9 +120,7 @@ impl ThreadsafeGatewayCache { .read() .await .get(&identity_key) - .map_or(false, |cache_item| { - cache_item.valid_until > SystemTime::now() - }) + .is_some_and(|cache_item| cache_item.valid_until > SystemTime::now()) } pub(crate) async fn get_locations(&self) -> GatewayLocationCache { diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index ce5876c949..a9e85ab97e 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -106,9 +106,7 @@ impl ThreadsafeMixNodesCache { .read() .await .get(&mix_id) - .map_or(false, |cache_item| { - cache_item.valid_until > SystemTime::now() - }) + .is_some_and(|cache_item| cache_item.valid_until > SystemTime::now()) } pub(crate) async fn get_locations(&self) -> MixnodeLocationCache { diff --git a/explorer-api/src/unstable/models.rs b/explorer-api/src/unstable/models.rs index 39254d9c07..9abb81942d 100644 --- a/explorer-api/src/unstable/models.rs +++ b/explorer-api/src/unstable/models.rs @@ -60,9 +60,7 @@ impl ThreadSafeNymNodesCache { .read() .await .get(&node_id) - .map_or(false, |cache_item| { - cache_item.valid_until > SystemTime::now() - }) + .is_some_and(|cache_item| cache_item.valid_until > SystemTime::now()) } pub(crate) async fn get_bonded_nymnodes( diff --git a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs index dc6309e8a7..392fa71992 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs @@ -710,7 +710,7 @@ pub(crate) struct ChainWritePermit<'a> { inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>, } -impl<'a> ChainWritePermit<'a> { +impl ChainWritePermit<'_> { pub(crate) async fn make_deposits( self, short_sha: &'static str, diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 6d1ec90fd4..63fb5fff3f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -6388,9 +6388,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasmtimer" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" dependencies = [ "futures", "js-sys", diff --git a/tools/internal/testnet-manager/src/manager/dkg_skip.rs b/tools/internal/testnet-manager/src/manager/dkg_skip.rs index 7720202665..ec74a01af3 100644 --- a/tools/internal/testnet-manager/src/manager/dkg_skip.rs +++ b/tools/internal/testnet-manager/src/manager/dkg_skip.rs @@ -168,9 +168,9 @@ impl NetworkManager { Ok(()) } - async fn validate_existing_contracts<'a>( + async fn validate_existing_contracts( &self, - ctx: &DkgSkipCtx<'a>, + ctx: &DkgSkipCtx<'_>, ) -> Result { ctx.println(format!( "🔬 {}Validating the current DKG and group contracts...", @@ -215,9 +215,9 @@ impl NetworkManager { Ok(current_code) } - async fn persist_dkg_keys<'a, P: AsRef>( + async fn persist_dkg_keys>( &self, - ctx: &mut DkgSkipCtx<'a>, + ctx: &mut DkgSkipCtx<'_>, output_dir: P, ) -> Result<(), NetworkManagerError> { ctx.println(format!( @@ -272,9 +272,9 @@ impl NetworkManager { Ok(()) } - async fn upload_bypass_contract<'a, P: AsRef>( + async fn upload_bypass_contract>( &self, - ctx: &DkgSkipCtx<'a>, + ctx: &DkgSkipCtx<'_>, dkg_bypass_contract: P, ) -> Result { ctx.println(format!( @@ -297,9 +297,9 @@ impl NetworkManager { Ok(res.code_id) } - async fn migrate_to_bypass_contract<'a>( + async fn migrate_to_bypass_contract( &self, - ctx: &DkgSkipCtx<'a>, + ctx: &DkgSkipCtx<'_>, code_id: ContractCodeId, ) -> Result<(), NetworkManagerError> { ctx.println(format!( @@ -336,9 +336,9 @@ impl NetworkManager { Ok(()) } - async fn restore_dkg_contract<'a>( + async fn restore_dkg_contract( &self, - ctx: &DkgSkipCtx<'a>, + ctx: &DkgSkipCtx<'_>, code_id: ContractCodeId, ) -> Result<(), NetworkManagerError> { ctx.println(format!( @@ -363,7 +363,7 @@ impl NetworkManager { Ok(()) } - async fn add_group_members<'a>(&self, ctx: &DkgSkipCtx<'a>) -> Result<(), NetworkManagerError> { + async fn add_group_members(&self, ctx: &DkgSkipCtx<'_>) -> Result<(), NetworkManagerError> { ctx.println(format!( "👪 {}Adding all the cw4 group members...", style("[7/8]").bold().dim() @@ -387,9 +387,9 @@ impl NetworkManager { Ok(()) } - async fn transfer_signer_tokens<'a>( + async fn transfer_signer_tokens( &self, - ctx: &DkgSkipCtx<'a>, + ctx: &DkgSkipCtx<'_>, ) -> Result<(), NetworkManagerError> { ctx.println(format!( "💸 {}Transferring tokens to the new signers...", diff --git a/tools/internal/testnet-manager/src/manager/local_apis.rs b/tools/internal/testnet-manager/src/manager/local_apis.rs index 8ad28e8743..7da09928c3 100644 --- a/tools/internal/testnet-manager/src/manager/local_apis.rs +++ b/tools/internal/testnet-manager/src/manager/local_apis.rs @@ -67,9 +67,9 @@ impl NetworkManager { .join(DEFAULT_CONFIG_FILENAME) } - async fn initialise_api<'a>( + async fn initialise_api( &self, - ctx: &LocalApisCtx<'a>, + ctx: &LocalApisCtx<'_>, info: &EcashSignerWithPaths, ) -> Result<(), NetworkManagerError> { let address = &info.data.cosmos_account.address; @@ -139,7 +139,7 @@ impl NetworkManager { Ok(()) } - async fn initialise_apis<'a>(&self, ctx: &LocalApisCtx<'a>) -> Result<(), NetworkManagerError> { + async fn initialise_apis(&self, ctx: &LocalApisCtx<'_>) -> Result<(), NetworkManagerError> { ctx.println(format!( "🔏 {}Initialising local nym-apis...", style("[1/1]").bold().dim() diff --git a/tools/internal/testnet-manager/src/manager/local_client.rs b/tools/internal/testnet-manager/src/manager/local_client.rs index f5c8ba173a..c95c3062df 100644 --- a/tools/internal/testnet-manager/src/manager/local_client.rs +++ b/tools/internal/testnet-manager/src/manager/local_client.rs @@ -80,9 +80,9 @@ impl NetworkManager { .join(DEFAULT_CONFIG_FILENAME) } - async fn wait_for_api_gateway<'a>( + async fn wait_for_api_gateway( &self, - ctx: &LocalClientCtx<'a>, + ctx: &LocalClientCtx<'_>, ) -> Result { // create api client // hehe, that's disgusting, but it's not meant to be used by users @@ -145,9 +145,9 @@ impl NetworkManager { } } - async fn wait_for_gateway_endpoint<'a>( + async fn wait_for_gateway_endpoint( &self, - ctx: &LocalClientCtx<'a>, + ctx: &LocalClientCtx<'_>, gateway: SocketAddr, ) -> Result<(), NetworkManagerError> { ctx.set_pb_message(format!( @@ -177,17 +177,14 @@ impl NetworkManager { Ok(()) } - async fn wait_for_gateway<'a>( - &self, - ctx: &LocalClientCtx<'a>, - ) -> Result<(), NetworkManagerError> { + async fn wait_for_gateway(&self, ctx: &LocalClientCtx<'_>) -> Result<(), NetworkManagerError> { let endpoint = self.wait_for_api_gateway(ctx).await?; self.wait_for_gateway_endpoint(ctx, endpoint).await } - async fn prepare_nym_client<'a>( + async fn prepare_nym_client( &self, - ctx: &LocalClientCtx<'a>, + ctx: &LocalClientCtx<'_>, ) -> Result<(), NetworkManagerError> { ctx.println(format!( "🔏 {}Initialising local nym-client...", diff --git a/tools/internal/testnet-manager/src/manager/local_nodes.rs b/tools/internal/testnet-manager/src/manager/local_nodes.rs index 792c67e8fe..e140ccb20a 100644 --- a/tools/internal/testnet-manager/src/manager/local_nodes.rs +++ b/tools/internal/testnet-manager/src/manager/local_nodes.rs @@ -102,9 +102,9 @@ struct ReducedSignatureOut { } impl NetworkManager { - async fn initialise_nym_node<'a>( + async fn initialise_nym_node( &self, - ctx: &mut LocalNodesCtx<'a>, + ctx: &mut LocalNodesCtx<'_>, offset: u16, is_gateway: bool, ) -> Result<(), NetworkManagerError> { @@ -222,9 +222,9 @@ impl NetworkManager { Ok(()) } - async fn initialise_nym_nodes<'a>( + async fn initialise_nym_nodes( &self, - ctx: &mut LocalNodesCtx<'a>, + ctx: &mut LocalNodesCtx<'_>, mixnodes: u16, gateways: u16, ) -> Result<(), NetworkManagerError> { @@ -250,9 +250,9 @@ impl NetworkManager { Ok(()) } - async fn transfer_bonding_tokens<'a>( + async fn transfer_bonding_tokens( &self, - ctx: &LocalNodesCtx<'a>, + ctx: &LocalNodesCtx<'_>, ) -> Result<(), NetworkManagerError> { ctx.println(format!( "💸 {}Transferring tokens to the bond owners...", @@ -281,9 +281,9 @@ impl NetworkManager { Ok(()) } - async fn bond_node<'a>( + async fn bond_node( &self, - ctx: &LocalNodesCtx<'a>, + ctx: &LocalNodesCtx<'_>, node: &NymNode, is_gateway: bool, ) -> Result<(), NetworkManagerError> { @@ -318,7 +318,7 @@ impl NetworkManager { Ok(()) } - async fn bond_nym_nodes<'a>(&self, ctx: &LocalNodesCtx<'a>) -> Result<(), NetworkManagerError> { + async fn bond_nym_nodes(&self, ctx: &LocalNodesCtx<'_>) -> Result<(), NetworkManagerError> { ctx.println(format!( "⛓️ {}Bonding the local nym-nodes...", style("[3/5]").bold().dim() @@ -336,9 +336,9 @@ impl NetworkManager { Ok(()) } - async fn assign_to_active_set<'a>( + async fn assign_to_active_set( &self, - ctx: &LocalNodesCtx<'a>, + ctx: &LocalNodesCtx<'_>, ) -> Result<(), NetworkManagerError> { ctx.println(format!( "🔌 {}Assigning nodes to the active set...", @@ -460,9 +460,9 @@ impl NetworkManager { ctx.progress.output_run_commands(cmds) } - async fn persist_nodes_in_database<'a>( + async fn persist_nodes_in_database( &self, - ctx: &LocalNodesCtx<'a>, + ctx: &LocalNodesCtx<'_>, ) -> Result<(), NetworkManagerError> { ctx.println(format!( "📦 {}Storing the node information in the database", From 5a6770e5e2f9d00e122f38b813d3dd213a403adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 10 Jan 2025 14:17:03 +0100 Subject: [PATCH 74/80] chore: readjusted --mode behaviour to fix the regression (#5331) --- nym-node/src/config/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index c9d614e6b1..243f0b403f 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -75,11 +75,12 @@ pub enum NodeMode { #[clap(alias = "entry", alias = "gateway")] EntryGateway, + // to not break existing behaviour, this means exit capabilities AND entry capabilities #[clap(alias = "exit")] ExitGateway, - // entry + exit - FullGateway, + // will start only SP needed for exit capabilities WITHOUT entry routing + ExitProvidersOnly, } impl Display for NodeMode { @@ -88,7 +89,7 @@ impl Display for NodeMode { NodeMode::Mixnode => "mixnode".fmt(f), NodeMode::EntryGateway => "entry-gateway".fmt(f), NodeMode::ExitGateway => "exit-gateway".fmt(f), - NodeMode::FullGateway => "full-gateway".fmt(f), + NodeMode::ExitProvidersOnly => "exit-providers-only".fmt(f), } } } @@ -129,8 +130,8 @@ impl NodeModes { match mode { NodeMode::Mixnode => self.with_mixnode(), NodeMode::EntryGateway => self.with_entry(), - NodeMode::ExitGateway => self.with_exit(), - NodeMode::FullGateway => self.with_entry().with_exit(), + NodeMode::ExitGateway => self.with_entry().with_exit(), + NodeMode::ExitProvidersOnly => self.with_exit(), } } From 676e93a3727a4abed7cc054b8c486b0a849a9460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 10 Jan 2025 15:52:52 +0100 Subject: [PATCH 75/80] bugfix: make sure refresh data key matches bond info (#5329) --- nym-api/src/node_describe_cache/mod.rs | 65 ++++++++++++++++----- nym-api/src/nym_contract_cache/cache/mod.rs | 6 +- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/nym-api/src/node_describe_cache/mod.rs b/nym-api/src/node_describe_cache/mod.rs index adf25c29f1..0cd2d0fe3e 100644 --- a/nym-api/src/node_describe_cache/mod.rs +++ b/nym-api/src/node_describe_cache/mod.rs @@ -12,6 +12,7 @@ use futures::{stream, StreamExt}; use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer}; use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription}; use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; +use nym_crypto::asymmetric::ed25519; use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails}; use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt}; use nym_topology::gateway::GatewayConversionError; @@ -58,6 +59,13 @@ pub enum NodeDescribeCacheError { #[error("could not verify signed host information for node {node_id}")] MissignedHostInformation { node_id: NodeId }, + #[error("identity of node {node_id} does not match. expected {expected} but got {got}")] + MismatchedIdentity { + node_id: NodeId, + expected: String, + got: String, + }, + #[error("node {node_id} is announcing an illegal ip address")] IllegalIpAddress { node_id: NodeId }, } @@ -289,6 +297,15 @@ async fn try_get_description( let host_info = client.get_host_information().await.map_err(map_query_err)?; + // check if the identity key matches the information provided during bonding + if data.expected_identity != host_info.keys.ed25519_identity { + return Err(NodeDescribeCacheError::MismatchedIdentity { + node_id: data.node_id, + expected: data.expected_identity.to_base58_string(), + got: host_info.keys.ed25519_identity.to_base58_string(), + }); + } + if !host_info.verify_host_information() { return Err(NodeDescribeCacheError::MissignedHostInformation { node_id: data.node_id, @@ -315,47 +332,58 @@ async fn try_get_description( pub(crate) struct RefreshData { host: String, node_id: NodeId, + expected_identity: ed25519::PublicKey, node_type: DescribedNodeType, port: Option, } -impl<'a> From<&'a LegacyMixNodeDetailsWithLayer> for RefreshData { - fn from(node: &'a LegacyMixNodeDetailsWithLayer) -> Self { - RefreshData::new( +impl<'a> TryFrom<&'a LegacyMixNodeDetailsWithLayer> for RefreshData { + type Error = ed25519::Ed25519RecoveryError; + + fn try_from(node: &'a LegacyMixNodeDetailsWithLayer) -> Result { + Ok(RefreshData::new( &node.bond_information.mix_node.host, + node.bond_information.identity().parse()?, DescribedNodeType::LegacyMixnode, node.mix_id(), Some(node.bond_information.mix_node.http_api_port), - ) + )) } } -impl<'a> From<&'a LegacyGatewayBondWithId> for RefreshData { - fn from(node: &'a LegacyGatewayBondWithId) -> Self { - RefreshData::new( +impl<'a> TryFrom<&'a LegacyGatewayBondWithId> for RefreshData { + type Error = ed25519::Ed25519RecoveryError; + + fn try_from(node: &'a LegacyGatewayBondWithId) -> Result { + Ok(RefreshData::new( &node.bond.gateway.host, + node.bond.identity().parse()?, DescribedNodeType::LegacyGateway, node.node_id, None, - ) + )) } } -impl<'a> From<&'a NymNodeDetails> for RefreshData { - fn from(node: &'a NymNodeDetails) -> Self { - RefreshData::new( +impl<'a> TryFrom<&'a NymNodeDetails> for RefreshData { + type Error = ed25519::Ed25519RecoveryError; + + fn try_from(node: &'a NymNodeDetails) -> Result { + Ok(RefreshData::new( &node.bond_information.node.host, + node.bond_information.identity().parse()?, DescribedNodeType::NymNode, node.node_id(), node.bond_information.node.custom_http_port, - ) + )) } } impl RefreshData { pub fn new( host: impl Into, + expected_identity: ed25519::PublicKey, node_type: DescribedNodeType, node_id: NodeId, port: Option, @@ -363,6 +391,7 @@ impl RefreshData { RefreshData { host: host.into(), node_id, + expected_identity, node_type, port, } @@ -404,7 +433,9 @@ impl CacheItemProvider for NodeDescriptionProvider { None => error!("failed to obtain mixnodes information from the cache"), Some(legacy_mixnodes) => { for node in &**legacy_mixnodes { - nodes_to_query.push(node.into()) + if let Ok(data) = node.try_into() { + nodes_to_query.push(data); + } } } } @@ -413,7 +444,9 @@ impl CacheItemProvider for NodeDescriptionProvider { None => error!("failed to obtain gateways information from the cache"), Some(legacy_gateways) => { for node in &**legacy_gateways { - nodes_to_query.push(node.into()) + if let Ok(data) = node.try_into() { + nodes_to_query.push(data); + } } } } @@ -422,7 +455,9 @@ impl CacheItemProvider for NodeDescriptionProvider { None => error!("failed to obtain nym-nodes information from the cache"), Some(nym_nodes) => { for node in &**nym_nodes { - nodes_to_query.push(node.into()) + if let Ok(data) = node.try_into() { + nodes_to_query.push(data); + } } } } diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index 1738901550..236a4f22cf 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -385,7 +385,7 @@ impl NymContractCache { .iter() .find(|n| n.bond_information.identity() == encoded_identity) { - return Some(nym_node.into()); + return nym_node.try_into().ok(); } // 2. check legacy mixnodes @@ -394,7 +394,7 @@ impl NymContractCache { .iter() .find(|n| n.bond_information.identity() == encoded_identity) { - return Some(mixnode.into()); + return mixnode.try_into().ok(); } // 3. check legacy gateways @@ -403,7 +403,7 @@ impl NymContractCache { .iter() .find(|n| n.identity() == &encoded_identity) { - return Some(gateway.into()); + return gateway.try_into().ok(); } None From 25766dc0ec2449cac8520516ad562931eb665899 Mon Sep 17 00:00:00 2001 From: RadekSabacky Date: Tue, 14 Jan 2025 13:22:31 +0100 Subject: [PATCH 76/80] + add alert message into nav components --- explorer-nextjs/app/components/Nav/DesktopNav.tsx | 2 ++ explorer-nextjs/app/components/Nav/MobileNav.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/explorer-nextjs/app/components/Nav/DesktopNav.tsx b/explorer-nextjs/app/components/Nav/DesktopNav.tsx index 618037ea04..e1faab46f1 100644 --- a/explorer-nextjs/app/components/Nav/DesktopNav.tsx +++ b/explorer-nextjs/app/components/Nav/DesktopNav.tsx @@ -20,6 +20,7 @@ import { NYM_WEBSITE } from '@/app/api/constants' import { useMainContext } from '@/app/context/main' import { MobileDrawerClose } from '@/app/icons/MobileDrawerClose' import { NavOptionType, originalNavOptions } from '@/app/context/nav' +import { ReleaseAlert } from '@/app/components/ReleaseAlert' import { DarkLightSwitchDesktop } from '@/app/components/Switch' import { Footer } from '@/app/components/Footer' import { ConnectKeplrWallet } from '@/app/components/Wallet/ConnectKeplrWallet' @@ -369,6 +370,7 @@ export const Nav: FCWithChildren = ({ children }) => { style={{ width: `calc(100% - ${drawerWidth}px` }} sx={{ py: 5, px: 6, mt: 7 }} > + {children}