diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 814b81246b..cd3cfb848b 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -38,7 +38,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ arc-ubuntu-22.04, custom-windows-11, custom-macos-15 ] + os: [ arc-linux-latest, custom-windows-11, custom-macos-15 ] runs-on: ${{ matrix.os }} env: CARGO_TERM_COLOR: always @@ -46,9 +46,9 @@ jobs: RUSTUP_PERMIT_COPY_RENAME: 1 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 + 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 cmake continue-on-error: true - if: contains(matrix.os, 'ubuntu') + if: contains(matrix.os, 'linux') - name: Check out repository code uses: actions/checkout@v4 @@ -63,7 +63,7 @@ jobs: # To avoid running out of disk space, skip generating debug symbols - name: Set debug to false (unix) - if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'mac') + if: contains(matrix.os, 'linux') || contains(matrix.os, 'mac') run: | sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml git diff @@ -93,14 +93,14 @@ jobs: command: build - name: Build all examples - if: contains(matrix.os, 'ubuntu') + if: contains(matrix.os, 'linux') uses: actions-rs/cargo@v1 with: command: build args: --workspace --examples - name: Run all tests - if: contains(matrix.os, 'ubuntu') + if: contains(matrix.os, 'linux') uses: actions-rs/cargo@v1 env: NYM_API: https://sandbox-nym-api1.nymtech.net/api @@ -109,7 +109,7 @@ jobs: args: --workspace - name: Run expensive tests - if: (github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master') && contains(matrix.os, 'ubuntu') + if: (github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master') && contains(matrix.os, 'linux') uses: actions-rs/cargo@v1 with: command: test diff --git a/.github/workflows/publish-nym-binaries.yml b/.github/workflows/publish-nym-binaries.yml index 5ceb1477b2..276987e522 100644 --- a/.github/workflows/publish-nym-binaries.yml +++ b/.github/workflows/publish-nym-binaries.yml @@ -30,13 +30,11 @@ jobs: release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} client_hash: ${{ steps.binary-hashes.outputs.client_hash }} nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }} - nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }} socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }} netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }} cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }} client_version: ${{ steps.binary-versions.outputs.client_version }} nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }} - nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }} socks5_version: ${{ steps.binary-versions.outputs.socks5_version }} netreq_version: ${{ steps.binary-versions.outputs.netreq_version }} cli_version: ${{ steps.binary-versions.outputs.cli_version }} @@ -56,7 +54,7 @@ jobs: - name: Install Rust stable uses: actions-rs/toolchain@v1 with: - toolchain: 1.86.0 + toolchain: 1.88.0 override: true - name: Build all binaries @@ -76,7 +74,6 @@ jobs: target/release/nym-network-requester target/release/nym-cli target/release/nymvisor - target/release/nym-node retention-days: 30 - id: create-release @@ -91,7 +88,6 @@ jobs: target/release/nym-network-requester target/release/nym-cli target/release/nymvisor - target/release/nym-node push-release-data-client: if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} diff --git a/.github/workflows/push-node-status-agent.yaml b/.github/workflows/push-node-status-agent.yaml index b2c156b1d9..00c8cbc774 100644 --- a/.github/workflows/push-node-status-agent.yaml +++ b/.github/workflows/push-node-status-agent.yaml @@ -40,7 +40,8 @@ jobs: - name: Get version from cargo.toml id: get_version run: | - yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml + VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml) + echo "result=$VERSION" >> $GITHUB_OUTPUT - name: cleanup-gateway-probe-ref id: cleanup_gateway_probe_ref @@ -52,13 +53,16 @@ jobs: - name: Set GIT_TAG variable run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV - - name: Set RELEASE_TAG variable + - name: Initialize RELEASE_TAG + run: echo "RELEASE_TAG=" >> $GITHUB_ENV + + - name: Set RELEASE_TAG for release if: github.event.inputs.release_image == 'true' run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV - + - name: Set IMAGE_NAME_AND_TAGS variable run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV - + - name: New env vars run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'" diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e14815ce..759005e547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,48 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2025.16-halloumi] (2025-09-16) + +- Backport metadata endpoint ([#6010]) +- bugfix: make sure tables are removed in correct order to not trigger FK constraint issue ([#5987]) +- chore: move authenticator into gateway crate ([#5982]) +- Fix the ns api ci workflow ([#5981]) +- Remove freshness check on testrun submit ([#5977]) +- Update sysinfo to the latest ([#5976]) +- bugfix: manually calculate per node work on rewarded set changes ([#5972]) +- fixing the ci for ns agent ([#5965]) +- Feature/testing utils ([#5963]) +- bugfix: fix ci-build for linux (and use updated runner) ([#5958]) +- chore: updated refs to cheddar rev of nym repo ([#5955]) +- http api client adjustment ([#5953]) +- chore: fix rust 1.89 clippy issues ([#5944]) +- Wireguard metadata client library ([#5943]) +- chore: remove unused import ([#5942]) +- feat: introduce additional checks when attempting to send to bounded channels ([#5941]) +- Move credential verifier in peer controller ([#5938]) +- change PK/FK on expiration date signatures tables ([#5934]) +- Wireguard private metadata ([#5915]) + +[#6010]: https://github.com/nymtech/nym/pull/6010 +[#5987]: https://github.com/nymtech/nym/pull/5987 +[#5982]: https://github.com/nymtech/nym/pull/5982 +[#5981]: https://github.com/nymtech/nym/pull/5981 +[#5977]: https://github.com/nymtech/nym/pull/5977 +[#5976]: https://github.com/nymtech/nym/pull/5976 +[#5972]: https://github.com/nymtech/nym/pull/5972 +[#5965]: https://github.com/nymtech/nym/pull/5965 +[#5963]: https://github.com/nymtech/nym/pull/5963 +[#5958]: https://github.com/nymtech/nym/pull/5958 +[#5955]: https://github.com/nymtech/nym/pull/5955 +[#5953]: https://github.com/nymtech/nym/pull/5953 +[#5944]: https://github.com/nymtech/nym/pull/5944 +[#5943]: https://github.com/nymtech/nym/pull/5943 +[#5942]: https://github.com/nymtech/nym/pull/5942 +[#5941]: https://github.com/nymtech/nym/pull/5941 +[#5938]: https://github.com/nymtech/nym/pull/5938 +[#5934]: https://github.com/nymtech/nym/pull/5934 +[#5915]: https://github.com/nymtech/nym/pull/5915 + ## [2025.15-gruyere] (2025-08-20) - Migrate strum to 0.27.2 ([#5960]) diff --git a/Cargo.lock b/Cargo.lock index 063fbb7ea3..360ca7476c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2321,8 +2321,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "nym-coconut-dkg-common 0.1.0", - "nym-contracts-common 0.5.0", + "nym-coconut-dkg-common", + "nym-contracts-common", ] [[package]] @@ -2400,8 +2400,8 @@ dependencies = [ "dashmap", "dirs", "futures", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", + "nym-bin-common", + "nym-crypto", "nym-sdk", "serde", "tempfile", @@ -2546,15 +2546,6 @@ dependencies = [ "log", ] -[[package]] -name = "envy" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" -dependencies = [ - "serde", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -2939,7 +2930,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows 0.61.3", + "windows", ] [[package]] @@ -2990,18 +2981,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getset" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" -dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "ghash" version = "0.5.1" @@ -3249,15 +3228,6 @@ dependencies = [ "http 1.3.1", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -3630,7 +3600,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -3775,11 +3745,11 @@ dependencies = [ "clap", "dirs", "importer-contract", - "nym-bin-common 0.6.0", - "nym-mixnet-contract-common 0.6.0", - "nym-network-defaults 0.1.0", - "nym-validator-client 0.1.0", - "nym-vesting-contract-common 0.7.0", + "nym-bin-common", + "nym-mixnet-contract-common", + "nym-network-defaults", + "nym-validator-client", + "nym-vesting-contract-common", "serde", "serde_json", "tokio", @@ -4514,8 +4484,8 @@ dependencies = [ "async-trait", "futures", "js-sys", - "nym-bin-common 0.6.0", - "nym-http-api-client 0.1.0", + "nym-bin-common", + "nym-http-api-client", "nym-ordered-buffer", "nym-service-providers-common", "nym-socks5-requests", @@ -4539,9 +4509,9 @@ dependencies = [ "anyhow", "clap", "futures", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", - "nym-network-defaults 0.1.0", + "nym-bin-common", + "nym-crypto", + "nym-network-defaults", "nym-sdk", "tokio", "tracing", @@ -4855,7 +4825,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.64" +version = "1.1.65" dependencies = [ "anyhow", "async-trait", @@ -4877,35 +4847,35 @@ dependencies = [ "futures", "humantime-serde", "moka", - "nym-api-requests 0.1.0", + "nym-api-requests", "nym-bandwidth-controller", - "nym-bin-common 0.6.0", - "nym-coconut-dkg-common 0.1.0", - "nym-compact-ecash 0.1.0", - "nym-config 0.1.0", - "nym-contracts-common 0.5.0", + "nym-bin-common", + "nym-coconut-dkg-common", + "nym-compact-ecash", + "nym-config", + "nym-contracts-common", "nym-credential-storage", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", + "nym-credentials-interface", + "nym-crypto", "nym-dkg", - "nym-ecash-contract-common 0.1.0", + "nym-ecash-contract-common", "nym-ecash-signer-check", - "nym-ecash-time 0.1.0", + "nym-ecash-time", "nym-gateway-client", - "nym-http-api-common 0.1.0", - "nym-mixnet-contract-common 0.6.0", - "nym-node-requests 0.1.0", + "nym-http-api-common", + "nym-mixnet-contract-common", + "nym-node-requests", "nym-node-tester-utils", - "nym-pemstore 0.3.0", - "nym-serde-helpers 0.1.0", - "nym-sphinx 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-ticketbooks-merkle 0.1.0", - "nym-topology 0.1.0", + "nym-pemstore", + "nym-serde-helpers", + "nym-sphinx", + "nym-statistics-common", + "nym-task", + "nym-ticketbooks-merkle", + "nym-topology", "nym-types", - "nym-validator-client 0.1.0", + "nym-validator-client", "pin-project", "rand 0.8.5", "rand_chacha 0.3.1", @@ -4943,20 +4913,20 @@ dependencies = [ "ecdsa", "hex", "humantime-serde", - "nym-coconut-dkg-common 0.1.0", - "nym-compact-ecash 0.1.0", - "nym-config 0.1.0", - "nym-contracts-common 0.5.0", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", + "nym-coconut-dkg-common", + "nym-compact-ecash", + "nym-config", + "nym-contracts-common", + "nym-credentials-interface", + "nym-crypto", "nym-ecash-signer-check-types", - "nym-ecash-time 0.1.0", - "nym-mixnet-contract-common 0.6.0", - "nym-network-defaults 0.1.0", - "nym-node-requests 0.1.0", + "nym-ecash-time", + "nym-mixnet-contract-common", + "nym-network-defaults", + "nym-node-requests", "nym-noise-keys", - "nym-serde-helpers 0.1.0", - "nym-ticketbooks-merkle 0.1.0", + "nym-serde-helpers", + "nym-ticketbooks-merkle", "rand_chacha 0.3.1", "schemars 0.8.22", "serde", @@ -4971,40 +4941,6 @@ dependencies = [ "utoipa", ] -[[package]] -name = "nym-api-requests" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bs58", - "cosmrs", - "cosmwasm-std", - "ecdsa", - "getset", - "hex", - "humantime-serde", - "nym-compact-ecash 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-config 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-credentials-interface 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-ecash-time 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-mixnet-contract-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-node-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-ticketbooks-merkle 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars 0.8.22", - "serde", - "serde_json", - "sha2 0.10.9", - "tendermint", - "tendermint-rpc", - "thiserror 2.0.12", - "time", - "utoipa", -] - [[package]] name = "nym-async-file-watcher" version = "0.1.0" @@ -5015,51 +4951,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "nym-authenticator" -version = "0.1.0" -dependencies = [ - "anyhow", - "bincode", - "bs58", - "bytes", - "clap", - "defguard_wireguard_rs", - "fastrand 2.3.0", - "futures", - "ipnetwork", - "log", - "mock_instant", - "nym-authenticator-requests", - "nym-bin-common 0.6.0", - "nym-client-core", - "nym-config 0.1.0", - "nym-credential-verification", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-gateway-requests", - "nym-gateway-storage", - "nym-id", - "nym-network-defaults 0.1.0", - "nym-sdk", - "nym-service-provider-requests-common", - "nym-service-providers-common", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", - "nym-types", - "nym-wireguard", - "nym-wireguard-types 0.1.0", - "rand 0.8.5", - "serde", - "serde_json", - "thiserror 2.0.12", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "url", -] - [[package]] name = "nym-authenticator-requests" version = "0.1.0" @@ -5067,12 +4958,12 @@ dependencies = [ "base64 0.22.1", "bincode", "hmac", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-network-defaults 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-network-defaults", "nym-service-provider-requests-common", - "nym-sphinx 0.1.0", - "nym-wireguard-types 0.1.0", + "nym-sphinx", + "nym-wireguard-types", "rand 0.8.5", "serde", "sha2 0.10.9", @@ -5088,13 +4979,13 @@ dependencies = [ "log", "nym-credential-storage", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-ecash-contract-common 0.1.0", - "nym-ecash-time 0.1.0", - "nym-network-defaults 0.1.0", - "nym-task 0.1.0", - "nym-validator-client 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-contract-common", + "nym-ecash-time", + "nym-network-defaults", + "nym-task", + "nym-validator-client", "rand 0.8.5", "thiserror 2.0.12", "url", @@ -5123,22 +5014,9 @@ dependencies = [ "vergen", ] -[[package]] -name = "nym-bin-common" -version = "0.6.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "const-str", - "log", - "schemars 0.8.22", - "serde", - "utoipa", - "vergen", -] - [[package]] name = "nym-cli" -version = "1.1.61" +version = "1.1.62" dependencies = [ "anyhow", "base64 0.22.1", @@ -5150,10 +5028,10 @@ dependencies = [ "dotenvy", "inquire", "log", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-cli-commands", - "nym-network-defaults 0.1.0", - "nym-validator-client 0.1.0", + "nym-network-defaults", + "nym-validator-client", "serde", "serde_json", "tap", @@ -5183,28 +5061,28 @@ dependencies = [ "k256", "log", "nym-bandwidth-controller", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core", - "nym-coconut-dkg-common 0.1.0", - "nym-config 0.1.0", - "nym-contracts-common 0.5.0", + "nym-coconut-dkg-common", + "nym-config", + "nym-contracts-common", "nym-credential-proxy-requests", "nym-credential-storage", "nym-credential-utils", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-ecash-contract-common 0.1.0", - "nym-ecash-time 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-contract-common", + "nym-ecash-time", "nym-id", - "nym-mixnet-contract-common 0.6.0", - "nym-multisig-contract-common 0.1.0", - "nym-network-defaults 0.1.0", - "nym-pemstore 0.3.0", - "nym-sphinx 0.1.0", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", "nym-types", - "nym-validator-client 0.1.0", - "nym-vesting-contract-common 0.7.0", + "nym-validator-client", + "nym-vesting-contract-common", "rand 0.8.5", "serde", "serde_json", @@ -5220,7 +5098,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.61" +version = "1.1.62" dependencies = [ "bs58", "clap", @@ -5228,21 +5106,21 @@ dependencies = [ "futures", "log", "nym-bandwidth-controller", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core", "nym-client-websocket-requests", - "nym-config 0.1.0", + "nym-config", "nym-credential-storage", "nym-credentials", - "nym-crypto 0.4.0", + "nym-crypto", "nym-gateway-requests", "nym-id", - "nym-network-defaults 0.1.0", - "nym-pemstore 0.3.0", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", - "nym-topology 0.1.0", - "nym-validator-client 0.1.0", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", + "nym-task", + "nym-topology", + "nym-validator-client", "rand 0.8.5", "serde", "serde_json", @@ -5276,22 +5154,22 @@ dependencies = [ "nym-client-core-gateways-storage", "nym-client-core-surb-storage", "nym-credential-storage", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-ecash-time 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-time", "nym-gateway-client", "nym-gateway-requests", - "nym-http-api-client 0.1.0", + "nym-http-api-client", "nym-id", "nym-mixnet-client", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-nonexhaustive-delayqueue", - "nym-pemstore 0.3.0", - "nym-sphinx 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-topology 0.1.0", - "nym-validator-client 0.1.0", + "nym-pemstore", + "nym-sphinx", + "nym-statistics-common", + "nym-task", + "nym-topology", + "nym-validator-client", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -5319,11 +5197,11 @@ name = "nym-client-core-config-types" version = "0.1.0" dependencies = [ "humantime-serde", - "nym-config 0.1.0", - "nym-pemstore 0.3.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-statistics-common 0.1.0", + "nym-config", + "nym-pemstore", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-statistics-common", "serde", "thiserror 2.0.12", "url", @@ -5335,7 +5213,7 @@ version = "0.1.0" dependencies = [ "async-trait", "cosmrs", - "nym-crypto 0.4.0", + "nym-crypto", "nym-gateway-requests", "serde", "sqlx", @@ -5353,9 +5231,9 @@ version = "0.1.0" dependencies = [ "async-trait", "dashmap", - "nym-crypto 0.4.0", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", + "nym-crypto", + "nym-sphinx", + "nym-task", "sqlx", "sqlx-pool-guard", "thiserror 2.0.12", @@ -5371,7 +5249,7 @@ dependencies = [ "anyhow", "futures", "js-sys", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-node-tester-utils", "nym-node-tester-wasm", "rand 0.8.5", @@ -5391,7 +5269,7 @@ dependencies = [ name = "nym-client-websocket-requests" version = "0.1.0" dependencies = [ - "nym-sphinx 0.1.0", + "nym-sphinx", "serde", "serde_json", ] @@ -5405,22 +5283,8 @@ dependencies = [ "cw-utils", "cw2", "cw4", - "nym-contracts-common 0.5.0", - "nym-multisig-contract-common 0.1.0", -] - -[[package]] -name = "nym-coconut-dkg-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils", - "cw2", - "cw4", - "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-multisig-contract-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-contracts-common", + "nym-multisig-contract-common", ] [[package]] @@ -5436,8 +5300,8 @@ dependencies = [ "ff", "group", "itertools 0.14.0", - "nym-network-defaults 0.1.0", - "nym-pemstore 0.3.0", + "nym-network-defaults", + "nym-pemstore", "rand 0.8.5", "rayon", "serde", @@ -5447,29 +5311,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-compact-ecash" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bincode", - "bls12_381", - "bs58", - "cfg-if", - "digest 0.10.7", - "ff", - "group", - "itertools 0.14.0", - "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-pemstore 0.3.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "rand 0.8.5", - "serde", - "sha2 0.10.9", - "subtle 2.6.1", - "thiserror 2.0.12", - "zeroize", -] - [[package]] name = "nym-config" version = "0.1.0" @@ -5477,22 +5318,7 @@ dependencies = [ "dirs", "handlebars", "log", - "nym-network-defaults 0.1.0", - "serde", - "thiserror 2.0.12", - "toml 0.8.23", - "url", -] - -[[package]] -name = "nym-config" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "dirs", - "handlebars", - "log", - "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-network-defaults", "serde", "thiserror 2.0.12", "toml 0.8.23", @@ -5516,21 +5342,6 @@ dependencies = [ "vergen", ] -[[package]] -name = "nym-contracts-common" -version = "0.5.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bs58", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "schemars 0.8.22", - "serde", - "thiserror 2.0.12", - "vergen", -] - [[package]] name = "nym-contracts-common-testing" version = "0.1.0" @@ -5551,10 +5362,10 @@ dependencies = [ "anyhow", "bs58", "lazy_static", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-ffi-shared", "nym-sdk", - "nym-sphinx-anonymous-replies 0.1.0", + "nym-sphinx-anonymous-replies", "tokio", ] @@ -5563,40 +5374,36 @@ name = "nym-credential-proxy" version = "0.1.7" dependencies = [ "anyhow", - "async-trait", "axum 0.7.9", "bip39", "bs58", "cfg-if", "clap", - "colored", - "dotenvy", "futures", "humantime", - "nym-bin-common 0.6.0", - "nym-compact-ecash 0.1.0", - "nym-config 0.1.0", + "nym-bin-common", + "nym-compact-ecash", + "nym-config", "nym-credential-proxy-requests", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-ecash-contract-common 0.1.0", - "nym-http-api-common 0.1.0", - "nym-network-defaults 0.1.0", - "nym-validator-client 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-contract-common", + "nym-http-api-common", + "nym-network-defaults", + "nym-validator-client", "rand 0.8.5", "reqwest 0.12.22", "serde", "serde_json", "sqlx", - "strum 0.26.3", - "strum_macros 0.26.4", + "strum", + "strum_macros", "tempfile", "thiserror 2.0.12", "time", "tokio", "tokio-util", - "tower 0.5.2", "tower-http 0.5.2", "tracing", "url", @@ -5612,10 +5419,10 @@ version = "0.1.0" dependencies = [ "async-trait", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-http-api-client 0.1.0", - "nym-http-api-common 0.1.0", - "nym-serde-helpers 0.1.0", + "nym-credentials-interface", + "nym-http-api-client", + "nym-http-api-common", + "nym-serde-helpers", "reqwest 0.12.22", "schemars 0.8.22", "serde", @@ -5635,9 +5442,9 @@ dependencies = [ "async-trait", "bincode", "log", - "nym-compact-ecash 0.1.0", + "nym-compact-ecash", "nym-credentials", - "nym-ecash-time 0.1.0", + "nym-ecash-time", "serde", "sqlx", "sqlx-pool-guard", @@ -5653,12 +5460,12 @@ dependencies = [ "log", "nym-bandwidth-controller", "nym-client-core", - "nym-config 0.1.0", + "nym-config", "nym-credential-storage", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-ecash-time 0.1.0", - "nym-validator-client 0.1.0", + "nym-credentials-interface", + "nym-ecash-time", + "nym-validator-client", "thiserror 2.0.12", "time", "tokio", @@ -5674,14 +5481,14 @@ dependencies = [ "cw-utils", "dyn-clone", "futures", - "nym-api-requests 0.1.0", + "nym-api-requests", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-ecash-contract-common 0.1.0", + "nym-credentials-interface", + "nym-ecash-contract-common", "nym-gateway-requests", "nym-gateway-storage", - "nym-task 0.1.0", - "nym-validator-client 0.1.0", + "nym-task", + "nym-validator-client", "rand 0.8.5", "si-scale", "thiserror 2.0.12", @@ -5698,14 +5505,14 @@ dependencies = [ "bls12_381", "cosmrs", "log", - "nym-api-requests 0.1.0", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-ecash-contract-common 0.1.0", - "nym-ecash-time 0.1.0", - "nym-network-defaults 0.1.0", - "nym-serde-helpers 0.1.0", - "nym-validator-client 0.1.0", + "nym-api-requests", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-contract-common", + "nym-ecash-time", + "nym-network-defaults", + "nym-serde-helpers", + "nym-validator-client", "rand 0.8.5", "serde", "thiserror 2.0.12", @@ -5718,29 +5525,13 @@ name = "nym-credentials-interface" version = "0.1.0" dependencies = [ "bls12_381", - "nym-compact-ecash 0.1.0", - "nym-ecash-time 0.1.0", - "nym-network-defaults 0.1.0", + "nym-compact-ecash", + "nym-ecash-time", + "nym-network-defaults", "rand 0.8.5", "serde", - "strum 0.26.3", - "thiserror 2.0.12", - "time", - "utoipa", -] - -[[package]] -name = "nym-credentials-interface" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bls12_381", - "nym-compact-ecash 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-ecash-time 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "rand 0.8.5", - "serde", - "strum 0.26.3", + "strum", + "strum_macros", "thiserror 2.0.12", "time", "utoipa", @@ -5762,8 +5553,8 @@ dependencies = [ "generic-array 0.14.7", "hkdf", "hmac", - "nym-pemstore 0.3.0", - "nym-sphinx-types 0.2.0", + "nym-pemstore", + "nym-sphinx-types", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -5775,35 +5566,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-crypto" -version = "0.4.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "aead", - "aes", - "aes-gcm-siv", - "blake3", - "bs58", - "cipher", - "ctr", - "digest 0.10.7", - "ed25519-dalek", - "generic-array 0.14.7", - "hkdf", - "hmac", - "nym-pemstore 0.3.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "rand 0.8.5", - "serde", - "serde_bytes", - "sha2 0.10.9", - "subtle-encoding", - "thiserror 2.0.12", - "x25519-dalek", - "zeroize", -] - [[package]] name = "nym-dkg" version = "0.1.0" @@ -5815,8 +5577,8 @@ dependencies = [ "ff", "group", "lazy_static", - "nym-contracts-common 0.5.0", - "nym-pemstore 0.3.0", + "nym-contracts-common", + "nym-pemstore", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -5837,21 +5599,7 @@ dependencies = [ "cw-controllers", "cw-utils", "cw2", - "nym-multisig-contract-common 0.1.0", - "thiserror 2.0.12", -] - -[[package]] -name = "nym-ecash-contract-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bs58", - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers", - "cw-utils", - "nym-multisig-contract-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-multisig-contract-common", "thiserror 2.0.12", ] @@ -5861,8 +5609,8 @@ version = "0.1.0" dependencies = [ "futures", "nym-ecash-signer-check-types", - "nym-network-defaults 0.1.0", - "nym-validator-client 0.1.0", + "nym-network-defaults", + "nym-validator-client", "semver 1.0.26", "thiserror 2.0.12", "tokio", @@ -5874,8 +5622,8 @@ dependencies = [ name = "nym-ecash-signer-check-types" version = "0.1.0" dependencies = [ - "nym-coconut-dkg-common 0.1.0", - "nym-crypto 0.4.0", + "nym-coconut-dkg-common", + "nym-crypto", "semver 1.0.26", "serde", "thiserror 2.0.12", @@ -5889,15 +5637,7 @@ dependencies = [ name = "nym-ecash-time" version = "0.1.0" dependencies = [ - "nym-compact-ecash 0.1.0", - "time", -] - -[[package]] -name = "nym-ecash-time" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ + "nym-compact-ecash", "time", ] @@ -5921,18 +5661,6 @@ dependencies = [ "utoipa", ] -[[package]] -name = "nym-exit-policy" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "serde", - "serde_json", - "thiserror 2.0.12", - "tracing", - "utoipa", -] - [[package]] name = "nym-ffi-shared" version = "0.2.1" @@ -5940,10 +5668,10 @@ dependencies = [ "anyhow", "bs58", "lazy_static", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", + "nym-bin-common", + "nym-crypto", "nym-sdk", - "nym-sphinx-anonymous-replies 0.1.0", + "nym-sphinx-anonymous-replies", "tokio", "uniffi", "uniffi_build", @@ -5955,39 +5683,46 @@ version = "1.1.36" dependencies = [ "anyhow", "async-trait", + "bincode", "bip39", "bs58", "dashmap", "defguard_wireguard_rs", + "fastrand 2.3.0", "futures", "ipnetwork", - "nym-api-requests 0.1.0", - "nym-authenticator", + "mock_instant", + "nym-api-requests", + "nym-authenticator-requests", + "nym-client-core", "nym-credential-verification", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", + "nym-credentials-interface", + "nym-crypto", "nym-gateway-requests", "nym-gateway-stats-storage", "nym-gateway-storage", + "nym-id", "nym-ip-packet-router", "nym-mixnet-client", "nym-mixnode-common", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-network-requester", "nym-node-metrics", "nym-sdk", - "nym-sphinx 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-topology 0.1.0", + "nym-service-provider-requests-common", + "nym-sphinx", + "nym-statistics-common", + "nym-task", + "nym-topology", "nym-types", - "nym-validator-client 0.1.0", + "nym-validator-client", "nym-wireguard", - "nym-wireguard-types 0.1.0", + "nym-wireguard-private-metadata-server", + "nym-wireguard-types", "rand 0.8.5", + "serde", "sha2 0.10.9", - "sqlx", "thiserror 2.0.12", "time", "tokio", @@ -6009,16 +5744,16 @@ dependencies = [ "nym-bandwidth-controller", "nym-credential-storage", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", + "nym-credentials-interface", + "nym-crypto", "nym-gateway-requests", - "nym-http-api-client 0.1.0", - "nym-network-defaults 0.1.0", - "nym-pemstore 0.3.0", - "nym-sphinx 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-validator-client 0.1.0", + "nym-http-api-client", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", + "nym-statistics-common", + "nym-task", + "nym-validator-client", "rand 0.8.5", "serde", "si-scale", @@ -6041,22 +5776,24 @@ dependencies = [ name = "nym-gateway-requests" version = "0.1.0" dependencies = [ + "anyhow", "bs58", "futures", "generic-array 0.14.7", - "nym-compact-ecash 0.1.0", + "nym-compact-ecash", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-pemstore 0.3.0", - "nym-serde-helpers 0.1.0", - "nym-sphinx 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-pemstore", + "nym-serde-helpers", + "nym-sphinx", + "nym-statistics-common", + "nym-task", + "nym-test-utils", "rand 0.8.5", "serde", "serde_json", - "strum 0.26.3", + "strum", "subtle 2.6.1", "thiserror 2.0.12", "time", @@ -6072,10 +5809,10 @@ name = "nym-gateway-stats-storage" version = "0.1.0" dependencies = [ "nym-node-metrics", - "nym-sphinx 0.1.0", - "nym-statistics-common 0.1.0", + "nym-sphinx", + "nym-statistics-common", "sqlx", - "strum 0.26.3", + "strum", "thiserror 2.0.12", "time", "tokio", @@ -6090,9 +5827,9 @@ dependencies = [ "bincode", "defguard_wireguard_rs", "dyn-clone", - "nym-credentials-interface 0.1.0", + "nym-credentials-interface", "nym-gateway-requests", - "nym-sphinx 0.1.0", + "nym-sphinx", "sqlx", "thiserror 2.0.12", "time", @@ -6106,11 +5843,11 @@ version = "0.2.2" dependencies = [ "anyhow", "lazy_static", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", + "nym-bin-common", + "nym-crypto", "nym-ffi-shared", "nym-sdk", - "nym-sphinx-anonymous-replies 0.1.0", + "nym-sphinx-anonymous-replies", "thiserror 2.0.12", "tokio", "uniffi", @@ -6128,18 +5865,6 @@ dependencies = [ "serde", ] -[[package]] -name = "nym-group-contract-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "cosmwasm-schema", - "cw-controllers", - "cw4", - "schemars 0.8.22", - "serde", -] - [[package]] name = "nym-http-api-client" version = "0.1.0" @@ -6152,8 +5877,8 @@ dependencies = [ "http 1.3.1", "itertools 0.14.0", "mime", - "nym-bin-common 0.6.0", - "nym-http-api-common 0.1.0", + "nym-bin-common", + "nym-http-api-common", "once_cell", "reqwest 0.12.22", "serde", @@ -6165,30 +5890,6 @@ dependencies = [ "wasmtimer", ] -[[package]] -name = "nym-http-api-client" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "async-trait", - "bincode", - "bytes", - "encoding_rs", - "hickory-resolver", - "http 1.3.1", - "mime", - "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-http-api-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "once_cell", - "reqwest 0.12.22", - "serde", - "serde_json", - "thiserror 2.0.12", - "tracing", - "url", - "wasmtimer", -] - [[package]] name = "nym-http-api-common" version = "0.1.0" @@ -6211,23 +5912,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-http-api-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "axum 0.7.9", - "axum-client-ip", - "bincode", - "colored", - "futures", - "serde", - "subtle 2.6.1", - "tower 0.5.2", - "tracing", - "zeroize", -] - [[package]] name = "nym-id" version = "0.1.0" @@ -6247,7 +5931,7 @@ dependencies = [ "anyhow", "bs58", "clap", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-credential-storage", "nym-id", "tokio", @@ -6269,10 +5953,10 @@ version = "0.1.0" dependencies = [ "bincode", "bytes", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", + "nym-bin-common", + "nym-crypto", "nym-service-provider-requests-common", - "nym-sphinx 0.1.0", + "nym-sphinx", "rand 0.8.5", "serde", "thiserror 2.0.12", @@ -6294,24 +5978,24 @@ dependencies = [ "etherparse", "futures", "log", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core", - "nym-config 0.1.0", - "nym-crypto 0.4.0", - "nym-exit-policy 0.1.0", + "nym-config", + "nym-crypto", + "nym-exit-policy", "nym-id", "nym-ip-packet-requests", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-network-requester", "nym-sdk", "nym-service-provider-requests-common", "nym-service-providers-common", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", + "nym-sphinx", + "nym-task", "nym-tun", "nym-types", "nym-wireguard", - "nym-wireguard-types 0.1.0", + "nym-wireguard-types", "rand 0.8.5", "reqwest 0.12.22", "serde", @@ -6345,27 +6029,16 @@ dependencies = [ "tracing", ] -[[package]] -name = "nym-metrics" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "dashmap", - "lazy_static", - "prometheus", - "tracing", -] - [[package]] name = "nym-mixnet-client" version = "0.1.0" dependencies = [ "dashmap", "futures", - "nym-crypto 0.4.0", + "nym-crypto", "nym-noise", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", + "nym-sphinx", + "nym-task", "rand 0.8.5", "tokio", "tokio-stream", @@ -6384,7 +6057,7 @@ dependencies = [ "cw-storage-plus", "cw2", "humantime-serde", - "nym-contracts-common 0.5.0", + "nym-contracts-common", "rand_chacha 0.3.1", "schemars 0.8.22", "semver 1.0.26", @@ -6396,28 +6069,6 @@ dependencies = [ "utoipa", ] -[[package]] -name = "nym-mixnet-contract-common" -version = "0.6.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bs58", - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers", - "cw-storage-plus", - "humantime-serde", - "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars 0.8.22", - "semver 1.0.26", - "serde", - "serde-json-wasm", - "serde_repr", - "thiserror 2.0.12", - "time", - "utoipa", -] - [[package]] name = "nym-mixnode-common" version = "0.1.0" @@ -6426,16 +6077,16 @@ dependencies = [ "futures", "humantime-serde", "log", - "nym-crypto 0.4.0", - "nym-metrics 0.1.0", - "nym-network-defaults 0.1.0", - "nym-sphinx-acknowledgements 0.1.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-forwarding 0.1.0", - "nym-sphinx-framing 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-types 0.2.0", - "nym-task 0.1.0", + "nym-crypto", + "nym-metrics", + "nym-network-defaults", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-forwarding", + "nym-sphinx-framing", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-task", "rand 0.8.5", "serde", "thiserror 2.0.12", @@ -6460,22 +6111,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "nym-multisig-contract-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw3", - "cw4", - "schemars 0.8.22", - "serde", - "thiserror 2.0.12", -] - [[package]] name = "nym-network-defaults" version = "0.1.0" @@ -6490,21 +6125,6 @@ dependencies = [ "utoipa", ] -[[package]] -name = "nym-network-defaults" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "cargo_metadata 0.18.1", - "dotenvy", - "log", - "regex", - "schemars 0.8.22", - "serde", - "url", - "utoipa", -] - [[package]] name = "nym-network-monitor" version = "1.0.2" @@ -6515,16 +6135,16 @@ dependencies = [ "dashmap", "futures", "log", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core", - "nym-crypto 0.4.0", + "nym-crypto", "nym-gateway-requests", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-sdk", - "nym-sphinx 0.1.0", - "nym-topology 0.1.0", + "nym-sphinx", + "nym-topology", "nym-types", - "nym-validator-client 0.1.0", + "nym-validator-client", "petgraph", "rand 0.8.5", "rand_chacha 0.3.1", @@ -6540,7 +6160,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.62" +version = "1.1.63" dependencies = [ "addr", "anyhow", @@ -6553,23 +6173,23 @@ dependencies = [ "ipnetwork", "log", "nym-async-file-watcher", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core", "nym-client-websocket-requests", - "nym-config 0.1.0", + "nym-config", "nym-credential-storage", "nym-credentials", - "nym-crypto 0.4.0", - "nym-exit-policy 0.1.0", + "nym-crypto", + "nym-exit-policy", "nym-id", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-ordered-buffer", "nym-sdk", "nym-service-providers-common", "nym-socks5-proxy-helpers", "nym-socks5-requests", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", + "nym-sphinx", + "nym-task", "nym-types", "publicsuffix", "rand 0.8.5", @@ -6590,7 +6210,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.17.0" +version = "1.18.0" dependencies = [ "anyhow", "arc-swap", @@ -6618,40 +6238,39 @@ dependencies = [ "indicatif 0.17.11", "ipnetwork", "lioness", - "nym-authenticator", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core-config-types", - "nym-config 0.1.0", - "nym-crypto 0.4.0", + "nym-config", + "nym-crypto", "nym-gateway", "nym-gateway-stats-storage", - "nym-http-api-client 0.1.0", - "nym-http-api-common 0.1.0", + "nym-http-api-client", + "nym-http-api-common", "nym-ip-packet-router", - "nym-metrics 0.1.0", + "nym-metrics", "nym-mixnet-client", "nym-network-requester", "nym-node-metrics", - "nym-node-requests 0.1.0", + "nym-node-requests", "nym-noise", "nym-noise-keys", "nym-nonexhaustive-delayqueue", - "nym-pemstore 0.3.0", - "nym-sphinx-acknowledgements 0.1.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-forwarding 0.1.0", - "nym-sphinx-framing 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-routing 0.1.0", - "nym-sphinx-types 0.2.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-topology 0.1.0", + "nym-pemstore", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-forwarding", + "nym-sphinx-framing", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-statistics-common", + "nym-task", + "nym-topology", "nym-types", - "nym-validator-client 0.1.0", + "nym-validator-client", "nym-verloc", "nym-wireguard", - "nym-wireguard-types 0.1.0", + "nym-wireguard-types", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -6679,9 +6298,9 @@ version = "0.1.0" dependencies = [ "dashmap", "futures", - "nym-metrics 0.1.0", - "nym-statistics-common 0.1.0", - "strum 0.26.3", + "nym-metrics", + "nym-statistics-common", + "strum", "time", "tokio", "tracing", @@ -6695,46 +6314,24 @@ dependencies = [ "celes", "humantime", "humantime-serde", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", - "nym-exit-policy 0.1.0", - "nym-http-api-client 0.1.0", + "nym-bin-common", + "nym-crypto", + "nym-exit-policy", + "nym-http-api-client", "nym-noise-keys", - "nym-wireguard-types 0.1.0", + "nym-wireguard-types", "rand_chacha 0.3.1", "schemars 0.8.22", "serde", "serde_json", - "strum 0.26.3", + "strum", + "strum_macros", "thiserror 2.0.12", "time", "tokio", "utoipa", ] -[[package]] -name = "nym-node-requests" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "async-trait", - "celes", - "humantime", - "humantime-serde", - "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-exit-policy 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-wireguard-types 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars 0.8.22", - "serde", - "serde_json", - "strum 0.26.3", - "thiserror 2.0.12", - "time", - "utoipa", -] - [[package]] name = "nym-node-status-agent" version = "1.0.4" @@ -6742,8 +6339,8 @@ dependencies = [ "anyhow", "clap", "futures", - "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-bin-common", + "nym-crypto", "nym-node-status-client", "rand 0.8.5", "tempfile", @@ -6754,7 +6351,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "3.2.2" +version = "3.3.0" dependencies = [ "ammonia", "anyhow", @@ -6764,24 +6361,24 @@ dependencies = [ "celes", "clap", "cosmwasm-std", - "envy", "futures-util", + "humantime", "itertools 0.14.0", "moka", - "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-http-api-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-mixnet-contract-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-bin-common", + "nym-contracts-common", + "nym-crypto", + "nym-http-api-client", + "nym-http-api-common", + "nym-mixnet-contract-common", + "nym-network-defaults", "nym-node-metrics", - "nym-node-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-node-requests", "nym-node-status-client", - "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-statistics-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-task 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-validator-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-serde-helpers", + "nym-statistics-common", + "nym-task", + "nym-validator-client", "rand 0.8.5", "rand_chacha 0.3.1", "regex", @@ -6791,8 +6388,8 @@ dependencies = [ "serde_json", "serde_json_path", "sqlx", - "strum 0.26.3", - "strum_macros 0.26.4", + "strum", + "strum_macros", "thiserror 2.0.12", "time", "tokio", @@ -6808,12 +6405,11 @@ dependencies = [ [[package]] name = "nym-node-status-client" -version = "0.1.2" +version = "0.2.0" dependencies = [ "anyhow", "bincode", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-crypto", "reqwest 0.12.22", "serde", "serde_json", @@ -6827,11 +6423,11 @@ version = "0.1.0" dependencies = [ "futures", "log", - "nym-crypto 0.4.0", - "nym-sphinx 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-task 0.1.0", - "nym-topology 0.1.0", + "nym-crypto", + "nym-sphinx", + "nym-sphinx-params", + "nym-task", + "nym-topology", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -6869,13 +6465,15 @@ dependencies = [ "arc-swap", "bytes", "futures", - "nym-crypto 0.4.0", + "nym-crypto", "nym-noise-keys", + "nym-test-utils", "pin-project", "rand_chacha 0.3.1", "sha2 0.10.9", "snow", - "strum 0.26.3", + "strum", + "strum_macros", "thiserror 2.0.12", "tokio", "tokio-util", @@ -6886,7 +6484,7 @@ dependencies = [ name = "nym-noise-keys" version = "0.1.0" dependencies = [ - "nym-crypto 0.4.0", + "nym-crypto", "schemars 0.8.22", "serde", "utoipa", @@ -6909,8 +6507,8 @@ dependencies = [ "anyhow", "clap", "log", - "nym-bin-common 0.6.0", - "nym-network-defaults 0.1.0", + "nym-bin-common", + "nym-network-defaults", "nym-sdk", "nym-service-providers-common", "nym-socks5-requests", @@ -6945,24 +6543,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-outfox" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "blake3", - "chacha20", - "chacha20poly1305", - "getrandom 0.2.16", - "log", - "rand 0.8.5", - "rayon", - "sphinx-packet", - "thiserror 2.0.12", - "x25519-dalek", - "zeroize", -] - [[package]] name = "nym-pemstore" version = "0.3.0" @@ -6972,15 +6552,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-pemstore" -version = "0.3.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "pem", - "tracing", -] - [[package]] name = "nym-performance-contract-common" version = "0.1.0" @@ -6988,7 +6559,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers", - "nym-contracts-common 0.5.0", + "nym-contracts-common", "schemars 0.8.22", "serde", "thiserror 2.0.12", @@ -7027,25 +6598,25 @@ dependencies = [ "httpcodec", "log", "nym-bandwidth-controller", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core", "nym-credential-storage", "nym-credential-utils", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", + "nym-credentials-interface", + "nym-crypto", "nym-gateway-requests", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-ordered-buffer", "nym-service-providers-common", "nym-socks5-client-core", "nym-socks5-requests", - "nym-sphinx 0.1.0", - "nym-sphinx-addressing 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-topology 0.1.0", - "nym-validator-client 0.1.0", + "nym-sphinx", + "nym-sphinx-addressing", + "nym-statistics-common", + "nym-task", + "nym-topology", + "nym-validator-client", "parking_lot", "rand 0.8.5", "reqwest 0.12.22", @@ -7076,18 +6647,6 @@ dependencies = [ "time", ] -[[package]] -name = "nym-serde-helpers" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "base64 0.22.1", - "bs58", - "hex", - "serde", - "time", -] - [[package]] name = "nym-service-provider-requests-common" version = "0.1.0" @@ -7104,10 +6663,10 @@ dependencies = [ "anyhow", "async-trait", "log", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-sdk", "nym-socks5-requests", - "nym-sphinx-anonymous-replies 0.1.0", + "nym-sphinx-anonymous-replies", "serde", "serde_json", "thiserror 2.0.12", @@ -7116,26 +6675,26 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.61" +version = "1.1.62" dependencies = [ "bs58", "clap", "log", - "nym-bin-common 0.6.0", + "nym-bin-common", "nym-client-core", - "nym-config 0.1.0", + "nym-config", "nym-credential-storage", "nym-credentials", - "nym-crypto 0.4.0", + "nym-crypto", "nym-gateway-requests", "nym-id", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-ordered-buffer", - "nym-pemstore 0.3.0", + "nym-pemstore", "nym-socks5-client-core", - "nym-sphinx 0.1.0", - "nym-topology 0.1.0", - "nym-validator-client 0.1.0", + "nym-sphinx", + "nym-topology", + "nym-validator-client", "rand 0.8.5", "serde", "serde_json", @@ -7157,17 +6716,17 @@ dependencies = [ "log", "nym-bandwidth-controller", "nym-client-core", - "nym-config 0.1.0", - "nym-contracts-common 0.5.0", + "nym-config", + "nym-contracts-common", "nym-credential-storage", - "nym-mixnet-contract-common 0.6.0", - "nym-network-defaults 0.1.0", + "nym-mixnet-contract-common", + "nym-network-defaults", "nym-service-providers-common", "nym-socks5-proxy-helpers", "nym-socks5-requests", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", - "nym-validator-client 0.1.0", + "nym-sphinx", + "nym-task", + "nym-validator-client", "pin-project", "rand 0.8.5", "reqwest 0.12.22", @@ -7188,7 +6747,7 @@ dependencies = [ "log", "nym-ordered-buffer", "nym-socks5-requests", - "nym-task 0.1.0", + "nym-task", "tokio", "tokio-test", "tokio-util", @@ -7200,9 +6759,9 @@ version = "0.1.0" dependencies = [ "bincode", "log", - "nym-exit-policy 0.1.0", + "nym-exit-policy", "nym-service-providers-common", - "nym-sphinx-addressing 0.1.0", + "nym-sphinx-addressing", "serde", "serde_json", "tap", @@ -7213,46 +6772,20 @@ dependencies = [ name = "nym-sphinx" version = "0.1.0" dependencies = [ - "nym-crypto 0.4.0", - "nym-metrics 0.1.0", - "nym-mixnet-contract-common 0.6.0", - "nym-sphinx-acknowledgements 0.1.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-anonymous-replies 0.1.0", - "nym-sphinx-chunking 0.1.0", - "nym-sphinx-cover 0.1.0", - "nym-sphinx-forwarding 0.1.0", - "nym-sphinx-framing 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-routing 0.1.0", - "nym-sphinx-types 0.2.0", - "nym-topology 0.1.0", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_distr", - "thiserror 2.0.12", - "tokio", - "tracing", -] - -[[package]] -name = "nym-sphinx" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-metrics 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-acknowledgements 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-anonymous-replies 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-chunking 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-cover 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-forwarding 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-framing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-routing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-topology 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-crypto", + "nym-metrics", + "nym-mixnet-contract-common", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-anonymous-replies", + "nym-sphinx-chunking", + "nym-sphinx-cover", + "nym-sphinx-forwarding", + "nym-sphinx-framing", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", "rand 0.8.5", "rand_chacha 0.3.1", "rand_distr", @@ -7266,69 +6799,41 @@ name = "nym-sphinx-acknowledgements" version = "0.1.0" dependencies = [ "generic-array 0.14.7", - "nym-crypto 0.4.0", - "nym-pemstore 0.3.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-routing 0.1.0", - "nym-sphinx-types 0.2.0", - "nym-topology 0.1.0", + "nym-crypto", + "nym-pemstore", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", "rand 0.8.5", "serde", "thiserror 2.0.12", "zeroize", ] -[[package]] -name = "nym-sphinx-acknowledgements" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-pemstore 0.3.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-routing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-topology 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "rand 0.8.5", - "thiserror 2.0.12", - "zeroize", -] - [[package]] name = "nym-sphinx-addressing" version = "0.1.0" dependencies = [ - "nym-crypto 0.4.0", - "nym-sphinx-types 0.2.0", + "nym-crypto", + "nym-sphinx-types", "rand 0.8.5", "serde", "thiserror 2.0.12", ] -[[package]] -name = "nym-sphinx-addressing" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "serde", - "thiserror 2.0.12", -] - [[package]] name = "nym-sphinx-anonymous-replies" version = "0.1.0" dependencies = [ "bs58", - "nym-crypto 0.4.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-routing 0.1.0", - "nym-sphinx-types 0.2.0", - "nym-topology 0.1.0", + "nym-crypto", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", "rand 0.8.5", "rand_chacha 0.3.1", "thiserror 2.0.12", @@ -7336,54 +6841,17 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "nym-sphinx-anonymous-replies" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bs58", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-routing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-topology 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "rand 0.8.5", - "serde", - "thiserror 2.0.12", - "tracing", - "wasm-bindgen", -] - [[package]] name = "nym-sphinx-chunking" version = "0.1.0" dependencies = [ "dashmap", "log", - "nym-crypto 0.4.0", - "nym-metrics 0.1.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-types 0.2.0", - "rand 0.8.5", - "serde", - "thiserror 2.0.12", - "utoipa", -] - -[[package]] -name = "nym-sphinx-chunking" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "dashmap", - "log", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-metrics 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-crypto", + "nym-metrics", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", "rand 0.8.5", "serde", "thiserror 2.0.12", @@ -7394,33 +6862,15 @@ dependencies = [ name = "nym-sphinx-cover" version = "0.1.0" dependencies = [ - "nym-crypto 0.4.0", - "nym-sphinx-acknowledgements 0.1.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-chunking 0.1.0", - "nym-sphinx-forwarding 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-routing 0.1.0", - "nym-sphinx-types 0.2.0", - "nym-topology 0.1.0", - "rand 0.8.5", - "thiserror 2.0.12", -] - -[[package]] -name = "nym-sphinx-cover" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-acknowledgements 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-chunking 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-forwarding 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-routing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-topology 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-crypto", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-chunking", + "nym-sphinx-forwarding", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", "rand 0.8.5", "thiserror 2.0.12", ] @@ -7429,22 +6879,10 @@ dependencies = [ name = "nym-sphinx-forwarding" version = "0.1.0" dependencies = [ - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-anonymous-replies 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-types 0.2.0", - "thiserror 2.0.12", -] - -[[package]] -name = "nym-sphinx-forwarding" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-sphinx-addressing", + "nym-sphinx-anonymous-replies", + "nym-sphinx-params", + "nym-sphinx-types", "thiserror 2.0.12", ] @@ -7453,50 +6891,23 @@ name = "nym-sphinx-framing" version = "0.1.0" dependencies = [ "bytes", - "nym-sphinx-acknowledgements 0.1.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-forwarding 0.1.0", - "nym-sphinx-params 0.1.0", - "nym-sphinx-types 0.2.0", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-forwarding", + "nym-sphinx-params", + "nym-sphinx-types", "thiserror 2.0.12", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "nym-sphinx-framing" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "bytes", - "nym-sphinx-acknowledgements 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-forwarding 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "thiserror 2.0.12", - "tokio-util", - "tracing", -] - [[package]] name = "nym-sphinx-params" version = "0.1.0" dependencies = [ - "nym-crypto 0.4.0", - "nym-sphinx-types 0.2.0", - "serde", - "thiserror 2.0.12", -] - -[[package]] -name = "nym-sphinx-params" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-crypto", + "nym-sphinx-types", "serde", "thiserror 2.0.12", ] @@ -7505,18 +6916,8 @@ dependencies = [ name = "nym-sphinx-routing" version = "0.1.0" dependencies = [ - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-types 0.2.0", - "thiserror 2.0.12", -] - -[[package]] -name = "nym-sphinx-routing" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-sphinx-addressing", + "nym-sphinx-types", "thiserror 2.0.12", ] @@ -7524,24 +6925,14 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox 0.1.0", - "sphinx-packet", - "thiserror 2.0.12", -] - -[[package]] -name = "nym-sphinx-types" -version = "0.2.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-outfox", "sphinx-packet", "thiserror 2.0.12", ] [[package]] name = "nym-statistics-api" -version = "0.1.4" +version = "0.1.5" dependencies = [ "anyhow", "axum 0.7.9", @@ -7549,12 +6940,12 @@ dependencies = [ "axum-extra", "celes", "clap", - "nym-bin-common 0.6.0", - "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-http-api-common 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-validator-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-bin-common", + "nym-http-api-client", + "nym-http-api-common", + "nym-statistics-common", + "nym-task", + "nym-validator-client", "serde", "serde_json", "sqlx", @@ -7576,16 +6967,17 @@ version = "0.1.0" dependencies = [ "futures", "log", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-metrics 0.1.0", - "nym-sphinx 0.1.0", - "nym-task 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-metrics", + "nym-sphinx", + "nym-task", "serde", "serde_json", "sha2 0.10.9", "si-scale", - "strum 0.26.3", + "strum", + "strum_macros", "sysinfo", "thiserror 2.0.12", "time", @@ -7594,30 +6986,6 @@ dependencies = [ "wasmtimer", ] -[[package]] -name = "nym-statistics-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "futures", - "log", - "nym-credentials-interface 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-metrics 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-task 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "serde", - "serde_json", - "sha2 0.10.9", - "si-scale", - "strum 0.26.3", - "sysinfo", - "thiserror 2.0.12", - "time", - "tokio", - "wasmtimer", -] - [[package]] name = "nym-store-cipher" version = "0.1.0" @@ -7650,28 +7018,21 @@ dependencies = [ ] [[package]] -name = "nym-task" +name = "nym-test-utils" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ - "cfg-if", + "anyhow", "futures", - "log", - "thiserror 2.0.12", + "rand_chacha 0.3.1", "tokio", - "tokio-util", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasmtimer", ] [[package]] name = "nym-ticketbooks-merkle" version = "0.1.0" dependencies = [ - "nym-credentials-interface 0.1.0", - "nym-serde-helpers 0.1.0", + "nym-credentials-interface", + "nym-serde-helpers", "rand 0.8.5", "rand_chacha 0.3.1", "rs_merkle", @@ -7683,31 +7044,16 @@ dependencies = [ "utoipa", ] -[[package]] -name = "nym-ticketbooks-merkle" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "nym-credentials-interface 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "rs_merkle", - "schemars 0.8.22", - "serde", - "sha2 0.10.9", - "time", - "utoipa", -] - [[package]] name = "nym-topology" version = "0.1.0" dependencies = [ "async-trait", - "nym-api-requests 0.1.0", - "nym-crypto 0.4.0", - "nym-mixnet-contract-common 0.6.0", - "nym-sphinx-addressing 0.1.0", - "nym-sphinx-types 0.2.0", + "nym-api-requests", + "nym-crypto", + "nym-mixnet-contract-common", + "nym-sphinx-addressing", + "nym-sphinx-types", "rand 0.8.5", "reqwest 0.12.22", "serde", @@ -7720,33 +7066,13 @@ dependencies = [ "wasm-utils", ] -[[package]] -name = "nym-topology" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "async-trait", - "nym-api-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-config 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-mixnet-contract-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-routing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "rand 0.8.5", - "reqwest 0.12.22", - "serde", - "thiserror 2.0.12", - "tracing", -] - [[package]] name = "nym-tun" version = "0.1.0" dependencies = [ "etherparse", "log", - "nym-wireguard-types 0.1.0", + "nym-wireguard-types", "thiserror 2.0.12", "tokio", "tokio-tun", @@ -7763,17 +7089,18 @@ dependencies = [ "hmac", "itertools 0.14.0", "log", - "nym-config 0.1.0", - "nym-crypto 0.4.0", - "nym-mixnet-contract-common 0.6.0", - "nym-validator-client 0.1.0", - "nym-vesting-contract-common 0.7.0", + "nym-config", + "nym-crypto", + "nym-mixnet-contract-common", + "nym-validator-client", + "nym-vesting-contract-common", "reqwest 0.12.22", "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", - "strum 0.26.3", + "strum", + "strum_macros", "tempfile", "thiserror 2.0.12", "ts-rs", @@ -7802,20 +7129,20 @@ dependencies = [ "flate2", "futures", "itertools 0.14.0", - "nym-api-requests 0.1.0", - "nym-coconut-dkg-common 0.1.0", - "nym-compact-ecash 0.1.0", - "nym-config 0.1.0", - "nym-contracts-common 0.5.0", - "nym-ecash-contract-common 0.1.0", - "nym-group-contract-common 0.1.0", - "nym-http-api-client 0.1.0", - "nym-mixnet-contract-common 0.6.0", - "nym-multisig-contract-common 0.1.0", - "nym-network-defaults 0.1.0", + "nym-api-requests", + "nym-coconut-dkg-common", + "nym-compact-ecash", + "nym-config", + "nym-contracts-common", + "nym-ecash-contract-common", + "nym-group-contract-common", + "nym-http-api-client", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-network-defaults", "nym-performance-contract-common", - "nym-serde-helpers 0.1.0", - "nym-vesting-contract-common 0.7.0", + "nym-serde-helpers", + "nym-vesting-contract-common", "prost", "reqwest 0.12.22", "serde", @@ -7832,55 +7159,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-validator-client" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "async-trait", - "base64 0.22.1", - "bip32", - "bip39", - "colored", - "cosmrs", - "cosmwasm-std", - "cw-controllers", - "cw-utils", - "cw2", - "cw3", - "cw4", - "eyre", - "flate2", - "futures", - "itertools 0.14.0", - "nym-api-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-coconut-dkg-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-compact-ecash 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-config 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-ecash-contract-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-group-contract-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-mixnet-contract-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-multisig-contract-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-vesting-contract-common 0.7.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "prost", - "reqwest 0.12.22", - "serde", - "serde_json", - "sha2 0.10.9", - "tendermint-rpc", - "thiserror 2.0.12", - "time", - "tokio", - "tracing", - "url", - "wasmtimer", - "zeroize", -] - [[package]] name = "nym-validator-rewarder" version = "0.3.0" @@ -7892,21 +7170,21 @@ dependencies = [ "futures", "humantime", "humantime-serde", - "nym-bin-common 0.6.0", - "nym-coconut-dkg-common 0.1.0", - "nym-compact-ecash 0.1.0", - "nym-config 0.1.0", - "nym-contracts-common 0.5.0", + "nym-bin-common", + "nym-coconut-dkg-common", + "nym-compact-ecash", + "nym-config", + "nym-contracts-common", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-ecash-time 0.1.0", - "nym-network-defaults 0.1.0", - "nym-pemstore 0.3.0", - "nym-serde-helpers 0.1.0", - "nym-task 0.1.0", - "nym-ticketbooks-merkle 0.1.0", - "nym-validator-client 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-time", + "nym-network-defaults", + "nym-pemstore", + "nym-serde-helpers", + "nym-task", + "nym-ticketbooks-merkle", + "nym-validator-client", "nyxd-scraper", "rand 0.8.5", "rand_chacha 0.3.1", @@ -7930,9 +7208,9 @@ dependencies = [ "bytes", "futures", "humantime", - "nym-crypto 0.4.0", - "nym-task 0.1.0", - "nym-validator-client 0.1.0", + "nym-crypto", + "nym-task", + "nym-validator-client", "rand 0.8.5", "thiserror 2.0.12", "time", @@ -7949,26 +7227,13 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw2", - "nym-contracts-common 0.5.0", - "nym-mixnet-contract-common 0.6.0", + "nym-contracts-common", + "nym-mixnet-contract-common", "serde", "thiserror 2.0.12", "ts-rs", ] -[[package]] -name = "nym-vesting-contract-common" -version = "0.7.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-mixnet-contract-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "serde", - "thiserror 2.0.12", -] - [[package]] name = "nym-vpn-api-lib-wasm" version = "0.1.0" @@ -7976,13 +7241,13 @@ dependencies = [ "bs58", "getrandom 0.2.16", "js-sys", - "nym-bin-common 0.6.0", - "nym-compact-ecash 0.1.0", + "nym-bin-common", + "nym-compact-ecash", "nym-credential-proxy-requests", "nym-credentials", - "nym-credentials-interface 0.1.0", - "nym-crypto 0.4.0", - "nym-ecash-time 0.1.0", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-time", "serde", "serde-wasm-bindgen 0.6.5", "serde_json", @@ -8001,15 +7266,16 @@ dependencies = [ "cosmrs", "cosmwasm-std", "hex-literal", - "nym-config 0.1.0", - "nym-mixnet-contract-common 0.6.0", - "nym-network-defaults 0.1.0", + "nym-config", + "nym-mixnet-contract-common", + "nym-network-defaults", "nym-types", - "nym-validator-client 0.1.0", - "nym-vesting-contract-common 0.7.0", + "nym-validator-client", + "nym-vesting-contract-common", "serde", "serde_json", - "strum 0.23.0", + "strum", + "strum_macros", "ts-rs", ] @@ -8029,12 +7295,14 @@ dependencies = [ "log", "nym-authenticator-requests", "nym-credential-verification", - "nym-crypto 0.4.0", + "nym-credentials-interface", + "nym-crypto", + "nym-gateway-requests", "nym-gateway-storage", - "nym-network-defaults 0.1.0", + "nym-network-defaults", "nym-node-metrics", - "nym-task 0.1.0", - "nym-wireguard-types 0.1.0", + "nym-task", + "nym-wireguard-types", "thiserror 2.0.12", "time", "tokio", @@ -8043,38 +7311,86 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "nym-wireguard-private-metadata-client" +version = "1.0.0" +dependencies = [ + "async-trait", + "nym-http-api-client", + "nym-wireguard-private-metadata-shared", + "tracing", +] + +[[package]] +name = "nym-wireguard-private-metadata-server" +version = "1.0.0" +dependencies = [ + "anyhow", + "async-trait", + "axum 0.7.9", + "futures", + "nym-credential-verification", + "nym-credentials-interface", + "nym-http-api-common", + "nym-wireguard", + "nym-wireguard-private-metadata-shared", + "tokio", + "tokio-util", + "tower-http 0.5.2", + "utoipa", + "utoipa-swagger-ui", +] + +[[package]] +name = "nym-wireguard-private-metadata-shared" +version = "1.0.0" +dependencies = [ + "axum 0.7.9", + "bincode", + "nym-credentials-interface", + "schemars 0.8.22", + "serde", + "thiserror 2.0.12", + "utoipa", +] + +[[package]] +name = "nym-wireguard-private-metadata-tests" +version = "1.0.0" +dependencies = [ + "async-trait", + "axum 0.7.9", + "nym-credential-verification", + "nym-credentials-interface", + "nym-http-api-client", + "nym-http-api-common", + "nym-wireguard", + "nym-wireguard-private-metadata-client", + "nym-wireguard-private-metadata-server", + "nym-wireguard-private-metadata-shared", + "tokio", + "tower-http 0.5.2", + "utoipa", +] + [[package]] name = "nym-wireguard-types" version = "0.1.0" dependencies = [ "base64 0.22.1", "log", - "nym-config 0.1.0", - "nym-crypto 0.4.0", - "nym-network-defaults 0.1.0", + "nym-config", + "nym-crypto", + "nym-network-defaults", "rand 0.8.5", "serde", "thiserror 2.0.12", "x25519-dalek", ] -[[package]] -name = "nym-wireguard-types" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" -dependencies = [ - "base64 0.22.1", - "log", - "nym-config 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "serde", - "thiserror 2.0.12", - "x25519-dalek", -] - [[package]] name = "nymvisor" -version = "0.1.26" +version = "0.1.27" dependencies = [ "anyhow", "bytes", @@ -8087,9 +7403,9 @@ dependencies = [ "humantime-serde", "nix 0.27.1", "nym-async-file-watcher", - "nym-bin-common 0.6.0", - "nym-config 0.1.0", - "nym-task 0.1.0", + "nym-bin-common", + "nym-config", + "nym-task", "reqwest 0.12.22", "serde", "serde_json", @@ -8111,11 +7427,11 @@ dependencies = [ "axum 0.7.9", "chrono", "clap", - "nym-bin-common 0.6.0", - "nym-config 0.1.0", - "nym-network-defaults 0.1.0", - "nym-task 0.1.0", - "nym-validator-client 0.1.0", + "nym-bin-common", + "nym-config", + "nym-network-defaults", + "nym-task", + "nym-validator-client", "nyxd-scraper", "reqwest 0.12.22", "schemars 0.8.22", @@ -8157,6 +7473,25 @@ dependencies = [ "url", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "object" version = "0.36.7" @@ -8780,28 +8115,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "proc-macro2" version = "1.0.95" @@ -10442,7 +9755,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "windows 0.61.3", + "windows", ] [[package]] @@ -10576,45 +9889,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.23.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.23.1", -] - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", + "strum_macros", ] [[package]] name = "strum_macros" -version = "0.23.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "rustversion", "syn 2.0.104", ] @@ -10695,16 +9985,16 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.33.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "rayon", - "windows 0.57.0", + "objc2-core-foundation", + "objc2-io-kit", + "windows", ] [[package]] @@ -10890,20 +10180,20 @@ dependencies = [ "dkg-bypass-contract", "humantime", "indicatif 0.17.11", - "nym-bin-common 0.6.0", - "nym-coconut-dkg-common 0.1.0", - "nym-compact-ecash 0.1.0", - "nym-config 0.1.0", - "nym-contracts-common 0.5.0", - "nym-crypto 0.4.0", - "nym-ecash-contract-common 0.1.0", - "nym-group-contract-common 0.1.0", - "nym-mixnet-contract-common 0.6.0", - "nym-multisig-contract-common 0.1.0", - "nym-pemstore 0.3.0", + "nym-bin-common", + "nym-coconut-dkg-common", + "nym-compact-ecash", + "nym-config", + "nym-contracts-common", + "nym-crypto", + "nym-ecash-contract-common", + "nym-group-contract-common", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-pemstore", "nym-performance-contract-common", - "nym-validator-client 0.1.0", - "nym-vesting-contract-common 0.7.0", + "nym-validator-client", + "nym-vesting-contract-common", "rand 0.8.5", "serde", "serde_json", @@ -11569,11 +10859,11 @@ name = "ts-rs-cli" version = "0.1.0" dependencies = [ "anyhow", - "nym-api-requests 0.1.0", - "nym-mixnet-contract-common 0.6.0", + "nym-api-requests", + "nym-mixnet-contract-common", "nym-types", - "nym-validator-client 0.1.0", - "nym-vesting-contract-common 0.7.0", + "nym-validator-client", + "nym-vesting-contract-common", "nym-wallet-types", "ts-rs", "walkdir", @@ -12019,12 +11309,12 @@ dependencies = [ "anyhow", "clap", "comfy-table", - "nym-bin-common 0.6.0", - "nym-network-defaults 0.1.0", - "nym-validator-client 0.1.0", + "nym-bin-common", + "nym-network-defaults", + "nym-validator-client", "serde", "serde_json", - "strum 0.26.3", + "strum", "time", "tokio", "tracing", @@ -12246,16 +11536,16 @@ dependencies = [ "js-sys", "nym-bandwidth-controller", "nym-client-core", - "nym-config 0.1.0", + "nym-config", "nym-credential-storage", - "nym-crypto 0.4.0", + "nym-crypto", "nym-gateway-client", - "nym-sphinx 0.1.0", - "nym-sphinx-acknowledgements 0.1.0", - "nym-statistics-common 0.1.0", - "nym-task 0.1.0", - "nym-topology 0.1.0", - "nym-validator-client 0.1.0", + "nym-sphinx", + "nym-sphinx-acknowledgements", + "nym-statistics-common", + "nym-task", + "nym-topology", + "nym-validator-client", "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", @@ -12451,16 +11741,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -12468,7 +11748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", "windows-link", "windows-numerics", @@ -12480,19 +11760,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -12501,10 +11769,10 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.3.4", + "windows-result", "windows-strings", ] @@ -12514,22 +11782,11 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link", "windows-threading", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "windows-implement" version = "0.60.0" @@ -12541,17 +11798,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -12575,19 +11821,10 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -13117,11 +12354,11 @@ dependencies = [ "bs58", "getrandom 0.2.16", "js-sys", - "nym-bin-common 0.6.0", - "nym-compact-ecash 0.1.0", + "nym-bin-common", + "nym-compact-ecash", "nym-credentials", - "nym-crypto 0.4.0", - "nym-http-api-client 0.1.0", + "nym-crypto", + "nym-http-api-client", "rand 0.8.5", "reqwest 0.12.22", "serde", @@ -13180,8 +12417,8 @@ name = "zulip-client" version = "0.1.0" dependencies = [ "itertools 0.14.0", - "nym-bin-common 0.6.0", - "nym-http-api-client 0.1.0", + "nym-bin-common", + "nym-http-api-client", "reqwest 0.12.22", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 1bee069b76..95b45ef28b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ members = [ "common/socks5/requests", "common/statistics", "common/store-cipher", - "common/task", + "common/task", "common/test-utils", "common/ticketbooks-merkle", "common/topology", "common/tun", @@ -102,7 +102,12 @@ members = [ "common/wasm/storage", "common/wasm/utils", "common/wireguard", - "common/wireguard-types", "common/zulip-client", + "common/wireguard-private-metadata/client", + "common/wireguard-private-metadata/server", + "common/wireguard-private-metadata/shared", + "common/wireguard-private-metadata/tests", + "common/wireguard-types", + "common/zulip-client", "documentation/autodoc", "gateway", "nym-api", @@ -126,7 +131,6 @@ members = [ "sdk/ffi/go", "sdk/ffi/shared", "sdk/rust/nym-sdk", - "service-providers/authenticator", "service-providers/common", "service-providers/ip-packet-router", "service-providers/network-requester", @@ -164,7 +168,6 @@ default-members = [ "nym-statistics-api", "nym-validator-rewarder", "nyx-chain-watcher", - "service-providers/authenticator", "service-providers/ip-packet-router", "service-providers/network-requester", "tools/nymvisor", @@ -320,11 +323,11 @@ si-scale = "0.2.3" snow = "0.9.6" sphinx-packet = "=0.6.0" sqlx = "0.8.6" -strum = "0.26" -strum_macros = "0.26" +strum = "0.27.2" +strum_macros = "0.27.2" subtle-encoding = "0.5" syn = "1" -sysinfo = "0.33.0" +sysinfo = "0.37.0" tap = "1.0.1" tar = "0.4.44" tempfile = "3.20" @@ -450,4 +453,3 @@ exit = "deny" panic = "deny" unimplemented = "deny" unreachable = "deny" - diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index a27a046e0b..3736fc047c 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.61" +version = "1.1.62" 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 8f69f57566..a4b1af15be 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.61" +version = "1.1.62" 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/common/bandwidth-controller/src/utils.rs b/common/bandwidth-controller/src/utils.rs index 2016849eb0..4502238c91 100644 --- a/common/bandwidth-controller/src/utils.rs +++ b/common/bandwidth-controller/src/utils.rs @@ -207,7 +207,7 @@ where ::StorageError: Send + Sync + 'static, { if let Some(stored) = storage - .get_expiration_date_signatures(expiration_date) + .get_expiration_date_signatures(expiration_date, epoch_id) .await .map_err(BandwidthControllerError::credential_storage_error)? { @@ -220,7 +220,7 @@ where ecash_apis, |api| async move { api.api_client - .global_expiration_date_signatures(Some(expiration_date)) + .global_expiration_date_signatures(Some(expiration_date), Some(epoch_id)) .await }, format!("aggregated coin index signatures for date {expiration_date}"), diff --git a/common/client-core/src/client/mix_traffic/mod.rs b/common/client-core/src/client/mix_traffic/mod.rs index be7a517f57..9839d74473 100644 --- a/common/client-core/src/client/mix_traffic/mod.rs +++ b/common/client-core/src/client/mix_traffic/mod.rs @@ -96,35 +96,59 @@ impl MixTrafficController { mut mix_packets: Vec, ) -> Result<(), ErasedGatewayError> { debug_assert!(!mix_packets.is_empty()); - - let result = if mix_packets.len() == 1 { + let send_future = if mix_packets.len() == 1 { // SAFETY: we just checked we have one packet #[allow(clippy::unwrap_used)] let mix_packet = mix_packets.pop().unwrap(); - self.gateway_transceiver.send_mix_packet(mix_packet).await + self.gateway_transceiver.send_mix_packet(mix_packet) } else { - self.gateway_transceiver - .batch_send_mix_packets(mix_packets) - .await + self.gateway_transceiver.batch_send_mix_packets(mix_packets) }; - if result.is_err() { - self.consecutive_gateway_failure_count += 1; - } else { - trace!("We *might* have managed to forward sphinx packet(s) to the gateway!"); - self.consecutive_gateway_failure_count = 0; - } + tokio::select! { + biased; + _ = self.task_client.recv() => { + trace!("received shutdown while handling messages"); + Ok(()) + } + result = send_future => { + if result.is_err() { + self.consecutive_gateway_failure_count += 1; + } else { + trace!("We *might* have managed to forward sphinx packet(s) to the gateway!"); + self.consecutive_gateway_failure_count = 0; + } - result + result + } + } + } + + async fn on_client_request(&mut self, client_request: ClientRequest) { + tokio::select! { + biased; + _ = self.task_client.recv() => { + trace!("received shutdown while handling client request"); + } + result = self.gateway_transceiver.send_client_request(client_request) => { + if let Err(err) = result { + error!("Failed to send client request: {err}") + } + } + } } pub fn start(mut self) { spawn_future!( async move { debug!("Started MixTrafficController with graceful shutdown support"); - while !self.task_client.is_shutdown() { tokio::select! { + biased; + _ = self.task_client.recv() => { + tracing::trace!("MixTrafficController: Received shutdown"); + break; + } mix_packets = self.mix_rx.recv() => match mix_packets { Some(mix_packets) => { if let Err(err) = self.on_messages(mix_packets).await { @@ -147,23 +171,15 @@ impl MixTrafficController { }, client_request = self.client_rx.recv() => match client_request { Some(client_request) => { - match self.gateway_transceiver.send_client_request(client_request).await { - Ok(_) => (), - Err(e) => error!("Failed to send client request: {e}"), - }; + self.on_client_request(client_request).await; }, None => { tracing::trace!("MixTrafficController, client request channel closed"); } }, - _ = self.task_client.recv() => { - tracing::trace!("MixTrafficController: Received shutdown"); - break; - } } } self.task_client.recv_timeout().await; - tracing::debug!("MixTrafficController: Exiting"); }, "MixTrafficController" diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index 8185eb5a0a..f47f050b3b 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -226,6 +226,11 @@ where while !self.task_client.is_shutdown() { tokio::select! { + biased; + _ = self.task_client.recv() => { + tracing::trace!("InputMessageListener: Received shutdown"); + break; + } input_msg = self.input_receiver.recv() => match input_msg { Some(input_msg) => { self.on_input_message(input_msg).await; @@ -235,9 +240,7 @@ where break; } }, - _ = self.task_client.recv() => { - tracing::trace!("InputMessageListener: Received shutdown"); - } + } } self.task_client.recv_timeout().await; diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index aab826de7a..0b066d0cc0 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -179,6 +179,11 @@ where while !self.task_client.is_shutdown() { tokio::select! { + biased; + _ = self.task_client.recv() => { + tracing::trace!("RetransmissionRequestListener: Received shutdown"); + break; + } timed_out_ack = self.request_receiver.next() => match timed_out_ack { Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await, None => { @@ -186,9 +191,7 @@ where break; } }, - _ = self.task_client.recv() => { - tracing::trace!("RetransmissionRequestListener: Received shutdown"); - } + } } self.task_client.recv_timeout().await; diff --git a/common/client-core/src/client/real_messages_control/message_handler.rs b/common/client-core/src/client/real_messages_control/message_handler.rs index 942d88c77a..1dca40cf7e 100644 --- a/common/client-core/src/client/real_messages_control/message_handler.rs +++ b/common/client-core/src/client/real_messages_control/message_handler.rs @@ -548,6 +548,7 @@ where pending_acks.push(pending_ack); } + drop(topology_permit); self.insert_pending_acks(pending_acks); self.forward_messages(real_messages, lane).await; @@ -730,17 +731,21 @@ where // tells real message sender (with the poisson timer) to send this to the mix network pub(crate) async fn forward_messages( - &self, + &mut self, messages: Vec, transmission_lane: TransmissionLane, ) { - if let Err(err) = self - .real_message_sender - .send((messages, transmission_lane)) - .await - { - if !self.task_client.is_shutdown_poll() { - error!("Failed to forward messages to the real message sender: {err}"); + tokio::select! { + biased; + _ = self.task_client.recv() => { + trace!("received shutdown while attempting to forward mixnet messages"); + } + sending_res = self.real_message_sender.send((messages, transmission_lane)) => { + if sending_res.is_err() { + error!( + "failed to forward mixnet messages due to closed channel (outside of shutdown!)" + ); + } } } } diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index d24947c9d2..7cbf3b6ad6 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -280,17 +280,33 @@ where } }; - if let Err(err) = self.mix_tx.send(vec![next_message]).await { - if !self.task_client.is_shutdown_poll() { - tracing::error!("Failed to send: {err}"); + let sending_res = tokio::select! { + biased; + _ = self.task_client.recv() => { + trace!("received shutdown signal while attempting to send mix message"); + return + } + sending_res = self.mix_tx.send(vec![next_message]) => { + sending_res + } + }; + + match sending_res { + Err(_) => { + if !self.task_client.is_shutdown_poll() { + tracing::error!( + "failed to send mixnet packet due to closed channel (outside of shutdown!)" + ); + } + } + Ok(_) => { + let event = if fragment_id.is_some() { + PacketStatisticsEvent::RealPacketSent(packet_size) + } else { + PacketStatisticsEvent::CoverPacketSent(packet_size) + }; + self.stats_tx.report(event.into()); } - } else { - let event = if fragment_id.is_some() { - PacketStatisticsEvent::RealPacketSent(packet_size) - } else { - PacketStatisticsEvent::CoverPacketSent(packet_size) - }; - self.stats_tx.report(event.into()); } // notify ack controller about sending our message only after we actually managed to push it diff --git a/common/client-core/src/client/topology_control/accessor.rs b/common/client-core/src/client/topology_control/accessor.rs index 5c08a6a9ae..9841127b52 100644 --- a/common/client-core/src/client/topology_control/accessor.rs +++ b/common/client-core/src/client/topology_control/accessor.rs @@ -126,7 +126,7 @@ impl TopologyAccessor { .map(|p| p.topology.clone()) } - pub async fn current_route_provider(&self) -> Option> { + pub async fn current_route_provider(&self) -> Option> { let provider = self.inner.topology.read().await; if provider.topology.is_empty() { None diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 97c3be13b7..1ca4b68191 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -148,7 +148,7 @@ async fn connect(endpoint: &str) -> Result { JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure) } -async fn measure_latency(gateway: &G) -> Result, ClientCoreError> +async fn measure_latency(gateway: &G) -> Result, ClientCoreError> where G: ConnectableGateway, { diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 612349315f..de90b3ad35 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -201,7 +201,7 @@ impl GatewayClient { #[cfg(not(target_arch = "wasm32"))] pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> { debug!( - "Attemting to establish connection to gateway at: {}", + "Attempting to establish connection to gateway at: {}", self.gateway_address ); let (ws_stream, _) = connect_async( diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index b2a1e6ebb0..5489ec3628 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -337,7 +337,7 @@ impl PartiallyDelegatedHandle { // check if the split stream didn't error out let receive_res = stream_receiver .try_recv() - .expect("stream sender was somehow dropped without sending anything!"); + .map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?; if let Some(res) = receive_res { let _res = res?; diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 16d1013c8b..2e6d19d9ea 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -719,10 +719,11 @@ impl NymApiClient { pub async fn partial_expiration_date_signatures( &self, expiration_date: Option, + epoch_id: Option, ) -> Result { Ok(self .nym_api - .partial_expiration_date_signatures(expiration_date) + .partial_expiration_date_signatures(expiration_date, epoch_id) .await?) } @@ -739,10 +740,11 @@ impl NymApiClient { pub async fn global_expiration_date_signatures( &self, expiration_date: Option, + epoch_id: Option, ) -> Result { Ok(self .nym_api - .global_expiration_date_signatures(expiration_date) + .global_expiration_date_signatures(expiration_date, epoch_id) .await?) } diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 31b7014f64..aa13634503 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -1103,8 +1103,9 @@ pub trait NymApiClientExt: ApiClient { async fn partial_expiration_date_signatures( &self, expiration_date: Option, + epoch_id: Option, ) -> Result { - let params = match expiration_date { + let mut params = match expiration_date { None => Vec::new(), Some(exp) => vec![( ecash::EXPIRATION_DATE_PARAM, @@ -1112,6 +1113,10 @@ pub trait NymApiClientExt: ApiClient { )], }; + if let Some(epoch_id) = epoch_id { + params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string())); + } + self.get_json( &[ routes::V1_API_VERSION, @@ -1148,8 +1153,9 @@ pub trait NymApiClientExt: ApiClient { async fn global_expiration_date_signatures( &self, expiration_date: Option, + epoch_id: Option, ) -> Result { - let params = match expiration_date { + let mut params = match expiration_date { None => Vec::new(), Some(exp) => vec![( ecash::EXPIRATION_DATE_PARAM, @@ -1157,6 +1163,10 @@ pub trait NymApiClientExt: ApiClient { )], }; + if let Some(epoch_id) = epoch_id { + params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string())); + } + self.get_json( &[ routes::V1_API_VERSION, diff --git a/common/commands/src/ecash/generate_ticket.rs b/common/commands/src/ecash/generate_ticket.rs index 0da2a878c4..4752c9211c 100644 --- a/common/commands/src/ecash/generate_ticket.rs +++ b/common/commands/src/ecash/generate_ticket.rs @@ -86,7 +86,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> { anyhow!("ticketbook got incorrectly imported - the master verification key is missing") })?; let expiration_signatures = persistent_storage - .get_expiration_date_signatures(expiration_date) + .get_expiration_date_signatures(expiration_date, epoch_id) .await? .ok_or_else(|| { anyhow!( diff --git a/common/commands/src/ecash/issue_ticket_book.rs b/common/commands/src/ecash/issue_ticket_book.rs index e57cb579dc..fae59ad759 100644 --- a/common/commands/src/ecash/issue_ticket_book.rs +++ b/common/commands/src/ecash/issue_ticket_book.rs @@ -120,7 +120,7 @@ async fn issue_to_file(args: Args, client: SigningClient) -> anyhow::Result<()> if args.include_expiration_date_signatures { let signatures = credentials_store - .get_expiration_date_signatures(expiration_date) + .get_expiration_date_signatures(expiration_date, epoch_id) .await? .ok_or(anyhow!("missing expiration date signatures!"))?; diff --git a/common/commands/src/validator/mixnet/delegators/delegate_to_multiple_mixnodes.rs b/common/commands/src/validator/mixnet/delegators/delegate_to_multiple_mixnodes.rs index 70eb85e7cd..79dc4347a0 100644 --- a/common/commands/src/validator/mixnet/delegators/delegate_to_multiple_mixnodes.rs +++ b/common/commands/src/validator/mixnet/delegators/delegate_to_multiple_mixnodes.rs @@ -241,7 +241,7 @@ pub async fn delegate_to_multiple_mixnodes(args: Args, client: SigningClient) { let node_id = row.node_id.clone().parse::().unwrap(); let coins: Vec = vec![]; undelegation_msgs.push((ExecuteMsg::Undelegate { node_id }, coins)); - undelegation_table.add_row(&[row.node_id.clone()]); + undelegation_table.add_row(std::slice::from_ref(&row.node_id)); if row.amount.amount > 0 { delegation_msgs diff --git a/common/cosmwasm-smart-contracts/contracts-common-testing/src/tester/mod.rs b/common/cosmwasm-smart-contracts/contracts-common-testing/src/tester/mod.rs index 2d041a0933..d6b88bafc5 100644 --- a/common/cosmwasm-smart-contracts/contracts-common-testing/src/tester/mod.rs +++ b/common/cosmwasm-smart-contracts/contracts-common-testing/src/tester/mod.rs @@ -188,7 +188,7 @@ impl ContractTesterBuilder { *self.app.api() } - pub fn querier(&self) -> QuerierWrapper { + pub fn querier(&self) -> QuerierWrapper<'_> { self.app.wrap() } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs index 125359d9f5..b5032e64be 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs @@ -57,7 +57,7 @@ pub trait NodeBond { fn is_unbonding(&self) -> bool; - fn identity(&self) -> IdentityKeyRef; + fn identity(&self) -> IdentityKeyRef<'_>; fn original_pledge(&self) -> &Coin; @@ -125,7 +125,7 @@ impl NodeBond for MixNodeBond { self.is_unbonding } - fn identity(&self) -> IdentityKeyRef { + fn identity(&self) -> IdentityKeyRef<'_> { self.identity() } @@ -178,7 +178,7 @@ impl NodeBond for NymNodeBond { self.is_unbonding } - fn identity(&self) -> IdentityKeyRef { + fn identity(&self) -> IdentityKeyRef<'_> { self.identity() } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs index cecbd4392f..6fc172db09 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs @@ -58,7 +58,7 @@ impl<'a> PrimaryKey<'a> for Role { type Suffix = >::Suffix; type SuperSuffix = >::SuperSuffix; - fn key(&self) -> Vec { + fn key(&self) -> Vec> { // I'm not sure why it wasn't possible to delegate the call to // `(*self as u8).key()` directly... // I guess because of the `Key::Ref(&'a [u8])` variant? diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs index e74a56e122..6f9d01eb6f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs @@ -86,6 +86,25 @@ impl IntervalRewardParams { pub fn to_inline_json(&self) -> String { to_json_string(self).unwrap_or_else(|_| "serialisation failure".into()) } + + pub fn active_node_work(&self, standby_node_work: Decimal) -> WorkFactor { + self.active_set_work_factor * standby_node_work + } + + pub fn standby_node_work( + &self, + rewarded_set_size: Decimal, + standby_set_size: Decimal, + ) -> WorkFactor { + let f = self.active_set_work_factor; + let k = rewarded_set_size; + let one = Decimal::one(); + + // nodes in reserve + let k_r = standby_set_size; + + one / (f * k - (f - one) * k_r) + } } /// Parameters used for reward calculation. @@ -109,18 +128,15 @@ pub struct RewardingParams { impl RewardingParams { pub fn active_node_work(&self) -> WorkFactor { - self.interval.active_set_work_factor * self.standby_node_work() + let standby_work = self.standby_node_work(); + self.interval.active_node_work(standby_work) } pub fn standby_node_work(&self) -> WorkFactor { - let f = self.interval.active_set_work_factor; - let k = self.dec_rewarded_set_size(); - let one = Decimal::one(); - - // nodes in reserve - let k_r = self.dec_standby_set_size(); - - one / (f * k - (f - one) * k_r) + let rewarded_set_size = self.dec_rewarded_set_size(); + let standby_set_size = self.dec_standby_set_size(); + self.interval + .standby_node_work(rewarded_set_size, standby_set_size) } pub fn rewarded_set_size(&self) -> u32 { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index b3c4e5b2be..9281e35bbb 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -3,6 +3,7 @@ use crate::config_score::{ConfigScoreParams, OutdatedVersionWeights, VersionScoreFormulaParams}; use crate::nym_node::Role; +use crate::reward_params::RewardedSetParams; use crate::EpochId; use contracts_common::Percent; use cosmwasm_schema::cw_serde; @@ -85,7 +86,11 @@ impl RewardedSet { } pub fn rewarded_set_size(&self) -> usize { - self.active_set_size() + self.standby.len() + self.active_set_size() + self.standby_set_size() + } + + pub fn standby_set_size(&self) -> usize { + self.standby.len() } pub fn get_role(&self, node_id: NodeId) -> Option { @@ -110,6 +115,13 @@ impl RewardedSet { } None } + + pub fn matches_parameters(&self, params: RewardedSetParams) -> bool { + self.entry_gateways.len() <= params.entry_gateways as usize + && self.exit_gateways.len() <= params.exit_gateways as usize + && self.layer1.len() + self.layer2.len() + self.layer3.len() <= params.mixnodes as usize + && self.standby.len() <= params.standby as usize + } } #[cw_serde] diff --git a/common/credential-storage/migrations/20250805120000_expiration_date_signatures_epoch_fix.sql b/common/credential-storage/migrations/20250805120000_expiration_date_signatures_epoch_fix.sql new file mode 100644 index 0000000000..3c83cc0aaa --- /dev/null +++ b/common/credential-storage/migrations/20250805120000_expiration_date_signatures_epoch_fix.sql @@ -0,0 +1,123 @@ +/* + * Copyright 2025 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +-- 1. add temporary `epoch_id` column +ALTER TABLE pending_issuance + ADD COLUMN epoch_id INTEGER; + +-- 2. populate the value +UPDATE pending_issuance +SET epoch_id = (SELECT epoch_id + FROM expiration_date_signatures + WHERE expiration_date_signatures.expiration_date = pending_issuance.expiration_date); + +-- 3. create new expiration_date_signatures table (with changed constraints) +CREATE TABLE expiration_date_signatures_new +( + expiration_date DATE NOT NULL, + + epoch_id INTEGER NOT NULL, + + serialization_revision INTEGER NOT NULL, + + -- combined signatures for all tuples issued for given day + serialised_signatures BLOB NOT NULL, + + PRIMARY KEY (epoch_id, expiration_date) +); + +-- 4. migrate the data +INSERT INTO expiration_date_signatures_new (expiration_date, epoch_id, serialization_revision, serialised_signatures) +SELECT expiration_date, epoch_id, serialization_revision, serialised_signatures +FROM expiration_date_signatures; + +-- 5. drop and recreate the table references (due to new FK) + +-- 5.1. +-- (data for ticketbooks that have an associated deposit, but failed to get issued) +CREATE TABLE pending_issuance_new +( + deposit_id INTEGER NOT NULL PRIMARY KEY, + + -- introduce a way for us to introduce breaking changes in serialization of data + serialization_revision INTEGER NOT NULL, + + pending_ticketbook_data BLOB NOT NULL UNIQUE, + + -- for each ticketbook we MUST have corresponding expiration date signatures + expiration_date DATE NOT NULL, + epoch_id INTEGER NOT NULL, + + -- for each ticketbook we MUST have corresponding expiration date signatures + FOREIGN KEY (epoch_id, expiration_date) REFERENCES expiration_date_signatures_new (epoch_id, expiration_date) +); + +INSERT INTO pending_issuance_new (deposit_id, serialization_revision, pending_ticketbook_data, expiration_date, + epoch_id) +SELECT deposit_id, serialization_revision, pending_ticketbook_data, expiration_date, epoch_id +FROM pending_issuance; + + +-- 5.2. +CREATE TABLE ecash_ticketbook_new +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + + -- introduce a way for us to introduce breaking changes in serialization of data + serialization_revision INTEGER NOT NULL, + + -- the type of the associated ticketbook + ticketbook_type TEXT NOT NULL, + + -- the actual crypto data of the ticketbook (wallet, keys, etc.) + ticketbook_data BLOB NOT NULL UNIQUE, + + -- for each ticketbook we MUST have corresponding expiration date signatures + expiration_date DATE NOT NULL, + + -- for each ticketbook we MUST have corresponding coin index signatures + epoch_id INTEGER NOT NULL, + + -- the initial number of tickets the wallet has been created for + total_tickets INTEGER NOT NULL, + + -- how many tickets have been used so far (the `l` value of the wallet) + used_tickets INTEGER NOT NULL, + + + -- FOREIGN KEYS: + + -- for each ticketbook we MUST have corresponding coin index signatures + FOREIGN KEY (epoch_id) REFERENCES coin_indices_signatures (epoch_id), + + -- for each ticketbook we MUST have corresponding expiration date signatures + FOREIGN KEY (expiration_date, epoch_id) REFERENCES expiration_date_signatures_new (expiration_date, epoch_id) +); + +INSERT INTO ecash_ticketbook_new (id, serialization_revision, ticketbook_type, ticketbook_data, expiration_date, + epoch_id, total_tickets, used_tickets) +SELECT id, + serialization_revision, + ticketbook_type, + ticketbook_data, + expiration_date, + epoch_id, + total_tickets, + used_tickets +FROM ecash_ticketbook; + +-- 6. finally swap out the old tables +-- drop old tables +DROP TABLE pending_issuance; +DROP TABLE ecash_ticketbook; +DROP TABLE expiration_date_signatures; + +-- rename new tables +ALTER TABLE pending_issuance_new + RENAME TO pending_issuance; +ALTER TABLE ecash_ticketbook_new + RENAME TO ecash_ticketbook; +ALTER TABLE expiration_date_signatures_new + RENAME TO expiration_date_signatures; diff --git a/common/credential-storage/src/backends/memory.rs b/common/credential-storage/src/backends/memory.rs index f565376748..b142b70a20 100644 --- a/common/credential-storage/src/backends/memory.rs +++ b/common/credential-storage/src/backends/memory.rs @@ -28,7 +28,7 @@ struct EcashCredentialManagerInner { pending: HashMap, master_vk: HashMap, coin_indices_sigs: HashMap>, - expiration_date_sigs: HashMap>, + expiration_date_sigs: HashMap<(u64, Date), Vec>, _next_id: i64, } @@ -242,10 +242,14 @@ impl MemoryEcachTicketbookManager { pub(crate) async fn get_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: u64, ) -> Option> { let guard = self.inner.read().await; - guard.expiration_date_sigs.get(&expiration_date).cloned() + guard + .expiration_date_sigs + .get(&(epoch_id, expiration_date)) + .cloned() } pub(crate) async fn insert_expiration_date_signatures( @@ -254,8 +258,9 @@ impl MemoryEcachTicketbookManager { ) { let mut guard = self.inner.write().await; - guard - .expiration_date_sigs - .insert(sigs.expiration_date, sigs.signatures.clone()); + guard.expiration_date_sigs.insert( + (sigs.epoch_id, sigs.expiration_date), + sigs.signatures.clone(), + ); } } diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index 6ac45aec47..d196cad049 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -39,7 +39,7 @@ impl SqliteEcashTicketbookManager { Ok(()) } - pub(crate) async fn begin_storage_tx(&self) -> Result, sqlx::Error> { + pub(crate) async fn begin_storage_tx(&self) -> Result, sqlx::Error> { self.connection_pool.begin().await } @@ -260,15 +260,17 @@ impl SqliteEcashTicketbookManager { pub(crate) async fn get_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: i64, ) -> Result, sqlx::Error> { sqlx::query_as!( RawExpirationDateSignatures, r#" - SELECT epoch_id as "epoch_id: u32", serialised_signatures, serialization_revision as "serialization_revision: u8" + SELECT serialised_signatures, serialization_revision as "serialization_revision: u8" FROM expiration_date_signatures - WHERE expiration_date = ? + WHERE expiration_date = ? AND epoch_id = ? "#, - expiration_date + expiration_date, + epoch_id ) .fetch_optional(&*self.connection_pool) .await diff --git a/common/credential-storage/src/ephemeral_storage.rs b/common/credential-storage/src/ephemeral_storage.rs index fdcd89ccbd..2356f2da9d 100644 --- a/common/credential-storage/src/ephemeral_storage.rs +++ b/common/credential-storage/src/ephemeral_storage.rs @@ -166,10 +166,11 @@ impl Storage for EphemeralStorage { async fn get_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: u64, ) -> Result>, Self::StorageError> { Ok(self .storage_manager - .get_expiration_date_signatures(expiration_date) + .get_expiration_date_signatures(expiration_date, epoch_id) .await) } diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 2edf9df3cb..219874d829 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -60,7 +60,6 @@ pub struct StoredPendingTicketbook { #[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))] pub struct RawExpirationDateSignatures { - pub epoch_id: u32, pub serialised_signatures: Vec, pub serialization_revision: u8, } diff --git a/common/credential-storage/src/persistent_storage/mod.rs b/common/credential-storage/src/persistent_storage/mod.rs index 35df10c318..23a9f00cb9 100644 --- a/common/credential-storage/src/persistent_storage/mod.rs +++ b/common/credential-storage/src/persistent_storage/mod.rs @@ -325,10 +325,11 @@ impl Storage for PersistentStorage { async fn get_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: u64, ) -> Result>, Self::StorageError> { let Some(raw) = self .storage_manager - .get_expiration_date_signatures(expiration_date) + .get_expiration_date_signatures(expiration_date, epoch_id as i64) .await? else { return Ok(None); diff --git a/common/credential-storage/src/storage.rs b/common/credential-storage/src/storage.rs index b4288f1b88..cc28282889 100644 --- a/common/credential-storage/src/storage.rs +++ b/common/credential-storage/src/storage.rs @@ -92,6 +92,7 @@ pub trait Storage: Clone + Send + Sync { async fn get_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: u64, ) -> Result>, Self::StorageError>; async fn insert_expiration_date_signatures( diff --git a/common/credential-verification/src/ecash/mod.rs b/common/credential-verification/src/ecash/mod.rs index 107ac45299..5586ef5ab1 100644 --- a/common/credential-verification/src/ecash/mod.rs +++ b/common/credential-verification/src/ecash/mod.rs @@ -39,7 +39,7 @@ impl traits::EcashManager for EcashManager { async fn verification_key( &self, epoch_id: EpochId, - ) -> Result, EcashTicketError> { + ) -> Result, EcashTicketError> { self.shared_state.verification_key(epoch_id).await } @@ -231,7 +231,7 @@ impl traits::EcashManager for MockEcashManager { async fn verification_key( &self, _epoch_id: EpochId, - ) -> Result, EcashTicketError> { + ) -> Result, EcashTicketError> { Ok(self.verfication_key.read().await) } diff --git a/common/credential-verification/src/ecash/state.rs b/common/credential-verification/src/ecash/state.rs index c16db2111f..85ada02f5c 100644 --- a/common/credential-verification/src/ecash/state.rs +++ b/common/credential-verification/src/ecash/state.rs @@ -125,7 +125,7 @@ impl SharedState { async fn set_epoch_data( &self, epoch_id: EpochId, - ) -> Result>, EcashTicketError> { + ) -> Result>, EcashTicketError> { let Some(threshold) = self.threshold(epoch_id).await? else { return Err(EcashTicketError::DKGThresholdUnavailable { epoch_id }); }; @@ -186,7 +186,7 @@ impl SharedState { pub(crate) async fn api_clients( &self, epoch_id: EpochId, - ) -> Result>, EcashTicketError> { + ) -> Result>, EcashTicketError> { let guard = self.epoch_data.read().await; // the key was already in the map @@ -212,7 +212,7 @@ impl SharedState { pub(crate) async fn verification_key( &self, epoch_id: EpochId, - ) -> Result, EcashTicketError> { + ) -> Result, EcashTicketError> { let guard = self.epoch_data.read().await; // the key was already in the map @@ -235,11 +235,11 @@ impl SharedState { })) } - pub(crate) async fn start_tx(&self) -> RwLockWriteGuard { + pub(crate) async fn start_tx(&self) -> RwLockWriteGuard<'_, DirectSigningHttpRpcNyxdClient> { self.nyxd_client.write().await } - pub(crate) async fn start_query(&self) -> RwLockReadGuard { + pub(crate) async fn start_query(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> { self.nyxd_client.read().await } diff --git a/common/credential-verification/src/ecash/traits.rs b/common/credential-verification/src/ecash/traits.rs index a7e8197681..ae25016f19 100644 --- a/common/credential-verification/src/ecash/traits.rs +++ b/common/credential-verification/src/ecash/traits.rs @@ -12,7 +12,7 @@ pub trait EcashManager { async fn verification_key( &self, epoch_id: EpochId, - ) -> Result, EcashTicketError>; + ) -> Result, EcashTicketError>; fn storage(&self) -> Box; async fn check_payment( &self, diff --git a/common/credential-verification/src/lib.rs b/common/credential-verification/src/lib.rs index 2164c25514..61f9a1b30c 100644 --- a/common/credential-verification/src/lib.rs +++ b/common/credential-verification/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::ecash::traits::EcashManager; +use async_trait::async_trait; use bandwidth_storage_manager::BandwidthStorageManager; use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime}; use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType}; @@ -139,3 +140,18 @@ impl CredentialVerifier { .await) } } + +#[async_trait] +pub trait TicketVerifier { + /// Verify that the ticket is valid and cryptographically correct. + /// If the verification succeeds, also increase the bandwidth with the ticket's + /// amount and return the latest available bandwidth + async fn verify(&mut self) -> Result; +} + +#[async_trait] +impl TicketVerifier for CredentialVerifier { + async fn verify(&mut self) -> Result { + self.verify().await + } +} diff --git a/common/credentials-interface/Cargo.toml b/common/credentials-interface/Cargo.toml index f7f957d68b..d6d4ce842b 100644 --- a/common/credentials-interface/Cargo.toml +++ b/common/credentials-interface/Cargo.toml @@ -15,6 +15,7 @@ bls12_381 = { workspace = true, default-features = false } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } strum = { workspace = true, features = ["derive"] } +strum_macros = { workspace = true } time = { workspace = true, features = ["serde"] } utoipa = { workspace = true } rand = { workspace = true } diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index cf1b138677..9653a3a72d 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -229,9 +229,9 @@ impl From for NymPayInfo { Serialize, Deserialize, Hash, - strum::Display, - strum::EnumString, - strum::EnumIter, + strum_macros::Display, + strum_macros::EnumString, + strum_macros::EnumIter, )] #[serde(rename_all = "kebab-case")] #[strum(serialize_all = "kebab-case")] diff --git a/common/credentials/src/ecash/utils.rs b/common/credentials/src/ecash/utils.rs index 96d24ad018..305aafd532 100644 --- a/common/credentials/src/ecash/utils.rs +++ b/common/credentials/src/ecash/utils.rs @@ -51,7 +51,7 @@ pub async fn obtain_expiration_date_signatures( for ecash_api_client in ecash_api_clients.iter() { match ecash_api_client .api_client - .partial_expiration_date_signatures(None) + .partial_expiration_date_signatures(None, None) .await { Ok(signature) => { diff --git a/common/gateway-requests/Cargo.toml b/common/gateway-requests/Cargo.toml index c39c5530dd..83042ead14 100644 --- a/common/gateway-requests/Cargo.toml +++ b/common/gateway-requests/Cargo.toml @@ -47,4 +47,7 @@ workspace = true default-features = false [dev-dependencies] +anyhow = { workspace = true } nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests +nym-test-utils = { path = "../test-utils" } +tokio = { workspace = true, features = ["full"] } diff --git a/common/gateway-requests/src/registration/handshake/mod.rs b/common/gateway-requests/src/registration/handshake/mod.rs index 53373f5c30..f6ef8a4de4 100644 --- a/common/gateway-requests/src/registration/handshake/mod.rs +++ b/common/gateway-requests/src/registration/handshake/mod.rs @@ -109,3 +109,85 @@ GATEWAY -> CLIENT DONE(status) */ + +#[cfg(test)] +mod tests { + use super::*; + use crate::ClientControlRequest; + use futures::StreamExt; + use nym_test_utils::helpers::u64_seeded_rng; + use nym_test_utils::mocks::stream_sink::mock_streams; + use nym_test_utils::traits::{Leak, Timeboxed, TimeboxedSpawnable}; + use tokio::join; + use tungstenite::Message; + + #[tokio::test] + async fn basic_handshake() -> anyhow::Result<()> { + use anyhow::Context as _; + + // solve the lifetime issue by just leaking the contents of the boxes + // which is perfectly fine in test + let client_rng = u64_seeded_rng(42).leak(); + let gateway_rng = u64_seeded_rng(69).leak(); + + let client_keys = ed25519::KeyPair::new(client_rng).leak(); + let gateway_keys = ed25519::KeyPair::new(gateway_rng).leak(); + + let (client_ws, gateway_ws) = mock_streams::(); + + // we need streams that return Result + let client_ws = client_ws.map(Ok); + let gateway_ws = gateway_ws.map(Ok); + + let client_ws = client_ws.leak(); + let gateway_ws = gateway_ws.leak(); + + let handshake_client = client_handshake( + client_rng, + client_ws, + client_keys, + *gateway_keys.public_key(), + false, + true, + TaskClient::dummy(), + ); + + let client_fut = handshake_client.spawn_timeboxed(); + + // we need to receive the first message so that it could be propagated to the gateway side of the handshake + let ClientControlRequest::RegisterHandshakeInitRequest { + protocol_version: _, + data, + } = (gateway_ws.next()) + .timeboxed() + .await + .context("timeout")? + .context("no message!")?? + .into_text()? + .parse::()? + else { + panic!("bad message") + }; + + let init_msg = data; + + let handshake_gateway = gateway_handshake( + gateway_rng, + gateway_ws, + gateway_keys, + init_msg, + TaskClient::dummy(), + ); + + let gateway_fut = handshake_gateway.spawn_timeboxed(); + let (client, gateway) = join!(client_fut, gateway_fut); + + let client_key = client???; + let gateway_key = gateway???; + + // ensure the created keys are the same + assert_eq!(client_key, gateway_key); + + Ok(()) + } +} diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 564d6874bb..60122efaf1 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -136,6 +136,7 @@ //! ``` #![warn(missing_docs)] +pub use reqwest::ClientBuilder as ReqwestClientBuilder; pub use reqwest::StatusCode; use crate::path::RequestPath; @@ -173,6 +174,10 @@ mod path; #[cfg(not(target_arch = "wasm32"))] pub use dns::{HickoryDnsError, HickoryDnsResolver}; +// helper for generating user agent based on binary information +#[doc(hidden)] +pub use nym_bin_common::bin_info; + /// Default HTTP request connection timeout. /// /// The timeout is relatively high as we are often making requests over the mixnet, where latency is @@ -608,6 +613,7 @@ impl Client { current_idx: Arc::new(Default::default()), reqwest_client: self.reqwest_client.clone(), + #[cfg(feature = "tunneling")] front: self.front.clone(), retry_limit: self.retry_limit, diff --git a/common/http-api-client/src/user_agent.rs b/common/http-api-client/src/user_agent.rs index 1543798a6a..f46a2038ae 100644 --- a/common/http-api-client/src/user_agent.rs +++ b/common/http-api-client/src/user_agent.rs @@ -20,6 +20,16 @@ pub struct UserAgent { pub git_commit: String, } +/// Create `UserAgent` based on the caller's crate information +// we can't use normal function as then `application` and `version` would correspond +// of that of `nym-http-api-client` lib +#[macro_export] +macro_rules! generate_user_agent { + () => { + $crate::UserAgent::from($crate::bin_info!()) + }; +} + #[derive(Clone, Debug, thiserror::Error)] #[error("invalid user agent string: {0}")] pub struct UserAgentError(String); diff --git a/common/network-defaults/src/constants.rs b/common/network-defaults/src/constants.rs index e9b70f54e3..1153fd902d 100644 --- a/common/network-defaults/src/constants.rs +++ b/common/network-defaults/src/constants.rs @@ -47,7 +47,8 @@ pub mod nyx { pub mod wireguard { use std::net::{Ipv4Addr, Ipv6Addr}; - pub const WG_PORT: u16 = 51822; + pub const WG_TUNNEL_PORT: u16 = 51822; + pub const WG_METADATA_PORT: u16 = 51830; // The interface used to route traffic pub const WG_TUN_BASE_NAME: &str = "nymwg"; diff --git a/common/nym-id/src/error.rs b/common/nym-id/src/error.rs index 9ccf73267b..b76afa47cf 100644 --- a/common/nym-id/src/error.rs +++ b/common/nym-id/src/error.rs @@ -25,8 +25,8 @@ pub enum NymIdError { #[error("attempted to import an expired credential (it expired on {expiration})")] ExpiredCredentialImport { expiration: Date }, - #[error("could not import ticketbook expiring at {date} since we do not have corresponding expiration date signatures")] - MissingExpirationDateSignatures { date: Date }, + #[error("could not import ticketbook expiring at {date} for epoch {epoch_id} since we do not have corresponding expiration date signatures")] + MissingExpirationDateSignatures { date: Date, epoch_id: u64 }, #[error("could not import ticketbook for epoch {epoch_id} since we do not have corresponding coin index signatures")] MissingCoinIndexSignatures { epoch_id: u64 }, diff --git a/common/nym-id/src/import_credential/helpers.rs b/common/nym-id/src/import_credential/helpers.rs index dec6228d79..e5caa1e181 100644 --- a/common/nym-id/src/import_credential/helpers.rs +++ b/common/nym-id/src/import_credential/helpers.rs @@ -99,7 +99,7 @@ where // in order to import the ticketbook we MUST have the appropriate signatures in the storage already if credentials_store - .get_expiration_date_signatures(ticketbook.expiration_date()) + .get_expiration_date_signatures(ticketbook.expiration_date(), ticketbook.epoch_id()) .await .map_err(|source| NymIdError::StorageError { source: Box::new(source), @@ -108,6 +108,7 @@ where { return Err(NymIdError::MissingExpirationDateSignatures { date: ticketbook.expiration_date(), + epoch_id: ticketbook.epoch_id(), }); } diff --git a/common/nymnoise/Cargo.toml b/common/nymnoise/Cargo.toml index eead4a60f5..63050814ef 100644 --- a/common/nymnoise/Cargo.toml +++ b/common/nymnoise/Cargo.toml @@ -14,6 +14,7 @@ pin-project = { workspace = true } sha2 = { workspace = true } snow = { workspace = true } strum = { workspace = true, features = ["derive"] } +strum_macros = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["net", "io-util", "time"] } tokio-util = { workspace = true, features = ["codec"] } @@ -27,6 +28,7 @@ anyhow = { workspace = true } tokio = { workspace = true, features = ["full"] } rand_chacha = { workspace = true } nym-crypto = { path = "../crypto", features = ["rand"] } +nym-test-utils = { path = "../test-utils" } [lints] diff --git a/common/nymnoise/src/config.rs b/common/nymnoise/src/config.rs index 94a25cf786..6f5e087d6c 100644 --- a/common/nymnoise/src/config.rs +++ b/common/nymnoise/src/config.rs @@ -13,7 +13,7 @@ use nym_crypto::asymmetric::x25519; use nym_noise_keys::{NoiseVersion, VersionedNoiseKey}; use snow::params::NoiseParams; -use strum::{EnumIter, FromRepr}; +use strum_macros::{EnumIter, FromRepr}; #[derive(Default, Debug, Clone, Copy, EnumIter, FromRepr, Eq, PartialEq)] #[repr(u8)] diff --git a/common/nymnoise/src/stream/framing.rs b/common/nymnoise/src/stream/framing.rs index c0ebcf0917..8c51daa39a 100644 --- a/common/nymnoise/src/stream/framing.rs +++ b/common/nymnoise/src/stream/framing.rs @@ -5,7 +5,7 @@ use crate::config::NoisePattern; use crate::error::NoiseError; use bytes::{Buf, BufMut, Bytes, BytesMut}; use nym_noise_keys::NoiseVersion; -use strum::FromRepr; +use strum_macros::FromRepr; #[derive(Debug)] pub struct NymNoiseFrame { diff --git a/common/nymnoise/src/stream/mod.rs b/common/nymnoise/src/stream/mod.rs index 048f50157c..e860ef46e0 100644 --- a/common/nymnoise/src/stream/mod.rs +++ b/common/nymnoise/src/stream/mod.rs @@ -411,122 +411,21 @@ where mod tests { use super::*; use nym_crypto::asymmetric::x25519; - use rand_chacha::rand_core::SeedableRng; - use std::io::Error; - use std::mem; + use nym_test_utils::helpers::deterministic_rng; + use nym_test_utils::mocks::async_read_write::mock_io_streams; + use nym_test_utils::traits::{Timeboxed, TimeboxedSpawnable}; use std::sync::Arc; - use std::task::{Context, Waker}; - use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::join; - use tokio::sync::Mutex; - use tokio::time::timeout; - - fn mock_streams() -> (MockStream, MockStream) { - let ch1 = Arc::new(Mutex::new(Default::default())); - let ch2 = Arc::new(Mutex::new(Default::default())); - - ( - MockStream { - inner: MockStreamInner { - tx: ch1.clone(), - rx: ch2.clone(), - }, - }, - MockStream { - inner: MockStreamInner { tx: ch2, rx: ch1 }, - }, - ) - } - - struct MockStream { - inner: MockStreamInner, - } - - #[allow(dead_code)] - impl MockStream { - fn unchecked_tx_data(&self) -> Vec { - self.inner.tx.try_lock().unwrap().data.clone() - } - - fn unchecked_rx_data(&self) -> Vec { - self.inner.rx.try_lock().unwrap().data.clone() - } - } - - struct MockStreamInner { - tx: Arc>, - rx: Arc>, - } - - #[derive(Default)] - struct DataWrapper { - data: Vec, - waker: Option, - } - - impl AsyncRead for MockStream { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - let mut inner = self.inner.rx.try_lock().unwrap(); - let data = mem::take(&mut inner.data); - if data.is_empty() { - inner.waker = Some(cx.waker().clone()); - return Poll::Pending; - } - - if let Some(waker) = inner.waker.take() { - waker.wake(); - } - - buf.put_slice(&data); - Poll::Ready(Ok(())) - } - } - - impl AsyncWrite for MockStream { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - let mut inner = self.inner.tx.try_lock().unwrap(); - let len = buf.len(); - - if !inner.data.is_empty() { - assert!(inner.waker.is_none()); - inner.waker = Some(cx.waker().clone()); - return Poll::Pending; - } - - inner.data.extend_from_slice(buf); - if let Some(waker) = inner.waker.take() { - waker.wake(); - } - Poll::Ready(Ok(len)) - } - - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - } #[tokio::test] async fn noise_handshake() -> anyhow::Result<()> { - let dummy_seed = [42u8; 32]; - let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); + let mut rng = deterministic_rng(); let initiator_keys = Arc::new(x25519::KeyPair::new(&mut rng)); let responder_keys = Arc::new(x25519::KeyPair::new(&mut rng)); - let (initiator_stream, responder_stream) = mock_streams(); + let (initiator_stream, responder_stream) = mock_io_streams(); let psk = generate_psk(*responder_keys.public_key(), NoiseVersion::V1)?; let pattern = NoisePattern::default(); @@ -547,14 +446,8 @@ mod tests { *responder_keys.public_key(), ); - let initiator_fut = - tokio::spawn( - async move { timeout(Duration::from_millis(200), stream_initiator).await }, - ); - let responder_fut = - tokio::spawn( - async move { timeout(Duration::from_millis(200), stream_responder).await }, - ); + let initiator_fut = stream_initiator.spawn_timeboxed(); + let responder_fut = stream_responder.spawn_timeboxed(); let (initiator, responder) = join!(initiator_fut, responder_fut); @@ -563,14 +456,13 @@ mod tests { let msg = b"hello there"; // if noise was successful we should be able to write a proper message across - timeout(Duration::from_millis(200), initiator.write_all(msg)).await??; - + initiator.write_all(msg).timeboxed().await??; initiator.inner_stream.flush().await?; let inner_buf = initiator.inner_stream.get_ref().unchecked_tx_data(); let mut buf = [0u8; 11]; - timeout(Duration::from_millis(200), responder.read(&mut buf)).await??; + responder.read(&mut buf).timeboxed().await??; assert_eq!(&buf[..], msg); diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index 1bbd7317c3..18a8ca8fc9 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -21,6 +21,7 @@ time = { workspace = true } tokio = { workspace = true } si-scale = { workspace = true } strum = { workspace = true } +strum_macros = { workspace = true } nym-crypto = { path = "../crypto" } nym-sphinx = { path = "../nymsphinx" } diff --git a/common/statistics/src/types.rs b/common/statistics/src/types.rs index dce345c9d8..127738c1cd 100644 --- a/common/statistics/src/types.rs +++ b/common/statistics/src/types.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; PartialEq, Copy, Clone, - strum::Display, - strum::EnumString, + strum_macros::Display, + strum_macros::EnumString, Serialize, Deserialize, Default, diff --git a/common/task/src/spawn.rs b/common/task/src/spawn.rs index 16f77299d1..e0fe98c5de 100644 --- a/common/task/src/spawn.rs +++ b/common/task/src/spawn.rs @@ -10,6 +10,7 @@ where } #[cfg(not(target_arch = "wasm32"))] +#[track_caller] pub fn spawn(future: F) where F: Future + Send + 'static, @@ -18,6 +19,7 @@ where tokio::spawn(future); } +#[track_caller] pub fn spawn_with_report_error(future: F, mut shutdown: TaskClient) where F: Future> + Send + 'static, diff --git a/common/test-utils/Cargo.toml b/common/test-utils/Cargo.toml new file mode 100644 index 0000000000..8c93779702 --- /dev/null +++ b/common/test-utils/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "nym-test-utils" +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 } +futures = { workspace = true } +rand_chacha = { workspace = true } +tokio = { workspace = true, features = ["sync", "time", "rt"] } + +[dev-dependencies] +tokio = { workspace = true, features = ["full"] } + +[lints] +workspace = true diff --git a/common/test-utils/src/helpers.rs b/common/test-utils/src/helpers.rs new file mode 100644 index 0000000000..26dd19f3b1 --- /dev/null +++ b/common/test-utils/src/helpers.rs @@ -0,0 +1,33 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::traits::Timeboxed; +use rand_chacha::rand_core::SeedableRng; +use rand_chacha::ChaCha20Rng; +use std::future::Future; +use tokio::task::JoinHandle; +use tokio::time::error::Elapsed; + +pub fn leak(val: T) -> &'static mut T { + Box::leak(Box::new(val)) +} + +pub fn spawn_timeboxed(fut: F) -> JoinHandle> +where + F: Future + Send + 'static, + ::Output: Send, +{ + tokio::spawn(async move { fut.timeboxed().await }) +} + +pub fn deterministic_rng() -> ChaCha20Rng { + seeded_rng([42u8; 32]) +} + +pub fn seeded_rng(seed: [u8; 32]) -> ChaCha20Rng { + ChaCha20Rng::from_seed(seed) +} + +pub fn u64_seeded_rng(seed: u64) -> ChaCha20Rng { + ChaCha20Rng::seed_from_u64(seed) +} diff --git a/common/test-utils/src/lib.rs b/common/test-utils/src/lib.rs new file mode 100644 index 0000000000..b1818473cc --- /dev/null +++ b/common/test-utils/src/lib.rs @@ -0,0 +1,6 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod helpers; +pub mod mocks; +pub mod traits; diff --git a/common/test-utils/src/mocks/async_read_write.rs b/common/test-utils/src/mocks/async_read_write.rs new file mode 100644 index 0000000000..859c815882 --- /dev/null +++ b/common/test-utils/src/mocks/async_read_write.rs @@ -0,0 +1,161 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mocks::shared::InnerWrapper; +use futures::ready; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +// sending buffer of the first stream is the receiving buffer of the second stream +// and vice versa +pub fn mock_io_streams() -> (MockIOStream, MockIOStream) { + let ch1 = MockIOStream::default(); + let ch2 = ch1.make_connection(); + + (ch1, ch2) +} + +#[derive(Default)] +pub struct MockIOStream { + // messages to send + tx: InnerWrapper>, + + // messages to receive + rx: InnerWrapper>, +} + +impl MockIOStream { + fn make_connection(&self) -> Self { + MockIOStream { + tx: self.rx.cloned_buffer(), + rx: self.tx.cloned_buffer(), + } + } + + // unwrap in test code is fine + #[allow(clippy::unwrap_used)] + pub fn unchecked_tx_data(&self) -> Vec { + self.tx.buffer.try_lock().unwrap().content.clone() + } + + // unwrap in test code is fine + #[allow(clippy::unwrap_used)] + pub fn unchecked_rx_data(&self) -> Vec { + self.rx.buffer.try_lock().unwrap().content.clone() + } +} + +impl AsyncRead for MockIOStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + ready!(Pin::new(&mut self.rx).poll_guard_ready(cx)); + + // SAFETY: guard is ready + #[allow(clippy::unwrap_used)] + let guard = self.rx.guard().unwrap(); + + let data = guard.take_content(); + if data.is_empty() { + // nothing to retrieve - store the waiter so that the sender could trigger it + guard.waker = Some(cx.waker().clone()); + + // drop the guard so that the sender could actually put messages in + self.rx.transition_to_idle(); + return Poll::Pending; + } + + // if let Some(waker) = guard.waker.take() { + // waker.wake(); + // } + + self.rx.transition_to_idle(); + + buf.put_slice(&data); + Poll::Ready(Ok(())) + } +} + +impl AsyncWrite for MockIOStream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + // wait until we transition to the locked state + ready!(Pin::new(&mut self.tx).poll_guard_ready(cx)); + + // SAFETY: guard is ready + #[allow(clippy::unwrap_used)] + let guard = self.tx.guard().unwrap(); + + let len = buf.len(); + guard.content.extend_from_slice(buf); + + // TODO: if we wanted the behaviour of always reading everything before writing anything extra + // if !guard.content.is_empty() { + // // sanity check + // assert!(guard.waker.is_none()); + // guard.waker = Some(cx.waker().clone()); + // self.tx.transition_to_idle(); + // return Poll::Pending; + // } + + Poll::Ready(Ok(len)) + } + + fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let Some(guard) = self.tx.guard() else { + return Poll::Ready(Err(io::Error::other( + "invalid lock state to send/flush messages", + ))); + }; + + if let Some(waker) = guard.waker.take() { + // notify the receiver if it was waiting for messages + waker.wake(); + } + + // release the guard + self.tx.transition_to_idle(); + + Poll::Ready(Ok(())) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + // make sure our guard is always dropped on close + self.tx.transition_to_idle(); + + Poll::Ready(Ok(())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + #[tokio::test] + async fn basic() { + let (mut stream1, mut stream2) = mock_io_streams(); + stream1.write_all(&[1, 2, 3, 4, 5]).await.unwrap(); + stream1.flush().await.unwrap(); + + let mut buf = [0u8; 5]; + let read = stream2.read(&mut buf).await.unwrap(); + assert_eq!(read, 5); + assert_eq!(&buf[0..5], &[1, 2, 3, 4, 5]); + + let mut buf = [0u8; 5]; + stream2.write_all(&[6, 7, 8, 9, 10]).await.unwrap(); + stream2.flush().await.unwrap(); + + let read = stream1.read(&mut buf).await.unwrap(); + assert_eq!(read, 5); + assert_eq!(&buf[0..5], &[6, 7, 8, 9, 10]); + } +} diff --git a/common/test-utils/src/mocks/mod.rs b/common/test-utils/src/mocks/mod.rs new file mode 100644 index 0000000000..9962160d2a --- /dev/null +++ b/common/test-utils/src/mocks/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod async_read_write; +mod shared; +pub mod stream_sink; diff --git a/common/test-utils/src/mocks/shared.rs b/common/test-utils/src/mocks/shared.rs new file mode 100644 index 0000000000..c760cd9b5d --- /dev/null +++ b/common/test-utils/src/mocks/shared.rs @@ -0,0 +1,109 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use futures::future::BoxFuture; +use futures::{ready, FutureExt}; +use std::mem; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll, Waker}; +use tokio::sync::{Mutex, OwnedMutexGuard}; + +#[derive(Default)] +pub(crate) struct InnerWrapper { + pub(crate) buffer: Arc>>, + lock_state: LockState, +} + +impl InnerWrapper { + pub(crate) fn clone_buffer(&self) -> Arc>> { + Arc::clone(&self.buffer) + } + + pub(crate) fn cloned_buffer(&self) -> Self { + assert!(matches!(self.lock_state, LockState::Idle)); + InnerWrapper { + buffer: self.clone_buffer(), + lock_state: LockState::Idle, + } + } + + // NOTE: it's responsibility of the caller to ensure the guard is released and state transitions to idle! + pub(crate) fn poll_guard_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + match &mut self.lock_state { + LockState::Idle => { + // 1. first try to obtain the guard without locking + let Ok(guard) = self.buffer.clone().try_lock_owned() else { + // 2. if that fails, create the future for obtaining it + self.lock_state = + LockState::TryingToLock(self.buffer.clone().lock_owned().boxed()); + return Poll::Pending; + }; + + // correctly transition to locked state and poll ourselves again + self.lock_state = LockState::Locked(guard); + cx.waker().wake_by_ref(); + Poll::Ready(()) + } + + LockState::TryingToLock(lock_fut) => { + // see if the guard future has resolved, if so, transition to locked state and schedule for another poll + let guard = ready!(lock_fut.as_mut().poll(cx)); + self.lock_state = LockState::Locked(guard); + cx.waker().wake_by_ref(); + Poll::Pending + } + + LockState::Locked(_) => Poll::Ready(()), + } + } + + pub(crate) fn guard(&mut self) -> Option<&mut OwnedMutexGuard>> { + match &mut self.lock_state { + LockState::Locked(guard) => Some(guard), + _ => None, + } + } + + pub(crate) fn transition_to_idle(&mut self) { + self.lock_state = LockState::Idle + } +} + +#[derive(Default)] +pub(crate) enum LockState { + // We haven’t started locking yet + #[default] + Idle, + + // Waiting for the mutex lock future to resolve + TryingToLock(BoxFuture<'static, OwnedMutexGuard>>), + + // We hold the mutex guard + Locked(OwnedMutexGuard>), +} + +#[derive(Default)] +pub struct ContentWrapper { + pub(crate) content: T, + pub(crate) waker: Option, +} + +impl ContentWrapper { + pub fn into_content(self) -> T { + self.content + } + + pub fn content(&self) -> &T { + &self.content + } + + pub(crate) fn take_content(&mut self) -> T + where + T: Default, + { + mem::take(&mut self.content) + } +} + +impl LockState {} diff --git a/common/test-utils/src/mocks/stream_sink.rs b/common/test-utils/src/mocks/stream_sink.rs new file mode 100644 index 0000000000..0cd866092e --- /dev/null +++ b/common/test-utils/src/mocks/stream_sink.rs @@ -0,0 +1,181 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mocks::shared::{ContentWrapper, InnerWrapper}; +use anyhow::{anyhow, bail}; +use futures::{ready, Sink, Stream}; +use std::collections::VecDeque; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tokio::sync::Mutex; + +// sending buffer of the first stream is the receiving buffer of the second stream +// and vice versa +pub fn mock_streams() -> (MockStream, MockStream) +where + T: Send, +{ + let ch1 = MockStream::default(); + let ch2 = ch1.make_connection(); + + (ch1, ch2) +} + +pub struct MockStream { + // messages to send + tx: InnerWrapper>, + + // messages to receive + rx: InnerWrapper>, +} + +impl MockStream { + pub fn clone_tx_buffer(&self) -> Arc>>> + where + T: Send, + { + self.tx.clone_buffer() + } + + pub fn clone_rx_buffer(&self) -> Arc>>> + where + T: Send, + { + self.rx.clone_buffer() + } + + fn make_connection(&self) -> Self + where + T: Send, + { + MockStream { + tx: self.rx.cloned_buffer(), + rx: self.tx.cloned_buffer(), + } + } +} + +impl Default for MockStream { + fn default() -> Self { + MockStream { + tx: InnerWrapper::default(), + rx: InnerWrapper::default(), + } + } +} + +impl Stream for MockStream +where + T: Send, +{ + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + ready!(Pin::new(&mut self.rx).poll_guard_ready(cx)); + + // SAFETY: guard is ready + #[allow(clippy::unwrap_used)] + let guard = self.rx.guard().unwrap(); + + let Some(next) = guard.content.pop_front() else { + // nothing to retrieve - store the waiter so that the sender could trigger it + guard.waker = Some(cx.waker().clone()); + + // drop the guard so that the sender could actually put messages in + self.rx.transition_to_idle(); + return Poll::Pending; + }; + + // there are more messages buffered waiting for us to retrieve + // keep the guard! + if !guard.content.is_empty() { + cx.waker().wake_by_ref(); + } else { + // no more messages, drop the guard + self.rx.transition_to_idle(); + } + + Poll::Ready(Some(next)) + } + + fn size_hint(&self) -> (usize, Option) { + // that's just a minor optimisation, so don't sweat about it too much, + // if we can obtain the mutex, give precise information, otherwise return default values + let Ok(guard) = self.rx.buffer.try_lock() else { + return (0, None); + }; + let items = guard.content.len(); + (items, Some(items)) + } +} + +impl Sink for MockStream +where + T: Send, +{ + type Error = anyhow::Error; + + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // wait until we transition to the locked state + ready!(Pin::new(&mut self.tx).poll_guard_ready(cx)); + Poll::Ready(Ok(())) + } + + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + let Some(guard) = self.tx.guard() else { + bail!("invalid lock state to send messages"); + }; + guard.content.push_back(item); + + Ok(()) + } + + fn poll_flush( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + let Some(guard) = self.tx.guard() else { + return Poll::Ready(Err(anyhow!("invalid lock state to send/flush messages"))); + }; + + if let Some(waker) = guard.waker.take() { + // notify the receiver if it was waiting for messages + waker.wake(); + } + + // release the guard + self.tx.transition_to_idle(); + + Poll::Ready(Ok(())) + } + + fn poll_close( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + // make sure our guard is always dropped on close + self.tx.transition_to_idle(); + + Poll::Ready(Ok(())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::{SinkExt, StreamExt}; + + #[tokio::test] + async fn basic() { + let (mut stream1, mut stream2) = mock_streams(); + stream1.send("foomp").await.unwrap(); + + let received = stream2.next().await.unwrap(); + assert_eq!(received, "foomp"); + + stream2.send("bar").await.unwrap(); + let received = stream1.next().await.unwrap(); + assert_eq!(received, "bar"); + } +} diff --git a/common/test-utils/src/traits.rs b/common/test-utils/src/traits.rs new file mode 100644 index 0000000000..9b95dbd6bf --- /dev/null +++ b/common/test-utils/src/traits.rs @@ -0,0 +1,57 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::helpers::{leak, spawn_timeboxed}; +use std::future::{Future, IntoFuture}; +use std::time::Duration; +use tokio::task::JoinHandle; +use tokio::time::error::Elapsed; + +// a helper trait for use in tests to easily convert `T` into `&'static mut T` +pub trait Leak { + fn leak(self) -> &'static mut Self; +} + +impl Leak for T { + fn leak(self) -> &'static mut T { + leak(self) + } +} + +// those are internal testing traits so we're not concerned about auto traits +#[allow(async_fn_in_trait)] +pub trait Timeboxed: IntoFuture + Sized { + async fn timeboxed(self) -> Result { + self.execute_with_deadline(Duration::from_millis(200)).await + } + + async fn execute_with_deadline(self, timeout: Duration) -> Result { + tokio::time::timeout(timeout, self).await + } +} + +impl Timeboxed for T where T: IntoFuture + Sized {} + +// those are internal testing traits so we're not concerned about auto traits +#[allow(async_fn_in_trait)] +pub trait Spawnable: Future + Sized + Send + 'static { + fn spawn(self) -> JoinHandle + where + ::Output: Send + 'static, + { + tokio::spawn(self) + } +} + +impl Spawnable for T where T: Future + Sized + Send + 'static {} + +pub trait TimeboxedSpawnable: Timeboxed + Spawnable { + fn spawn_timeboxed(self) -> JoinHandle::Output, Elapsed>> + where + ::Output: Send, + { + spawn_timeboxed(self) + } +} + +impl TimeboxedSpawnable for T where T: Spawnable + Future + Send {} diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 6ca8a3d9e4..620ec264d1 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -19,6 +19,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = { workspace = true } strum = { workspace = true, features = ["derive"] } +strum_macros = { workspace = true } thiserror = { workspace = true } ts-rs = { workspace = true } url = { workspace = true } diff --git a/common/types/src/currency.rs b/common/types/src/currency.rs index 9d13bfbc56..0810f278de 100644 --- a/common/types/src/currency.rs +++ b/common/types/src/currency.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::{Display, Formatter}; -use strum::{Display, EnumString, VariantNames}; +use strum_macros::{Display, EnumString, VariantNames}; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( diff --git a/common/wireguard-private-metadata/client/Cargo.toml b/common/wireguard-private-metadata/client/Cargo.toml new file mode 100644 index 0000000000..d7d23ce445 --- /dev/null +++ b/common/wireguard-private-metadata/client/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "nym-wireguard-private-metadata-client" +version = "1.0.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +async-trait = { workspace = true } +tracing = { workspace = true } +nym-http-api-client = { path = "../../http-api-client" } +nym-wireguard-private-metadata-shared = { path = "../shared" } + +[lints] +workspace = true diff --git a/common/wireguard-private-metadata/client/src/lib.rs b/common/wireguard-private-metadata/client/src/lib.rs new file mode 100644 index 0000000000..3f683efe5e --- /dev/null +++ b/common/wireguard-private-metadata/client/src/lib.rs @@ -0,0 +1,58 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use tracing::instrument; + +use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS}; + +use nym_wireguard_private_metadata_shared::{ + routes, Version, {ErrorResponse, Request, Response}, +}; + +pub type WireguardMetadataApiClientError = HttpClientError; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait WireguardMetadataApiClient: ApiClient { + #[instrument(level = "debug", skip(self))] + async fn version(&self) -> Result { + let version: u64 = self + .get_json( + &[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION], + NO_PARAMS, + ) + .await?; + Ok(version.into()) + } + + #[instrument(level = "debug", skip(self))] + async fn available_bandwidth( + &self, + request_body: &Request, + ) -> Result { + self.post_json( + &[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE], + NO_PARAMS, + request_body, + ) + .await + } + + #[instrument(level = "debug", skip(self, request_body))] + async fn topup_bandwidth( + &self, + request_body: &Request, + ) -> Result { + self.post_json( + &[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP], + NO_PARAMS, + request_body, + ) + .await + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl WireguardMetadataApiClient for Client {} diff --git a/common/wireguard-private-metadata/server/Cargo.toml b/common/wireguard-private-metadata/server/Cargo.toml new file mode 100644 index 0000000000..fa850181f1 --- /dev/null +++ b/common/wireguard-private-metadata/server/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "nym-wireguard-private-metadata-server" +version = "1.0.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow = { workspace = true } +axum = { workspace = true, features = ["tokio", "macros"] } +futures = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } +tokio-util = { workspace = true } +tower-http = { workspace = true, features = [ + "cors", + "trace", + "compression-br", + "compression-deflate", + "compression-gzip", + "compression-zstd", +] } +utoipa = { workspace = true, features = ["axum_extras", "time"] } +utoipa-swagger-ui = { workspace = true, features = ["axum"] } + +nym-credentials-interface = { path = "../../credentials-interface" } +nym-credential-verification = { path = "../../credential-verification" } +nym-http-api-common = { path = "../../http-api-common", features = [ + "middleware", + "utoipa", + "output", +] } +nym-wireguard = { path = "../../wireguard" } +nym-wireguard-private-metadata-shared = { path = "../shared" } + +[dev-dependencies] +async-trait = { workspace = true } + + +[lints] +workspace = true diff --git a/common/wireguard-private-metadata/server/src/http/mod.rs b/common/wireguard-private-metadata/server/src/http/mod.rs new file mode 100644 index 0000000000..e3d502d8b5 --- /dev/null +++ b/common/wireguard-private-metadata/server/src/http/mod.rs @@ -0,0 +1,46 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::Arc; + +use tokio::task::JoinHandle; +use tokio_util::sync::CancellationToken; + +use nym_wireguard::WgApiWrapper; + +pub(crate) mod openapi; +pub(crate) mod router; +pub(crate) mod state; + +/// Shutdown goes 2 directions: +/// 1. signal background tasks to gracefully finish +/// 2. signal server itself +/// +/// These are done through separate shutdown handles. Of course, shut down server +/// AFTER you have shut down BG tasks (or past their grace period). +#[allow(unused)] +pub struct ShutdownHandles { + axum_shutdown_button: CancellationToken, + /// Tokio JoinHandle for axum server's task + axum_join_handle: AxumJoinHandle, + /// Wireguard API for kernel interactions + wg_api: Arc, +} + +impl ShutdownHandles { + /// Cancellation token is given to Axum server constructor. When the token + /// receives a shutdown signal, Axum server will shut down gracefully. + pub fn new( + axum_join_handle: AxumJoinHandle, + wg_api: Arc, + axum_shutdown_button: CancellationToken, + ) -> Self { + Self { + axum_shutdown_button, + axum_join_handle, + wg_api, + } + } +} + +type AxumJoinHandle = JoinHandle>; diff --git a/common/wireguard-private-metadata/server/src/http/openapi.rs b/common/wireguard-private-metadata/server/src/http/openapi.rs new file mode 100644 index 0000000000..5e91266582 --- /dev/null +++ b/common/wireguard-private-metadata/server/src/http/openapi.rs @@ -0,0 +1,14 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use utoipa::OpenApi; + +use nym_wireguard_private_metadata_shared::{Request, Response}; + +#[derive(OpenApi)] +#[openapi( + info(title = "Nym Wireguard Private Metadata"), + tags(), + components(schemas(Request, Response)) +)] +pub(crate) struct ApiDoc; diff --git a/common/wireguard-private-metadata/server/src/http/router.rs b/common/wireguard-private-metadata/server/src/http/router.rs new file mode 100644 index 0000000000..c5936f1f4c --- /dev/null +++ b/common/wireguard-private-metadata/server/src/http/router.rs @@ -0,0 +1,101 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::anyhow; +use axum::response::Redirect; +use axum::routing::get; +use axum::Router; +use core::net::SocketAddr; +use nym_http_api_common::middleware::logging::log_request_info; +use tokio::net::TcpListener; +use tokio_util::sync::WaitForCancellationFutureOwned; +use tower_http::cors::CorsLayer; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; + +use crate::http::openapi::ApiDoc; +use crate::http::state::AppState; +use crate::network::bandwidth_routes; + +/// Wrapper around `axum::Router` which ensures correct [order of layers][order]. +/// Add new routes as if you were working directly with `axum`. +/// +/// Why? Middleware like logger, CORS, TLS which need to handle request before other +/// layers should be added last. Using this builder pattern ensures that. +/// +/// [order]: https://docs.rs/axum/latest/axum/middleware/index.html#ordering +pub struct RouterBuilder { + unfinished_router: Router, +} + +impl RouterBuilder { + /// All routes should be, if possible, added here. Exceptions are e.g. + /// routes which are added conditionally in other places based on some `if`. + pub fn with_default_routes() -> Self { + let default_routes = Router::new() + .merge(SwaggerUi::new("/swagger").url("/api-docs/openapi.json", ApiDoc::openapi())) + .route("/", get(|| async { Redirect::to("/swagger") })) + .nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes())); + Self { + unfinished_router: default_routes, + } + } + + /// Invoke this as late as possible before constructing HTTP server + /// (after all routes were added). + pub fn with_state(self, state: AppState) -> RouterWithState { + RouterWithState { + router: self.finalize_routes().with_state(state), + } + } + + /// Middleware added here intercepts the request before it gets to other routes. + fn finalize_routes(self) -> Router { + self.unfinished_router + .layer(setup_cors()) + .layer(axum::middleware::from_fn(log_request_info)) + } +} + +fn setup_cors() -> CorsLayer { + CorsLayer::new() + .allow_origin(tower_http::cors::Any) + .allow_methods([axum::http::Method::GET, axum::http::Method::POST]) + .allow_headers(tower_http::cors::Any) + .allow_credentials(false) +} + +pub struct RouterWithState { + pub router: Router, +} + +impl RouterWithState { + pub async fn build_server(self, bind_address: &SocketAddr) -> anyhow::Result { + let listener = tokio::net::TcpListener::bind(bind_address) + .await + .map_err(|err| anyhow!("Couldn't bind to address {} due to {}", bind_address, err))?; + + Ok(ApiHttpServer { + router: self.router, + listener, + }) + } +} + +pub struct ApiHttpServer { + router: Router, + listener: TcpListener, +} + +impl ApiHttpServer { + pub async fn run(self, receiver: WaitForCancellationFutureOwned) -> Result<(), std::io::Error> { + // into_make_service_with_connect_info allows us to see client ip address + axum::serve( + self.listener, + self.router + .into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(receiver) + .await + } +} diff --git a/common/wireguard-private-metadata/server/src/http/state.rs b/common/wireguard-private-metadata/server/src/http/state.rs new file mode 100644 index 0000000000..06916bb35f --- /dev/null +++ b/common/wireguard-private-metadata/server/src/http/state.rs @@ -0,0 +1,35 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::net::IpAddr; + +use nym_credentials_interface::CredentialSpendingData; + +use crate::transceiver::PeerControllerTransceiver; +use nym_wireguard_private_metadata_shared::error::MetadataError; + +#[derive(Clone, axum::extract::FromRef)] +pub struct AppState { + transceiver: PeerControllerTransceiver, +} + +impl AppState { + pub fn new(transceiver: PeerControllerTransceiver) -> Self { + Self { transceiver } + } + + pub async fn available_bandwidth(&self, ip: IpAddr) -> Result { + self.transceiver.query_bandwidth(ip).await + } + + // Top up with a credential and return the afterwards available bandwidth + pub async fn topup_bandwidth( + &self, + ip: IpAddr, + credential: CredentialSpendingData, + ) -> Result { + self.transceiver + .topup_bandwidth(ip, Box::new(credential)) + .await + } +} diff --git a/common/wireguard-private-metadata/server/src/lib.rs b/common/wireguard-private-metadata/server/src/lib.rs new file mode 100644 index 0000000000..3d772ac1e0 --- /dev/null +++ b/common/wireguard-private-metadata/server/src/lib.rs @@ -0,0 +1,13 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +mod http; +mod network; +mod transceiver; + +pub use http::{ + router::{ApiHttpServer, RouterBuilder, RouterWithState}, + state::AppState, + ShutdownHandles, +}; +pub use transceiver::PeerControllerTransceiver; diff --git a/common/wireguard-private-metadata/server/src/network.rs b/common/wireguard-private-metadata/server/src/network.rs new file mode 100644 index 0000000000..36b22ba32b --- /dev/null +++ b/common/wireguard-private-metadata/server/src/network.rs @@ -0,0 +1,111 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::net::SocketAddr; + +use axum::{ + extract::{ConnectInfo, Query, State}, + Json, Router, +}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_wireguard_private_metadata_shared::{ + interface::{RequestData, ResponseData}, + latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response, +}; +use tower_http::compression::CompressionLayer; + +use crate::http::state::AppState; + +pub(crate) fn bandwidth_routes() -> Router { + Router::new() + .route("/version", axum::routing::get(version)) + .route("/available", axum::routing::post(available_bandwidth)) + .route("/topup", axum::routing::post(topup_bandwidth)) + .layer(CompressionLayer::new()) +} + +#[utoipa::path( + tag = "bandwidth", + get, + path = "/v1/bandwidth/version", + responses( + (status = 200, content( + (Response = "application/bincode") + )) + ), +)] +async fn version(Query(output): Query) -> AxumResult> { + let output = output.output.unwrap_or_default(); + Ok(output.to_response(latest::VERSION.into())) +} + +#[utoipa::path( + tag = "bandwidth", + post, + request_body = Request, + path = "/v1/bandwidth/available", + responses( + (status = 200, content( + (Response = "application/bincode") + )) + ), +)] +async fn available_bandwidth( + ConnectInfo(addr): ConnectInfo, + Query(output): Query, + State(state): State, + Json(request): Json, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let (RequestData::AvailableBandwidth(_), version) = + request.extract().map_err(AxumErrorResponse::bad_request)? + else { + return Err(AxumErrorResponse::bad_request("incorrect request type")); + }; + let available_bandwidth = state + .available_bandwidth(addr.ip()) + .await + .map_err(AxumErrorResponse::bad_request)?; + let response = Response::construct( + ResponseData::AvailableBandwidth(available_bandwidth), + version, + ) + .map_err(AxumErrorResponse::bad_request)?; + + Ok(output.to_response(response)) +} + +#[utoipa::path( + tag = "bandwidth", + post, + request_body = Request, + path = "/v1/bandwidth/topup", + responses( + (status = 200, content( + (Response = "application/bincode") + )) + ), +)] +async fn topup_bandwidth( + ConnectInfo(addr): ConnectInfo, + Query(output): Query, + State(state): State, + Json(request): Json, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let (RequestData::TopUpBandwidth(credential), version) = + request.extract().map_err(AxumErrorResponse::bad_request)? + else { + return Err(AxumErrorResponse::bad_request("incorrect request type")); + }; + let available_bandwidth = state + .topup_bandwidth(addr.ip(), *credential) + .await + .map_err(AxumErrorResponse::bad_request)?; + let response = Response::construct(ResponseData::TopUpBandwidth(available_bandwidth), version) + .map_err(AxumErrorResponse::bad_request)?; + + Ok(output.to_response(response)) +} diff --git a/common/wireguard-private-metadata/server/src/transceiver.rs b/common/wireguard-private-metadata/server/src/transceiver.rs new file mode 100644 index 0000000000..cbe77126cf --- /dev/null +++ b/common/wireguard-private-metadata/server/src/transceiver.rs @@ -0,0 +1,307 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::net::IpAddr; + +use futures::channel::oneshot; +use tokio::sync::mpsc; + +use nym_credential_verification::ClientBandwidth; +use nym_credentials_interface::CredentialSpendingData; +use nym_wireguard::peer_controller::PeerControlRequest; +use nym_wireguard_private_metadata_shared::error::MetadataError; + +#[derive(Clone)] +pub struct PeerControllerTransceiver { + request_tx: mpsc::Sender, +} + +impl PeerControllerTransceiver { + pub fn new(request_tx: mpsc::Sender) -> Self { + Self { request_tx } + } + + async fn get_client_bandwidth(&self, ip: IpAddr) -> Result { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }; + self.request_tx + .send(msg) + .await + .map_err(|_| MetadataError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| MetadataError::NoResponse)? + .map_err(|err| MetadataError::Unsuccessful { + reason: err.to_string(), + }) + } + + pub(crate) async fn query_bandwidth(&self, ip: IpAddr) -> Result { + Ok(self.get_client_bandwidth(ip).await?.available().await) + } + + // Top up with a credential and return the afterwards available bandwidth + pub(crate) async fn topup_bandwidth( + &self, + ip: IpAddr, + credential: Box, + ) -> Result { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::GetVerifierByIp { + ip, + credential, + response_tx, + }; + self.request_tx + .send(msg) + .await + .map_err(|_| MetadataError::PeerInteractionStopped)?; + + let mut verifier = response_rx + .await + .map_err(|_| MetadataError::NoResponse)? + .map_err(|err| MetadataError::Unsuccessful { + reason: err.to_string(), + })?; + let available_bandwidth = + verifier + .verify() + .await + .map_err(|err| MetadataError::CredentialVerification { + message: err.to_string(), + })?; + + Ok(available_bandwidth) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use nym_credential_verification::TicketVerifier; + use nym_wireguard::CONTROL_CHANNEL_SIZE; + + use super::*; + + 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) struct MockVerifier { + ret: i64, + } + + impl MockVerifier { + pub(crate) fn new(ret: i64) -> MockVerifier { + Self { ret } + } + } + + #[async_trait::async_trait] + impl TicketVerifier for MockVerifier { + async fn verify(&mut self) -> nym_credential_verification::Result { + Ok(self.ret) + } + } + + #[tokio::test] + async fn get_bandwidth() { + let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let transceiver = PeerControllerTransceiver::new(request_tx); + + tokio::spawn(async move { + match request_rx.recv().await.unwrap() { + PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => { + response_tx + .send(Ok(ClientBandwidth::new(Default::default()))) + .ok(); + } + _ => panic!("Not expected"), + } + }); + + let bw = transceiver + .query_bandwidth("10.0.0.42".parse().unwrap()) + .await + .unwrap(); + assert_eq!(bw, 0); + } + + #[tokio::test] + async fn stop_peer() { + let (request_tx, request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let transceiver = PeerControllerTransceiver::new(request_tx); + + drop(request_rx); + let err = transceiver + .query_bandwidth("10.0.0.42".parse().unwrap()) + .await + .unwrap_err(); + assert_eq!(err, MetadataError::PeerInteractionStopped); + } + + #[tokio::test] + async fn unresponsive_peer() { + let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let transceiver = PeerControllerTransceiver::new(request_tx); + + tokio::spawn(async move { + match request_rx.recv().await.unwrap() { + PeerControlRequest::GetClientBandwidthByIp { + ip: _, + response_tx: _, + } => {} + _ => panic!("Not expected"), + } + }); + + let err = transceiver + .query_bandwidth("10.0.0.42".parse().unwrap()) + .await + .unwrap_err(); + assert_eq!(err, MetadataError::NoResponse); + } + + #[tokio::test] + async fn unsuccessful_query_bandwidth() { + let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let transceiver = PeerControllerTransceiver::new(request_tx); + + tokio::spawn(async move { + match request_rx.recv().await.unwrap() { + PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => { + response_tx + .send(Err(nym_wireguard::error::Error::Internal( + "testing".to_owned(), + ))) + .ok(); + } + _ => panic!("Not expected"), + } + }); + + let ret = transceiver + .query_bandwidth("10.0.0.42".parse().unwrap()) + .await; + assert!(ret.is_err()); + } + + #[tokio::test] + async fn topup() { + let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let transceiver = PeerControllerTransceiver::new(request_tx); + let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); + let verifier_bw = 42; + + tokio::spawn(async move { + match request_rx.recv().await.unwrap() { + PeerControlRequest::GetVerifierByIp { + ip: _, + credential: _, + response_tx, + } => { + response_tx + .send(Ok(Box::new(MockVerifier::new(verifier_bw)))) + .ok(); + } + _ => panic!("Not expected"), + } + }); + + let bw = transceiver + .topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential)) + .await + .unwrap(); + assert_eq!(bw, verifier_bw); + } + + #[tokio::test] + async fn unsuccessful_topup() { + let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let transceiver = PeerControllerTransceiver::new(request_tx); + let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); + + tokio::spawn(async move { + match request_rx.recv().await.unwrap() { + PeerControlRequest::GetVerifierByIp { + ip: _, + credential: _, + response_tx, + } => { + response_tx + .send(Err(nym_wireguard::error::Error::Internal( + "testing".to_owned(), + ))) + .ok(); + } + _ => panic!("Not expected"), + } + }); + + let ret = transceiver + .topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential)) + .await; + assert!(ret.is_err()); + } +} diff --git a/common/wireguard-private-metadata/shared/Cargo.toml b/common/wireguard-private-metadata/shared/Cargo.toml new file mode 100644 index 0000000000..26b3cc6cd6 --- /dev/null +++ b/common/wireguard-private-metadata/shared/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "nym-wireguard-private-metadata-shared" +version = "1.0.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +axum = { workspace = true } +bincode = { workspace = true } +schemars = { workspace = true, features = ["preserve_order"] } +serde = { workspace = true } +thiserror = { workspace = true } +utoipa = { workspace = true } + +nym-credentials-interface = { path = "../../credentials-interface" } + +[features] +testing = [] + +[lints] +workspace = true diff --git a/common/wireguard-private-metadata/shared/src/error.rs b/common/wireguard-private-metadata/shared/src/error.rs new file mode 100644 index 0000000000..3783462a4d --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/error.rs @@ -0,0 +1,28 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum MetadataError { + #[error("peers can't be interacted with anymore")] + PeerInteractionStopped, + + #[error("no response received")] + NoResponse, + + #[error("query was not successful: {reason}")] + Unsuccessful { reason: String }, + + #[error("Models error: {message}")] + Models { message: String }, + + #[error("Credential verification error: {message}")] + CredentialVerification { message: String }, +} + +impl From for MetadataError { + fn from(value: crate::models::error::Error) -> Self { + Self::Models { + message: value.to_string(), + } + } +} diff --git a/common/wireguard-private-metadata/shared/src/lib.rs b/common/wireguard-private-metadata/shared/src/lib.rs new file mode 100644 index 0000000000..de79157874 --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod error; +mod models; +pub mod routes; + +#[cfg(feature = "testing")] +pub use models::v0; +pub use models::{ + error::Error as ModelError, interface, latest, v1, AxumErrorResponse, AxumResult, Construct, + ErrorResponse, Extract, Request, Response, Version, +}; + +fn make_bincode_serializer() -> impl bincode::Options { + use bincode::Options; + bincode::DefaultOptions::new() + .with_big_endian() + .with_varint_encoding() +} diff --git a/common/wireguard-private-metadata/shared/src/models/error.rs b/common/wireguard-private-metadata/shared/src/models/error.rs new file mode 100644 index 0000000000..45dc88617d --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/error.rs @@ -0,0 +1,30 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::Version; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Bincode(#[from] bincode::Error), + + #[error("trying to deserialize from version {source_version:?} into {target_version:?}")] + InvalidVersion { + source_version: Version, + target_version: Version, + }, + + #[error( + "trying to deserialize from query type {source_query_type} query type {target_query_type}" + )] + InvalidQueryType { + source_query_type: String, + target_query_type: String, + }, + + #[error("update not possible from {from:?} to {to:?}")] + UpdateNotPossible { from: Version, to: Version }, + + #[error("downgrade not possible from {from:?} to {to:?}")] + DowngradeNotPossible { from: Version, to: Version }, +} diff --git a/common/wireguard-private-metadata/shared/src/models/interface.rs b/common/wireguard-private-metadata/shared/src/models/interface.rs new file mode 100644 index 0000000000..3f4537f170 --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/interface.rs @@ -0,0 +1,145 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credentials_interface::CredentialSpendingData; + +#[cfg(feature = "testing")] +use crate::models::v0; +use crate::models::{v1, Construct, Extract, Request, Response, Version}; + +pub enum RequestData { + AvailableBandwidth(()), + TopUpBandwidth(Box), +} + +impl From for RequestData { + fn from(value: super::latest::interface::RequestData) -> Self { + match value { + super::latest::interface::RequestData::AvailableBandwidth(inner) => { + Self::AvailableBandwidth(inner) + } + super::latest::interface::RequestData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl From for super::latest::interface::RequestData { + fn from(value: RequestData) -> Self { + match value { + RequestData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner), + RequestData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl From for ResponseData { + fn from(value: super::latest::interface::ResponseData) -> Self { + match value { + super::latest::interface::ResponseData::AvailableBandwidth(inner) => { + Self::AvailableBandwidth(inner) + } + super::latest::interface::ResponseData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl From for super::latest::interface::ResponseData { + fn from(value: ResponseData) -> Self { + match value { + ResponseData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner), + ResponseData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl Construct for Request { + fn construct(info: RequestData, version: Version) -> Result { + match version { + #[cfg(feature = "testing")] + Version::V0 => { + let translate_info = super::latest::interface::RequestData::from(info); + let downgrade_info = v0::interface::RequestData::try_from(translate_info)?; + let versioned_request = v0::VersionedRequest::construct(downgrade_info, version)?; + Ok(versioned_request.try_into()?) + } + Version::V1 => { + let versioned_request = v1::VersionedRequest::construct(info.into(), version)?; + Ok(versioned_request.try_into()?) + } + } + } +} + +impl Extract for Request { + fn extract(&self) -> Result<(RequestData, Version), crate::models::Error> { + match self.version { + #[cfg(feature = "testing")] + super::Version::V0 => { + let versioned_request = v0::VersionedRequest::try_from(self.clone())?; + let (request, version) = versioned_request.extract()?; + + let upgrade_request = super::latest::interface::RequestData::try_from(request)?; + + Ok((upgrade_request.into(), version)) + } + super::Version::V1 => { + let versioned_request = v1::VersionedRequest::try_from(self.clone())?; + let (extracted, version) = versioned_request.extract()?; + Ok((extracted.into(), version)) + } + } + } +} + +pub enum ResponseData { + AvailableBandwidth(i64), + TopUpBandwidth(i64), +} + +impl Construct for Response { + fn construct(info: ResponseData, version: Version) -> Result { + match version { + #[cfg(feature = "testing")] + super::Version::V0 => { + let translate_response = super::latest::interface::ResponseData::from(info); + let downgrade_response = v0::interface::ResponseData::try_from(translate_response)?; + let versioned_response = + v0::VersionedResponse::construct(downgrade_response, version)?; + Ok(versioned_response.try_into()?) + } + Version::V1 => { + let versioned_response = v1::VersionedResponse::construct(info.into(), version)?; + Ok(versioned_response.try_into()?) + } + } + } +} + +impl Extract for Response { + fn extract(&self) -> Result<(ResponseData, Version), super::error::Error> { + match self.version { + #[cfg(feature = "testing")] + super::Version::V0 => { + let versioned_response = v0::VersionedResponse::try_from(self.clone())?; + let (response, version) = versioned_response.extract()?; + + let upgrade_response = super::latest::interface::ResponseData::try_from(response)?; + + Ok((upgrade_response.into(), version)) + } + super::Version::V1 => { + let versioned_response = v1::VersionedResponse::try_from(self.clone())?; + let (extracted, version) = versioned_response.extract()?; + Ok((extracted.into(), version)) + } + } + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/mod.rs b/common/wireguard-private-metadata/shared/src/models/mod.rs new file mode 100644 index 0000000000..e408d7c5df --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/mod.rs @@ -0,0 +1,175 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::{Display, Formatter}; + +use axum::http::StatusCode; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +pub(crate) mod error; +pub mod interface; +#[cfg(feature = "testing")] +pub mod v0; // dummy version, only for filling boilerplate code for update/downgrade and testing +pub mod v1; + +pub use v1 as latest; + +use crate::models::error::Error; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] +pub enum Version { + #[cfg(feature = "testing")] + /// only used for testing purposes, don't include it in your matching arms + V0, + V1, +} + +impl From for Version { + fn from(value: u64) -> Self { + #[cfg(feature = "testing")] + let zero_version = Version::V0; + #[cfg(not(feature = "testing"))] + let zero_version = latest::VERSION; + match value { + 0 => zero_version, + 1 => Version::V1, + _ => latest::VERSION, // if unknown, it means we're behind, so we can use the latest we know about + } + } +} + +impl From for u64 { + fn from(value: Version) -> Self { + // remember to modify the above match if you're bumping the version + match value { + #[cfg(feature = "testing")] + Version::V0 => 0, + Version::V1 => 1, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct Request { + pub version: Version, + pub(crate) inner: Vec, +} + +#[derive(Clone, Serialize, Deserialize, ToSchema)] +pub struct Response { + pub version: Version, + pub(crate) inner: Vec, +} + +pub trait Extract { + fn extract(&self) -> Result<(T, Version), Error>; +} + +pub trait Construct: Sized { + fn construct(info: T, version: Version) -> Result; +} + +pub type AxumResult = Result; + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +pub struct ErrorResponse { + pub message: String, +} + +impl Display for ErrorResponse { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.message.fmt(f) + } +} + +pub struct AxumErrorResponse { + message: ErrorResponse, + status: StatusCode, +} + +impl AxumErrorResponse { + pub fn bad_request(msg: impl Display) -> Self { + Self { + message: ErrorResponse { + message: msg.to_string(), + }, + status: StatusCode::BAD_REQUEST, + } + } +} + +impl axum::response::IntoResponse for AxumErrorResponse { + fn into_response(self) -> axum::response::Response { + (self.status, self.message.message.to_string()).into_response() + } +} + +mod tests { + #[allow(dead_code)] + 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, + ]; +} diff --git a/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/mod.rs b/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/mod.rs new file mode 100644 index 0000000000..10698cca2d --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod request; +pub mod response; diff --git a/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/request.rs b/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/request.rs new file mode 100644 index 0000000000..78dfdec7b5 --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/request.rs @@ -0,0 +1,86 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Request}; + +use super::super::{Error, QueryType, VersionedRequest}; + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct InnerAvailableBandwidthRequest {} + +impl TryFrom for InnerAvailableBandwidthRequest { + type Error = Error; + + fn try_from(value: VersionedRequest) -> Result { + match value.query_type { + QueryType::AvailableBandwidth => { + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } + QueryType::TopupBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::AvailableBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedRequest { + type Error = Error; + + fn try_from(value: InnerAvailableBandwidthRequest) -> Result { + Ok(Self { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerAvailableBandwidthRequest { + type Error = crate::error::MetadataError; + + fn try_from(value: Request) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Request { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerAvailableBandwidthRequest) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let req = InnerAvailableBandwidthRequest {}; + let ser = VersionedRequest::try_from(req).unwrap(); + assert_eq!(QueryType::AvailableBandwidth, ser.query_type); + let de = InnerAvailableBandwidthRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn empty_content() { + let future_req = VersionedRequest { + query_type: QueryType::AvailableBandwidth, + inner: vec![], + }; + assert!(InnerAvailableBandwidthRequest::try_from(future_req).is_ok()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/response.rs b/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/response.rs new file mode 100644 index 0000000000..5243e312ee --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/available_bandwidth/response.rs @@ -0,0 +1,86 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Response}; + +use super::super::{Error, QueryType, VersionedResponse}; + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct InnerAvailableBandwidthResponse {} + +impl TryFrom for InnerAvailableBandwidthResponse { + type Error = Error; + + fn try_from(value: VersionedResponse) -> Result { + match value.query_type { + QueryType::AvailableBandwidth => { + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } + QueryType::TopupBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::AvailableBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedResponse { + type Error = Error; + + fn try_from(value: InnerAvailableBandwidthResponse) -> Result { + Ok(Self { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerAvailableBandwidthResponse { + type Error = crate::error::MetadataError; + + fn try_from(value: Response) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Response { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerAvailableBandwidthResponse) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let resp = InnerAvailableBandwidthResponse {}; + let ser = VersionedResponse::try_from(resp).unwrap(); + assert_eq!(QueryType::AvailableBandwidth, ser.query_type); + let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } + + #[test] + fn empty_content() { + let future_resp = VersionedResponse { + query_type: QueryType::AvailableBandwidth, + inner: vec![], + }; + assert!(InnerAvailableBandwidthResponse::try_from(future_resp).is_ok()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v0/interface.rs b/common/wireguard-private-metadata/shared/src/models/v0/interface.rs new file mode 100644 index 0000000000..2f8fd2805c --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/interface.rs @@ -0,0 +1,73 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::{ + available_bandwidth::{ + request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse, + }, + topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse}, + QueryType, VersionedRequest, VersionedResponse, VERSION, +}; +use crate::models::{error::Error, Construct, Extract, Version}; + +#[derive(Debug, Clone, PartialEq)] +pub enum RequestData { + AvailableBandwidth(()), + TopUpBandwidth(()), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ResponseData { + AvailableBandwidth(()), + TopUpBandwidth(()), +} + +impl Construct for VersionedRequest { + fn construct(info: RequestData, _version: Version) -> Result { + match info { + RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?), + RequestData::TopUpBandwidth(_) => Ok(InnerTopUpRequest {}.try_into()?), + } + } +} + +impl Extract for VersionedRequest { + fn extract(&self) -> Result<(RequestData, Version), Error> { + match self.query_type { + QueryType::AvailableBandwidth => { + let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?; + Ok((RequestData::AvailableBandwidth(()), VERSION)) + } + QueryType::TopupBandwidth => { + let _req = InnerTopUpRequest::try_from(self.clone())?; + Ok((RequestData::TopUpBandwidth(()), VERSION)) + } + } + } +} + +impl Construct for VersionedResponse { + fn construct(info: ResponseData, _version: Version) -> Result { + match info { + ResponseData::AvailableBandwidth(()) => { + Ok(InnerAvailableBandwidthResponse {}.try_into()?) + } + ResponseData::TopUpBandwidth(()) => Ok(InnerTopUpResponse {}.try_into()?), + } + } +} + +impl Extract for VersionedResponse { + fn extract(&self) -> Result<(ResponseData, Version), Error> { + match self.query_type { + QueryType::AvailableBandwidth => { + let _resp = InnerAvailableBandwidthResponse::try_from(self.clone())?; + Ok((ResponseData::AvailableBandwidth(()), VERSION)) + } + QueryType::TopupBandwidth => { + let _resp = InnerTopUpResponse::try_from(self.clone())?; + Ok((ResponseData::TopUpBandwidth(()), VERSION)) + } + } + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v0/mod.rs b/common/wireguard-private-metadata/shared/src/models/v0/mod.rs new file mode 100644 index 0000000000..997fbf6f57 --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/mod.rs @@ -0,0 +1,175 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Display; + +use bincode::Options; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use super::error::Error; +use crate::{ + make_bincode_serializer, + models::{Request, Response, Version}, +}; + +pub(crate) mod available_bandwidth; +pub mod interface; +pub(crate) mod topup_bandwidth; + +pub const VERSION: Version = Version::V0; + +pub use available_bandwidth::{ + request::InnerAvailableBandwidthRequest as AvailableBandwidthRequest, + response::InnerAvailableBandwidthResponse as AvailableBandwidthResponse, +}; +pub use topup_bandwidth::{ + request::InnerTopUpRequest as TopUpRequest, response::InnerTopUpResponse as TopUpResponse, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] +pub enum QueryType { + AvailableBandwidth, + TopupBandwidth, +} + +impl Display for QueryType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct VersionedRequest { + query_type: QueryType, + inner: Vec, +} + +impl TryFrom for Request { + type Error = Error; + + fn try_from(value: VersionedRequest) -> Result { + Ok(Request { + version: VERSION, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for VersionedRequest { + type Error = Error; + + fn try_from(value: Request) -> Result { + if value.version != VERSION { + return Err(Error::InvalidVersion { + source_version: value.version, + target_version: VERSION, + }); + } + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct VersionedResponse { + query_type: QueryType, + inner: Vec, +} + +impl TryFrom for Response { + type Error = Error; + + fn try_from(value: VersionedResponse) -> Result { + Ok(Response { + version: VERSION, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for VersionedResponse { + type Error = Error; + + fn try_from(value: Response) -> Result { + if value.version != VERSION { + return Err(Error::InvalidVersion { + source_version: value.version, + target_version: VERSION, + }); + } + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } +} + +#[cfg(test)] +mod tests { + use self::{ + available_bandwidth::{ + request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse, + }, + topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse}, + }; + + use super::*; + + #[test] + fn serde_request_av_bw() { + let req = VersionedRequest { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerAvailableBandwidthRequest {}) + .unwrap(), + }; + + let ser = Request::try_from(req.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn serde_response_av_bw() { + let resp = VersionedResponse { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerAvailableBandwidthResponse {}) + .unwrap(), + }; + + let ser = Response::try_from(resp.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } + + #[test] + fn serde_request_topup() { + let req = VersionedRequest { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerTopUpRequest {}) + .unwrap(), + }; + + let ser = Request::try_from(req.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn serde_response_topup() { + let resp = VersionedResponse { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerTopUpResponse {}) + .unwrap(), + }; + + let ser = Response::try_from(resp.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/mod.rs b/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/mod.rs new file mode 100644 index 0000000000..10698cca2d --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod request; +pub mod response; diff --git a/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/request.rs b/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/request.rs new file mode 100644 index 0000000000..9c333478d2 --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/request.rs @@ -0,0 +1,84 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Request}; + +use super::super::{Error, QueryType, VersionedRequest}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct InnerTopUpRequest {} + +impl TryFrom for InnerTopUpRequest { + type Error = Error; + + fn try_from(value: VersionedRequest) -> Result { + match value.query_type { + QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?), + QueryType::AvailableBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::TopupBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedRequest { + type Error = Error; + + fn try_from(value: InnerTopUpRequest) -> Result { + Ok(Self { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerTopUpRequest { + type Error = crate::error::MetadataError; + + fn try_from(value: Request) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Request { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerTopUpRequest) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let req = InnerTopUpRequest {}; + let ser = VersionedRequest::try_from(req.clone()).unwrap(); + assert_eq!(QueryType::TopupBandwidth, ser.query_type); + let de = InnerTopUpRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn empty_content() { + let future_req = VersionedRequest { + query_type: QueryType::TopupBandwidth, + inner: vec![], + }; + assert!(InnerTopUpRequest::try_from(future_req).is_ok()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/response.rs b/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/response.rs new file mode 100644 index 0000000000..cd934b6e7e --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v0/topup_bandwidth/response.rs @@ -0,0 +1,84 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Response}; + +use super::super::{Error, QueryType, VersionedResponse}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct InnerTopUpResponse {} + +impl TryFrom for InnerTopUpResponse { + type Error = Error; + + fn try_from(value: VersionedResponse) -> Result { + match value.query_type { + QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?), + QueryType::AvailableBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::TopupBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedResponse { + type Error = Error; + + fn try_from(value: InnerTopUpResponse) -> Result { + Ok(Self { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerTopUpResponse { + type Error = crate::error::MetadataError; + + fn try_from(value: Response) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Response { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerTopUpResponse) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let resp = InnerTopUpResponse {}; + let ser = VersionedResponse::try_from(resp.clone()).unwrap(); + assert_eq!(QueryType::TopupBandwidth, ser.query_type); + let de = InnerTopUpResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } + + #[test] + fn empty_content() { + let future_resp = VersionedResponse { + query_type: QueryType::TopupBandwidth, + inner: vec![], + }; + assert!(InnerTopUpResponse::try_from(future_resp).is_ok()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/mod.rs b/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/mod.rs new file mode 100644 index 0000000000..10698cca2d --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod request; +pub mod response; diff --git a/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/request.rs b/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/request.rs new file mode 100644 index 0000000000..78dfdec7b5 --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/request.rs @@ -0,0 +1,86 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Request}; + +use super::super::{Error, QueryType, VersionedRequest}; + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct InnerAvailableBandwidthRequest {} + +impl TryFrom for InnerAvailableBandwidthRequest { + type Error = Error; + + fn try_from(value: VersionedRequest) -> Result { + match value.query_type { + QueryType::AvailableBandwidth => { + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } + QueryType::TopupBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::AvailableBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedRequest { + type Error = Error; + + fn try_from(value: InnerAvailableBandwidthRequest) -> Result { + Ok(Self { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerAvailableBandwidthRequest { + type Error = crate::error::MetadataError; + + fn try_from(value: Request) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Request { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerAvailableBandwidthRequest) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let req = InnerAvailableBandwidthRequest {}; + let ser = VersionedRequest::try_from(req).unwrap(); + assert_eq!(QueryType::AvailableBandwidth, ser.query_type); + let de = InnerAvailableBandwidthRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn empty_content() { + let future_req = VersionedRequest { + query_type: QueryType::AvailableBandwidth, + inner: vec![], + }; + assert!(InnerAvailableBandwidthRequest::try_from(future_req).is_ok()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/response.rs b/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/response.rs new file mode 100644 index 0000000000..f5addd609f --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/available_bandwidth/response.rs @@ -0,0 +1,90 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Response}; + +use super::super::{Error, QueryType, VersionedResponse}; + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct InnerAvailableBandwidthResponse { + pub available_bandwidth: i64, +} + +impl TryFrom for InnerAvailableBandwidthResponse { + type Error = Error; + + fn try_from(value: VersionedResponse) -> Result { + match value.query_type { + QueryType::AvailableBandwidth => { + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } + QueryType::TopupBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::AvailableBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedResponse { + type Error = Error; + + fn try_from(value: InnerAvailableBandwidthResponse) -> Result { + Ok(Self { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerAvailableBandwidthResponse { + type Error = crate::error::MetadataError; + + fn try_from(value: Response) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Response { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerAvailableBandwidthResponse) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let resp = InnerAvailableBandwidthResponse { + available_bandwidth: 42, + }; + let ser = VersionedResponse::try_from(resp).unwrap(); + assert_eq!(QueryType::AvailableBandwidth, ser.query_type); + let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } + + #[test] + fn invalid_content() { + let future_resp = VersionedResponse { + query_type: QueryType::AvailableBandwidth, + inner: vec![], + }; + assert!(InnerAvailableBandwidthResponse::try_from(future_resp).is_err()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v1/interface.rs b/common/wireguard-private-metadata/shared/src/models/v1/interface.rs new file mode 100644 index 0000000000..46cafddc7d --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/interface.rs @@ -0,0 +1,224 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credentials_interface::CredentialSpendingData; + +#[cfg(feature = "testing")] +use super::super::v0 as previous; + +use super::{ + available_bandwidth::{ + request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse, + }, + topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse}, + QueryType, VersionedRequest, VersionedResponse, VERSION, +}; +use crate::models::{error::Error, Construct, Extract, Version}; + +#[derive(Debug, Clone, PartialEq)] +pub enum RequestData { + AvailableBandwidth(()), + TopUpBandwidth(Box), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ResponseData { + AvailableBandwidth(i64), + TopUpBandwidth(i64), +} + +impl Construct for VersionedRequest { + fn construct(info: RequestData, _version: Version) -> Result { + match info { + RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?), + RequestData::TopUpBandwidth(credential) => Ok(InnerTopUpRequest { + credential: *credential, + } + .try_into()?), + } + } +} + +impl Extract for VersionedRequest { + fn extract(&self) -> Result<(RequestData, Version), Error> { + match self.query_type { + QueryType::AvailableBandwidth => { + let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?; + Ok((RequestData::AvailableBandwidth(()), VERSION)) + } + QueryType::TopupBandwidth => { + let req = InnerTopUpRequest::try_from(self.clone())?; + Ok(( + RequestData::TopUpBandwidth(Box::new(req.credential)), + VERSION, + )) + } + } + } +} + +impl Construct for VersionedResponse { + fn construct(info: ResponseData, _version: Version) -> Result { + match info { + ResponseData::AvailableBandwidth(available_bandwidth) => { + Ok(InnerAvailableBandwidthResponse { + available_bandwidth, + } + .try_into()?) + } + ResponseData::TopUpBandwidth(available_bandwidth) => Ok(InnerTopUpResponse { + available_bandwidth, + } + .try_into()?), + } + } +} + +impl Extract for VersionedResponse { + fn extract(&self) -> Result<(ResponseData, Version), Error> { + match self.query_type { + QueryType::AvailableBandwidth => { + let resp = InnerAvailableBandwidthResponse::try_from(self.clone())?; + Ok(( + ResponseData::AvailableBandwidth(resp.available_bandwidth), + VERSION, + )) + } + QueryType::TopupBandwidth => { + let resp = InnerTopUpResponse::try_from(self.clone())?; + Ok(( + ResponseData::TopUpBandwidth(resp.available_bandwidth), + VERSION, + )) + } + } + } +} + +// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions +#[cfg(feature = "testing")] +impl TryFrom for RequestData { + type Error = super::Error; + + fn try_from(value: previous::interface::RequestData) -> Result { + match value { + previous::interface::RequestData::AvailableBandwidth(inner) => { + Ok(Self::AvailableBandwidth(inner)) + } + previous::interface::RequestData::TopUpBandwidth(_) => { + Err(super::Error::UpdateNotPossible { + from: previous::VERSION, + to: VERSION, + }) + } + } + } +} + +// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions +#[cfg(feature = "testing")] +impl TryFrom for previous::interface::RequestData { + type Error = super::Error; + + fn try_from(value: RequestData) -> Result { + match value { + RequestData::AvailableBandwidth(inner) => Ok(Self::AvailableBandwidth(inner)), + RequestData::TopUpBandwidth(_) => Ok(Self::TopUpBandwidth(())), + } + } +} + +// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions +#[cfg(feature = "testing")] +impl TryFrom for ResponseData { + type Error = super::Error; + + fn try_from(value: previous::interface::ResponseData) -> Result { + match value { + previous::interface::ResponseData::AvailableBandwidth(_) => { + Err(super::Error::UpdateNotPossible { + from: previous::VERSION, + to: VERSION, + }) + } + previous::interface::ResponseData::TopUpBandwidth(_) => { + Err(super::Error::UpdateNotPossible { + from: previous::VERSION, + to: VERSION, + }) + } + } + } +} + +// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions +#[cfg(feature = "testing")] +impl TryFrom for previous::interface::ResponseData { + type Error = super::Error; + + fn try_from(value: ResponseData) -> Result { + match value { + ResponseData::AvailableBandwidth(_) => Ok(Self::AvailableBandwidth(())), + ResponseData::TopUpBandwidth(_) => Ok(Self::TopUpBandwidth(())), + } + } +} + +#[cfg(test)] +mod test { + use crate::models::tests::CREDENTIAL_BYTES; + + use super::*; + + #[test] + fn request_upgrade() { + assert_eq!( + RequestData::try_from(previous::interface::RequestData::AvailableBandwidth(())) + .unwrap(), + RequestData::AvailableBandwidth(()) + ); + assert!( + RequestData::try_from(previous::interface::RequestData::TopUpBandwidth(())).is_err(), + ); + } + + #[test] + fn response_upgrade() { + assert!( + ResponseData::try_from(previous::interface::ResponseData::AvailableBandwidth(())) + .is_err() + ); + assert!( + ResponseData::try_from(previous::interface::ResponseData::TopUpBandwidth(())).is_err() + ); + } + + #[test] + fn request_downgrade() { + assert_eq!( + previous::interface::RequestData::try_from(RequestData::AvailableBandwidth(())) + .unwrap(), + previous::interface::RequestData::AvailableBandwidth(()) + ); + assert_eq!( + previous::interface::RequestData::try_from(RequestData::TopUpBandwidth(Box::new( + CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap() + ))) + .unwrap(), + previous::interface::RequestData::TopUpBandwidth(()) + ); + } + + #[test] + fn response_downgrade() { + assert_eq!( + previous::interface::ResponseData::try_from(ResponseData::AvailableBandwidth(42)) + .unwrap(), + previous::interface::ResponseData::AvailableBandwidth(()) + ); + assert_eq!( + previous::interface::ResponseData::try_from(ResponseData::TopUpBandwidth(42)).unwrap(), + previous::interface::ResponseData::TopUpBandwidth(()) + ); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v1/mod.rs b/common/wireguard-private-metadata/shared/src/models/v1/mod.rs new file mode 100644 index 0000000000..787a74f671 --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/mod.rs @@ -0,0 +1,224 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Display; + +use bincode::Options; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use super::error::Error; +use crate::{ + make_bincode_serializer, + models::{Request, Response, Version}, +}; + +pub use available_bandwidth::{ + request::InnerAvailableBandwidthRequest as AvailableBandwidthRequest, + response::InnerAvailableBandwidthResponse as AvailableBandwidthResponse, +}; +pub use topup_bandwidth::{ + request::InnerTopUpRequest as TopUpRequest, response::InnerTopUpResponse as TopUpResponse, +}; + +pub(crate) mod available_bandwidth; +pub mod interface; +pub(crate) mod topup_bandwidth; + +pub const VERSION: Version = Version::V1; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] +pub enum QueryType { + AvailableBandwidth, + TopupBandwidth, +} + +impl Display for QueryType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct VersionedRequest { + query_type: QueryType, + inner: Vec, +} + +impl TryFrom for Request { + type Error = Error; + + fn try_from(value: VersionedRequest) -> Result { + Ok(Request { + version: VERSION, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for VersionedRequest { + type Error = Error; + + fn try_from(value: Request) -> Result { + if value.version != VERSION { + return Err(Error::InvalidVersion { + source_version: value.version, + target_version: VERSION, + }); + } + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct VersionedResponse { + query_type: QueryType, + inner: Vec, +} + +impl TryFrom for Response { + type Error = Error; + + fn try_from(value: VersionedResponse) -> Result { + Ok(Response { + version: VERSION, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for VersionedResponse { + type Error = Error; + + fn try_from(value: Response) -> Result { + if value.version != VERSION { + return Err(Error::InvalidVersion { + source_version: value.version, + target_version: VERSION, + }); + } + Ok(make_bincode_serializer().deserialize(&value.inner)?) + } +} + +#[cfg(test)] +mod tests { + + use nym_credentials_interface::CredentialSpendingData; + + use crate::models::tests::CREDENTIAL_BYTES; + + use self::{ + available_bandwidth::{ + request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse, + }, + topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse}, + }; + + use super::*; + + #[test] + fn mismatched_request_version() { + let version = Version::V0; + let future_bw = Request { + version, + inner: vec![], + }; + if let Err(Error::InvalidVersion { + source_version, + target_version, + }) = VersionedRequest::try_from(future_bw) + { + assert_eq!(source_version, version); + assert_eq!(target_version, VERSION); + } else { + panic!("failed"); + }; + } + + #[test] + fn mismatched_response_version() { + let version = Version::V0; + let future_bw = Response { + version, + inner: vec![], + }; + if let Err(Error::InvalidVersion { + source_version, + target_version, + }) = VersionedResponse::try_from(future_bw) + { + assert_eq!(source_version, version); + assert_eq!(target_version, VERSION); + } else { + panic!("failed"); + }; + } + + #[test] + fn serde_request_av_bw() { + let req = VersionedRequest { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerAvailableBandwidthResponse { + available_bandwidth: 42, + }) + .unwrap(), + }; + + let ser = Request::try_from(req.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn serde_response_av_bw() { + let resp = VersionedResponse { + query_type: QueryType::AvailableBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerAvailableBandwidthRequest {}) + .unwrap(), + }; + + let ser = Response::try_from(resp.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } + + #[test] + fn serde_request_topup() { + let req = VersionedRequest { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerTopUpRequest { + credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(), + }) + .unwrap(), + }; + + let ser = Request::try_from(req.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn serde_response_topup() { + let resp = VersionedResponse { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer() + .serialize(&InnerTopUpResponse { + available_bandwidth: 42, + }) + .unwrap(), + }; + + let ser = Response::try_from(resp.clone()).unwrap(); + assert_eq!(VERSION, ser.version); + let de = VersionedResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/mod.rs b/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/mod.rs new file mode 100644 index 0000000000..10698cca2d --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod request; +pub mod response; diff --git a/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/request.rs b/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/request.rs new file mode 100644 index 0000000000..871cc127ef --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/request.rs @@ -0,0 +1,92 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use nym_credentials_interface::CredentialSpendingData; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Request}; + +use super::super::{Error, QueryType, VersionedRequest}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct InnerTopUpRequest { + /// Ecash credential + pub credential: CredentialSpendingData, +} + +impl TryFrom for InnerTopUpRequest { + type Error = Error; + + fn try_from(value: VersionedRequest) -> Result { + match value.query_type { + QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?), + QueryType::AvailableBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::TopupBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedRequest { + type Error = Error; + + fn try_from(value: InnerTopUpRequest) -> Result { + Ok(Self { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerTopUpRequest { + type Error = crate::error::MetadataError; + + fn try_from(value: Request) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Request { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerTopUpRequest) -> Result { + VersionedRequest::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use crate::models::tests::CREDENTIAL_BYTES; + + use super::*; + + #[test] + fn serde() { + let req = InnerTopUpRequest { + credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(), + }; + let ser = VersionedRequest::try_from(req.clone()).unwrap(); + assert_eq!(QueryType::TopupBandwidth, ser.query_type); + let de = InnerTopUpRequest::try_from(ser).unwrap(); + assert_eq!(req, de); + } + + #[test] + fn invalid_content() { + let future_req = VersionedRequest { + query_type: QueryType::TopupBandwidth, + inner: vec![], + }; + assert!(InnerTopUpRequest::try_from(future_req).is_err()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/response.rs b/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/response.rs new file mode 100644 index 0000000000..08e0ef111f --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/models/v1/topup_bandwidth/response.rs @@ -0,0 +1,88 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bincode::Options; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, models::Response}; + +use super::super::{Error, QueryType, VersionedResponse}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct InnerTopUpResponse { + pub available_bandwidth: i64, +} + +impl TryFrom for InnerTopUpResponse { + type Error = Error; + + fn try_from(value: VersionedResponse) -> Result { + match value.query_type { + QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?), + QueryType::AvailableBandwidth => Err(Error::InvalidQueryType { + source_query_type: value.query_type.to_string(), + target_query_type: QueryType::TopupBandwidth.to_string(), + }), + } + } +} + +impl TryFrom for VersionedResponse { + type Error = Error; + + fn try_from(value: InnerTopUpResponse) -> Result { + Ok(Self { + query_type: QueryType::TopupBandwidth, + inner: make_bincode_serializer().serialize(&value)?, + }) + } +} + +impl TryFrom for InnerTopUpResponse { + type Error = crate::error::MetadataError; + + fn try_from(value: Response) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +impl TryFrom for Response { + type Error = crate::error::MetadataError; + + fn try_from(value: InnerTopUpResponse) -> Result { + VersionedResponse::try_from(value)? + .try_into() + .map_err(|err: Error| crate::error::MetadataError::Models { + message: err.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let resp = InnerTopUpResponse { + available_bandwidth: 42, + }; + let ser = VersionedResponse::try_from(resp.clone()).unwrap(); + assert_eq!(QueryType::TopupBandwidth, ser.query_type); + let de = InnerTopUpResponse::try_from(ser).unwrap(); + assert_eq!(resp, de); + } + + #[test] + fn invalid_content() { + let future_resp = VersionedResponse { + query_type: QueryType::TopupBandwidth, + inner: vec![], + }; + assert!(InnerTopUpResponse::try_from(future_resp).is_err()); + } +} diff --git a/common/wireguard-private-metadata/shared/src/routes.rs b/common/wireguard-private-metadata/shared/src/routes.rs new file mode 100644 index 0000000000..bda615fe1c --- /dev/null +++ b/common/wireguard-private-metadata/shared/src/routes.rs @@ -0,0 +1,10 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub const V1_API_VERSION: &str = "v1"; + +pub const BANDWIDTH: &str = "bandwidth"; + +pub const VERSION: &str = "version"; +pub const AVAILABLE: &str = "available"; +pub const TOPUP: &str = "topup"; diff --git a/common/wireguard-private-metadata/tests/Cargo.toml b/common/wireguard-private-metadata/tests/Cargo.toml new file mode 100644 index 0000000000..6827c16f60 --- /dev/null +++ b/common/wireguard-private-metadata/tests/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "nym-wireguard-private-metadata-tests" +version = "1.0.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +async-trait = { workspace = true } +axum = { workspace = true, features = ["tokio", "macros"] } +nym-credential-verification = { path = "../../credential-verification" } +nym-credentials-interface = { path = "../../credentials-interface" } +nym-http-api-client = { path = "../../http-api-client" } +nym-http-api-common = { path = "../../http-api-common", features = [ + "middleware", + "utoipa", + "output", +] } +nym-wireguard = { path = "../../wireguard" } +tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } +tower-http = { workspace = true, features = [ + "cors", + "trace", + "compression-br", + "compression-deflate", + "compression-gzip", + "compression-zstd", +] } +utoipa = { workspace = true, features = ["axum_extras", "time"] } + +nym-wireguard-private-metadata-client = { path = "../client" } +nym-wireguard-private-metadata-shared = { path = "../shared", features = [ + "testing", +] } +nym-wireguard-private-metadata-server = { path = "../server" } + +[lints] +workspace = true diff --git a/common/wireguard-private-metadata/tests/src/lib.rs b/common/wireguard-private-metadata/tests/src/lib.rs new file mode 100644 index 0000000000..c1a981e484 --- /dev/null +++ b/common/wireguard-private-metadata/tests/src/lib.rs @@ -0,0 +1,217 @@ +#[cfg(test)] +mod v0; + +#[cfg(test)] +mod tests { + use std::net::SocketAddr; + + use nym_credential_verification::{ClientBandwidth, TicketVerifier}; + use nym_credentials_interface::CredentialSpendingData; + use nym_http_api_client::Client; + use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE}; + use nym_wireguard_private_metadata_client::WireguardMetadataApiClient; + use nym_wireguard_private_metadata_server::{ + AppState, PeerControllerTransceiver, RouterBuilder, + }; + use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse}; + use tokio::{net::TcpListener, sync::mpsc}; + + pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42; + 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) struct MockVerifier { + ret: i64, + } + + impl MockVerifier { + pub(crate) fn new(ret: i64) -> MockVerifier { + Self { ret } + } + } + + #[async_trait::async_trait] + impl TicketVerifier for MockVerifier { + async fn verify(&mut self) -> nym_credential_verification::Result { + Ok(self.ret) + } + } + + pub(crate) async fn spawn_server_and_create_client() -> Client { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let router = RouterBuilder::with_default_routes() + .with_state(AppState::new(PeerControllerTransceiver::new(request_tx))) + .router; + + tokio::spawn(async move { + loop { + match request_rx.recv().await { + Some(PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx }) => { + response_tx + .send(Ok(ClientBandwidth::new(Default::default()))) + .ok(); + } + Some(PeerControlRequest::GetVerifierByIp { + ip: _, + credential: _, + response_tx, + }) => { + response_tx + .send(Ok(Box::new(MockVerifier::new( + VERIFIER_AVAILABLE_BANDWIDTH, + )))) + .ok(); + } + None => break, + _ => panic!("Not expected"), + } + } + }); + + tokio::spawn(async move { + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .await + .unwrap(); + }); + Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap() + } + + #[tokio::test] + async fn query_latest_version() { + let client = spawn_server_and_create_client().await; + let version = client.version().await.unwrap(); + assert_eq!(version, latest::VERSION); + } + + #[tokio::test] + async fn query_against_server_v0() { + let client = super::v0::network::test::spawn_server_and_create_client().await; + + // version check + let version = client.version().await.unwrap(); + assert_eq!(version, v0::VERSION); + + // v0 reqwests + let request = v0::AvailableBandwidthRequest {}.try_into().unwrap(); + let response = client.available_bandwidth(&request).await.unwrap(); + v0::AvailableBandwidthResponse::try_from(response).unwrap(); + + let request = v0::TopUpRequest {}.try_into().unwrap(); + let response = client.topup_bandwidth(&request).await.unwrap(); + v0::TopUpResponse::try_from(response).unwrap(); + + // v1 reqwests + let request = v1::AvailableBandwidthRequest {}.try_into().unwrap(); + assert!(client.available_bandwidth(&request).await.is_err()); + + let request = v1::TopUpRequest { + credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(), + } + .try_into() + .unwrap(); + assert!(client.topup_bandwidth(&request).await.is_err()); + } + + #[tokio::test] + async fn query_against_server_v1() { + let client = spawn_server_and_create_client().await; + + // version check + let version = client.version().await.unwrap(); + assert_eq!(version, v1::VERSION); + + // v0 reqwests + let request = v0::AvailableBandwidthRequest {}.try_into().unwrap(); + let response = client.available_bandwidth(&request).await.unwrap(); + v0::AvailableBandwidthResponse::try_from(response).unwrap(); + + let request = v0::TopUpRequest {}.try_into().unwrap(); + assert!(client.topup_bandwidth(&request).await.is_err()); + + // v1 reqwests + let request = v1::AvailableBandwidthRequest {}.try_into().unwrap(); + let response = client.available_bandwidth(&request).await.unwrap(); + let available_bandwidth = v1::AvailableBandwidthResponse::try_from(response) + .unwrap() + .available_bandwidth; + assert_eq!(available_bandwidth, 0); + + let request = v1::TopUpRequest { + credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(), + } + .try_into() + .unwrap(); + let response = client.topup_bandwidth(&request).await.unwrap(); + let available_bandwidth = v1::TopUpResponse::try_from(response) + .unwrap() + .available_bandwidth; + assert_eq!(available_bandwidth, VERIFIER_AVAILABLE_BANDWIDTH); + } +} diff --git a/common/wireguard-private-metadata/tests/src/v0/interface.rs b/common/wireguard-private-metadata/tests/src/v0/interface.rs new file mode 100644 index 0000000000..77cd2d12fe --- /dev/null +++ b/common/wireguard-private-metadata/tests/src/v0/interface.rs @@ -0,0 +1,150 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_wireguard_private_metadata_shared::{ + v0 as latest, Construct, Extract, Request, Response, Version, +}; + +pub enum RequestData { + AvailableBandwidth(()), + TopUpBandwidth(()), +} + +impl From for RequestData { + fn from(value: latest::interface::RequestData) -> Self { + match value { + latest::interface::RequestData::AvailableBandwidth(inner) => { + Self::AvailableBandwidth(inner) + } + latest::interface::RequestData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl From for latest::interface::RequestData { + fn from(value: RequestData) -> Self { + match value { + RequestData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner), + RequestData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl From for ResponseData { + fn from(value: latest::interface::ResponseData) -> Self { + match value { + latest::interface::ResponseData::AvailableBandwidth(inner) => { + Self::AvailableBandwidth(inner) + } + latest::interface::ResponseData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl From for latest::interface::ResponseData { + fn from(value: ResponseData) -> Self { + match value { + ResponseData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner), + ResponseData::TopUpBandwidth(credential_spending_data) => { + Self::TopUpBandwidth(credential_spending_data) + } + } + } +} + +impl Construct for Request { + fn construct( + info: RequestData, + version: Version, + ) -> Result { + match version { + Version::V0 => { + let translate_info = latest::interface::RequestData::from(info); + let versioned_request = + latest::VersionedRequest::construct(translate_info, latest::VERSION)?; + Ok(versioned_request.try_into()?) + } + _ => Err( + nym_wireguard_private_metadata_shared::ModelError::DowngradeNotPossible { + from: version, + to: Version::V0, + }, + ), + } + } +} + +impl Extract for Request { + fn extract( + &self, + ) -> Result<(RequestData, Version), nym_wireguard_private_metadata_shared::ModelError> { + match self.version { + Version::V0 => { + let versioned_request = latest::VersionedRequest::try_from(self.clone())?; + let (request, version) = versioned_request.extract()?; + + Ok((request.into(), version)) + } + _ => Err( + nym_wireguard_private_metadata_shared::ModelError::UpdateNotPossible { + from: self.version, + to: Version::V0, + }, + ), + } + } +} + +pub enum ResponseData { + AvailableBandwidth(()), + TopUpBandwidth(()), +} + +impl Construct for Response { + fn construct( + info: ResponseData, + version: Version, + ) -> Result { + match version { + Version::V0 => { + let translate_response = latest::interface::ResponseData::from(info); + let versioned_response = + latest::VersionedResponse::construct(translate_response, version)?; + Ok(versioned_response.try_into()?) + } + _ => Err( + nym_wireguard_private_metadata_shared::ModelError::DowngradeNotPossible { + from: version, + to: Version::V0, + }, + ), + } + } +} + +impl Extract for Response { + fn extract( + &self, + ) -> Result<(ResponseData, Version), nym_wireguard_private_metadata_shared::ModelError> { + match self.version { + Version::V0 => { + let versioned_response = latest::VersionedResponse::try_from(self.clone())?; + let (response, version) = versioned_response.extract()?; + + Ok((response.into(), version)) + } + _ => Err( + nym_wireguard_private_metadata_shared::ModelError::UpdateNotPossible { + from: self.version, + to: Version::V0, + }, + ), + } + } +} diff --git a/common/wireguard-private-metadata/tests/src/v0/mod.rs b/common/wireguard-private-metadata/tests/src/v0/mod.rs new file mode 100644 index 0000000000..7338cc5ae2 --- /dev/null +++ b/common/wireguard-private-metadata/tests/src/v0/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod interface; +pub(crate) mod network; diff --git a/common/wireguard-private-metadata/tests/src/v0/network.rs b/common/wireguard-private-metadata/tests/src/v0/network.rs new file mode 100644 index 0000000000..6a847ecc36 --- /dev/null +++ b/common/wireguard-private-metadata/tests/src/v0/network.rs @@ -0,0 +1,146 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +pub(crate) mod test { + use std::net::SocketAddr; + + use crate::{ + tests::{MockVerifier, VERIFIER_AVAILABLE_BANDWIDTH}, + v0::interface::{RequestData, ResponseData}, + }; + use axum::{extract::Query, Json, Router}; + use nym_credential_verification::ClientBandwidth; + use nym_http_api_client::Client; + use nym_http_api_common::{FormattedResponse, OutputParams}; + use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE}; + use nym_wireguard_private_metadata_server::PeerControllerTransceiver; + use nym_wireguard_private_metadata_shared::ErrorResponse; + use nym_wireguard_private_metadata_shared::{ + v0 as latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response, + }; + use tokio::{net::TcpListener, sync::mpsc}; + use tower_http::compression::CompressionLayer; + + use nym_wireguard_private_metadata_server::AppState; + + fn bandwidth_routes() -> Router { + Router::new() + .route("/version", axum::routing::get(version)) + .route("/available", axum::routing::post(available_bandwidth)) + .route("/topup", axum::routing::post(topup_bandwidth)) + .layer(CompressionLayer::new()) + } + + #[utoipa::path( + tag = "bandwidth", + get, + path = "/v1/bandwidth/version", + responses( + (status = 200, content( + (Response = "application/bincode") + )) + ), +)] + async fn version(Query(output): Query) -> AxumResult> { + let output = output.output.unwrap_or_default(); + Ok(output.to_response(latest::VERSION.into())) + } + + #[utoipa::path( + tag = "bandwidth", + post, + request_body = Request, + path = "/v1/bandwidth/available", + responses( + (status = 200, content( + (Response = "application/bincode") + )) + ), +)] + async fn available_bandwidth( + Query(output): Query, + Json(request): Json, + ) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let (RequestData::AvailableBandwidth(_), version) = + request.extract().map_err(AxumErrorResponse::bad_request)? + else { + return Err(AxumErrorResponse::bad_request("incorrect request type")); + }; + let response = Response::construct(ResponseData::AvailableBandwidth(()), version) + .map_err(AxumErrorResponse::bad_request)?; + + Ok(output.to_response(response)) + } + + #[utoipa::path( + tag = "bandwidth", + post, + request_body = Request, + path = "/v1/bandwidth/topup", + responses( + (status = 200, content( + (Response = "application/bincode") + )) + ), +)] + async fn topup_bandwidth( + Query(output): Query, + Json(request): Json, + ) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let (RequestData::TopUpBandwidth(_), version) = + request.extract().map_err(AxumErrorResponse::bad_request)? + else { + return Err(AxumErrorResponse::bad_request("incorrect request type")); + }; + let response = Response::construct(ResponseData::TopUpBandwidth(()), version) + .map_err(AxumErrorResponse::bad_request)?; + + Ok(output.to_response(response)) + } + + pub(crate) async fn spawn_server_and_create_client() -> Client { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); + let router = Router::new() + .nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes())) + .with_state(AppState::new(PeerControllerTransceiver::new(request_tx))); + + tokio::spawn(async move { + match request_rx.recv().await.unwrap() { + PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => { + response_tx + .send(Ok(ClientBandwidth::new(Default::default()))) + .ok(); + } + PeerControlRequest::GetVerifierByIp { + ip: _, + credential: _, + response_tx, + } => { + response_tx + .send(Ok(Box::new(MockVerifier::new( + VERIFIER_AVAILABLE_BANDWIDTH, + )))) + .ok(); + } + _ => panic!("Not expected"), + } + }); + + tokio::spawn(async move { + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .await + .unwrap(); + }); + Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap() + } +} diff --git a/common/wireguard-types/src/config.rs b/common/wireguard-types/src/config.rs index fda6fc70f3..e322904d2e 100644 --- a/common/wireguard-types/src/config.rs +++ b/common/wireguard-types/src/config.rs @@ -17,9 +17,13 @@ pub struct Config { /// default: `fc01::1` pub private_ipv6: Ipv6Addr, - /// Port announced to external clients wishing to connect to the wireguard interface. + /// Tunnel port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. - pub announced_port: u16, + pub announced_tunnel_port: u16, + + /// Metadata port announced to external clients wishing to connect to the endpoint. + /// Useful in the instances where the node is behind a proxy. + pub announced_metadata_port: u16, /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. /// The maximum value for IPv4 is 32 diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index cfa82a8bda..e98e5fc27d 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -32,9 +32,11 @@ time = { workspace = true } tracing = { workspace = true } nym-authenticator-requests = { path = "../authenticator-requests" } +nym-credentials-interface = { path = "../credentials-interface" } nym-credential-verification = { path = "../credential-verification" } nym-crypto = { path = "../crypto", features = ["asymmetric"] } nym-gateway-storage = { path = "../gateway-storage" } +nym-gateway-requests = { path = "../gateway-requests" } nym-network-defaults = { path = "../network-defaults" } nym-task = { path = "../task" } nym-wireguard-types = { path = "../wireguard-types" } @@ -46,4 +48,3 @@ nym-gateway-storage = { path = "../gateway-storage", features = ["mock"] } [features] default = [] mock = ["nym-gateway-storage/mock"] - diff --git a/common/wireguard/src/error.rs b/common/wireguard/src/error.rs index 1353fba023..d240889d4a 100644 --- a/common/wireguard/src/error.rs +++ b/common/wireguard/src/error.rs @@ -21,3 +21,5 @@ pub enum Error { #[error("{0}")] SystemTime(#[from] std::time::SystemTimeError), } + +pub type Result = std::result::Result; diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 2706e17982..9fe3371fde 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -7,9 +7,9 @@ // #![warn(clippy::unwrap_used)] use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi, WireguardInterfaceApi}; -use nym_crypto::asymmetric::x25519::KeyPair; #[cfg(target_os = "linux")] -use nym_gateway_storage::GatewayStorage; +use nym_credential_verification::ecash::EcashManager; +use nym_crypto::asymmetric::x25519::KeyPair; use nym_wireguard_types::Config; use peer_controller::PeerControlRequest; use std::sync::Arc; @@ -18,11 +18,13 @@ use tokio::sync::mpsc::{self, Receiver, Sender}; #[cfg(target_os = "linux")] use nym_network_defaults::constants::WG_TUN_BASE_NAME; -pub(crate) mod error; +pub mod error; pub mod peer_controller; pub mod peer_handle; pub mod peer_storage_manager; +pub const CONTROL_CHANNEL_SIZE: usize = 256; + pub struct WgApiWrapper { inner: WGApi, } @@ -126,7 +128,7 @@ pub struct WireguardGatewayData { impl WireguardGatewayData { pub fn new(config: Config, keypair: Arc) -> (Self, Receiver) { - let (peer_tx, peer_rx) = mpsc::channel(1); + let (peer_tx, peer_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE); ( WireguardGatewayData { config, @@ -158,7 +160,7 @@ pub struct WireguardData { /// Start wireguard device #[cfg(target_os = "linux")] pub async fn start_wireguard( - storage: GatewayStorage, + ecash_manager: Arc, metrics: nym_node_metrics::NymNodeMetrics, peers: Vec, task_client: nym_task::TaskClient, @@ -167,6 +169,7 @@ pub async fn start_wireguard( use base64::{prelude::BASE64_STANDARD, Engine}; use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi}; use ip_network::IpNetwork; + use nym_credential_verification::ecash::traits::EcashManager; use peer_controller::PeerController; use std::collections::HashMap; use tokio::sync::RwLock; @@ -177,10 +180,16 @@ pub async fn start_wireguard( let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len()); for peer in peers.iter() { - let bandwidth_manager = Arc::new(RwLock::new( - PeerController::generate_bandwidth_manager(Box::new(storage.clone()), &peer.public_key) + let bandwidth_manager = peer_handle::SharedBandwidthStorageManager::new( + Arc::new(RwLock::new( + PeerController::generate_bandwidth_manager( + ecash_manager.storage(), + &peer.public_key, + ) .await?, - )); + )), + peer.allowed_ips.clone(), + ); peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone())); } @@ -189,7 +198,7 @@ pub async fn start_wireguard( name: ifname.clone(), prvkey: BASE64_STANDARD.encode(wireguard_data.inner.keypair().private_key().to_bytes()), address: wireguard_data.inner.config().private_ipv4.to_string(), - port: wireguard_data.inner.config().announced_port as u32, + port: wireguard_data.inner.config().announced_tunnel_port as u32, peers, mtu: None, }; @@ -232,8 +241,9 @@ pub async fn start_wireguard( let host = wg_api.read_interface_data()?; let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api)); + let mut controller = PeerController::new( - Box::new(storage), + ecash_manager, metrics, wg_api.clone(), host, diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index c7b8d5e32b..7da4a2336e 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -9,18 +9,26 @@ use defguard_wireguard_rs::{ use futures::channel::oneshot; use log::info; use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, - ClientBandwidth, + bandwidth_storage_manager::BandwidthStorageManager, ecash::traits::EcashManager, + BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, TicketVerifier, }; +use nym_credentials_interface::CredentialSpendingData; +use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_storage::traits::BandwidthGatewayStorage; 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 std::{ + net::IpAddr, + time::{Duration, SystemTime}, +}; use tokio::sync::{mpsc, RwLock}; use tokio_stream::{wrappers::IntervalStream, StreamExt}; -use crate::{error::Error, peer_handle::SharedBandwidthStorageManager}; +use crate::{ + error::{Error, Result}, + peer_handle::SharedBandwidthStorageManager, +}; use crate::{peer_handle::PeerHandle, peer_storage_manager::CachedPeerManager}; pub enum PeerControlRequest { @@ -36,31 +44,34 @@ pub enum PeerControlRequest { key: Key, response_tx: oneshot::Sender, }, - GetClientBandwidth { + GetClientBandwidthByKey { key: Key, response_tx: oneshot::Sender, }, + GetClientBandwidthByIp { + ip: IpAddr, + response_tx: oneshot::Sender, + }, + GetVerifierByKey { + key: Key, + credential: Box, + response_tx: oneshot::Sender, + }, + GetVerifierByIp { + ip: IpAddr, + credential: Box, + response_tx: oneshot::Sender, + }, } -pub struct AddPeerControlResponse { - pub success: bool, -} - -pub struct RemovePeerControlResponse { - pub success: bool, -} - -pub struct QueryPeerControlResponse { - pub success: bool, - pub peer: Option, -} - -pub struct GetClientBandwidthControlResponse { - pub client_bandwidth: Option, -} +pub type AddPeerControlResponse = Result<()>; +pub type RemovePeerControlResponse = Result<()>; +pub type QueryPeerControlResponse = Result>; +pub type GetClientBandwidthControlResponse = Result; +pub type QueryVerifierControlResponse = Result>; pub struct PeerController { - storage: Box, + ecash_verifier: Arc, // we have "all" metrics of a node, but they're behind a single Arc pointer, // so the overhead is minimal @@ -78,8 +89,8 @@ pub struct PeerController { impl PeerController { #[allow(clippy::too_many_arguments)] - pub fn new( - storage: Box, + pub(crate) fn new( + ecash_verifier: Arc, metrics: NymNodeMetrics, wg_api: Arc, initial_host_information: Host, @@ -114,7 +125,7 @@ impl PeerController { .collect(); PeerController { - storage, + ecash_verifier, wg_api, host_information, bw_storage_managers, @@ -127,8 +138,11 @@ impl PeerController { } // Function that should be used for peer removal, to handle both storage and kernel interaction - pub async fn remove_peer(&mut self, key: &Key) -> Result<(), Error> { - self.storage.remove_wireguard_peer(&key.to_string()).await?; + pub async fn remove_peer(&mut self, key: &Key) -> Result<()> { + self.ecash_verifier + .storage() + .remove_wireguard_peer(&key.to_string()) + .await?; self.bw_storage_managers.remove(key); let ret = self.wg_api.remove_peer(key); if ret.is_err() { @@ -140,7 +154,7 @@ impl PeerController { pub async fn generate_bandwidth_manager( storage: Box, public_key: &Key, - ) -> Result { + ) -> Result { let client_id = storage .get_wireguard_peer(&public_key.to_string()) .await? @@ -161,15 +175,15 @@ impl PeerController { )) } - async fn handle_add_request(&mut self, peer: &Peer) -> Result<(), Error> { + async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> { self.wg_api.configure_peer(peer)?; - let bandwidth_storage_manager = Arc::new(RwLock::new( - Self::generate_bandwidth_manager( - dyn_clone::clone_box(&*self.storage), - &peer.public_key, - ) - .await?, - )); + let bandwidth_storage_manager = SharedBandwidthStorageManager::new( + Arc::new(RwLock::new( + Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key) + .await?, + )), + peer.allowed_ips.clone(), + ); let cached_peer_manager = CachedPeerManager::new(peer); let mut handle = PeerHandle::new( peer.public_key.clone(), @@ -193,21 +207,93 @@ impl PeerController { Ok(()) } - async fn handle_query_peer(&self, key: &Key) -> Result, Error> { + async fn ip_to_key(&self, ip: IpAddr) -> Result> { Ok(self - .storage + .bw_storage_managers + .iter() + .find_map(|(key, bw_manager)| { + bw_manager + .allowed_ips() + .iter() + .find(|ip_mask| ip_mask.ip == ip) + .and(Some(key.clone())) + })) + } + + async fn handle_query_peer_by_key(&self, key: &Key) -> Result> { + Ok(self + .ecash_verifier + .storage() .get_wireguard_peer(&key.to_string()) .await? .map(Peer::try_from) .transpose()?) } - async fn handle_get_client_bandwidth(&self, key: &Key) -> Option { - if let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) { - Some(bandwidth_storage_manager.read().await.client_bandwidth()) - } else { - None - } + async fn handle_get_client_bandwidth_by_key(&self, key: &Key) -> Result { + let bandwidth_storage_manager = self + .bw_storage_managers + .get(key) + .ok_or(Error::MissingClientBandwidthEntry)?; + + Ok(bandwidth_storage_manager + .inner() + .read() + .await + .client_bandwidth()) + } + + async fn handle_get_client_bandwidth_by_ip(&self, ip: IpAddr) -> Result { + let Some(key) = self.ip_to_key(ip).await? else { + return Err(Error::MissingClientKernelEntry(ip.to_string())); + }; + + self.handle_get_client_bandwidth_by_key(&key).await + } + + async fn handle_query_verifier_by_key( + &self, + key: &Key, + credential: CredentialSpendingData, + ) -> Result> { + let storage = self.ecash_verifier.storage(); + let client_id = storage + .get_wireguard_peer(&key.to_string()) + .await? + .ok_or(Error::MissingClientBandwidthEntry)? + .client_id; + let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) else { + return Err(Error::MissingClientBandwidthEntry); + }; + let client_bandwidth = bandwidth_storage_manager + .inner() + .read() + .await + .client_bandwidth(); + let verifier = CredentialVerifier::new( + CredentialSpendingRequest::new(credential), + self.ecash_verifier.clone(), + BandwidthStorageManager::new( + storage, + client_bandwidth, + client_id, + BandwidthFlushingBehaviourConfig::default(), + true, + ), + ); + Ok(Box::new(verifier)) + } + + async fn handle_query_verifier_by_ip( + &self, + ip: IpAddr, + credential: CredentialSpendingData, + ) -> Result> { + let Some(key) = self.ip_to_key(ip).await? else { + return Err(Error::MissingClientKernelEntry(ip.to_string())); + }; + + self.handle_query_verifier_by_key(&key, credential).await } async fn update_metrics(&self, new_host: &Host) { @@ -304,33 +390,29 @@ impl PeerController { msg = self.request_rx.recv() => { match msg { Some(PeerControlRequest::AddPeer { peer, response_tx }) => { - let ret = self.handle_add_request(&peer).await; - if ret.is_ok() { - response_tx.send(AddPeerControlResponse { success: true }).ok(); - } else { - response_tx.send(AddPeerControlResponse { success: false }).ok(); - } + response_tx.send(self.handle_add_request(&peer).await).ok(); } Some(PeerControlRequest::RemovePeer { key, response_tx }) => { - let success = self.remove_peer(&key).await.is_ok(); - response_tx.send(RemovePeerControlResponse { success }).ok(); + response_tx.send(self.remove_peer(&key).await).ok(); } Some(PeerControlRequest::QueryPeer { key, response_tx }) => { - let ret = self.handle_query_peer(&key).await; - if let Ok(peer) = ret { - response_tx.send(QueryPeerControlResponse { success: true, peer }).ok(); - } else { - response_tx.send(QueryPeerControlResponse { success: false, peer: None }).ok(); - } + response_tx.send(self.handle_query_peer_by_key(&key).await).ok(); } - Some(PeerControlRequest::GetClientBandwidth { key, response_tx }) => { - let client_bandwidth = self.handle_get_client_bandwidth(&key).await; - response_tx.send(GetClientBandwidthControlResponse { client_bandwidth }).ok(); + Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => { + response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok(); + } + Some(PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }) => { + response_tx.send(self.handle_get_client_bandwidth_by_ip(ip).await).ok(); + } + Some(PeerControlRequest::GetVerifierByKey { key, credential, response_tx }) => { + response_tx.send(self.handle_query_verifier_by_key(&key, *credential).await).ok(); + } + Some(PeerControlRequest::GetVerifierByIp { ip, credential, response_tx }) => { + response_tx.send(self.handle_query_verifier_by_ip(ip, *credential).await).ok(); } None => { log::trace!("PeerController [main loop]: stopping since channel closed"); break; - } } } @@ -349,21 +431,21 @@ struct MockWgApi { impl WireguardInterfaceApi for MockWgApi { fn create_interface( &self, - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } fn assign_address( &self, _address: &defguard_wireguard_rs::net::IpAddrMask, - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } fn configure_peer_routing( &self, _peers: &[Peer], - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } @@ -371,7 +453,7 @@ impl WireguardInterfaceApi for MockWgApi { fn configure_interface( &self, _config: &defguard_wireguard_rs::InterfaceConfiguration, - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } @@ -380,20 +462,20 @@ impl WireguardInterfaceApi for MockWgApi { &self, _config: &defguard_wireguard_rs::InterfaceConfiguration, _dns: &[std::net::IpAddr], - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } fn remove_interface( &self, - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } fn configure_peer( &self, peer: &Peer, - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { self.peers .write() .unwrap() @@ -404,14 +486,14 @@ impl WireguardInterfaceApi for MockWgApi { fn remove_peer( &self, peer_pubkey: &Key, - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { self.peers.write().unwrap().remove(peer_pubkey); Ok(()) } fn read_interface_data( &self, - ) -> Result { + ) -> std::result::Result { let mut host = Host::default(); host.peers = self.peers.read().unwrap().clone(); Ok(host) @@ -420,7 +502,7 @@ impl WireguardInterfaceApi for MockWgApi { fn configure_dns( &self, _dns: &[std::net::IpAddr], - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } } @@ -433,13 +515,18 @@ pub fn start_controller( Arc>, nym_task::TaskManager, ) { + use std::sync::Arc; + let storage = Arc::new(RwLock::new( nym_gateway_storage::traits::mock::MockGatewayStorage::default(), )); + let ecash_manager = Arc::new(nym_credential_verification::ecash::MockEcashManager::new( + Box::new(storage.clone()), + )); let wg_api = Arc::new(MockWgApi::default()); let task_manager = nym_task::TaskManager::default(); let mut peer_controller = PeerController::new( - Box::new(storage.clone()), + ecash_manager, Default::default(), wg_api, Default::default(), diff --git a/common/wireguard/src/peer_handle.rs b/common/wireguard/src/peer_handle.rs index f6c4673e21..9eda055b2e 100644 --- a/common/wireguard/src/peer_handle.rs +++ b/common/wireguard/src/peer_handle.rs @@ -4,7 +4,7 @@ use crate::error::Error; use crate::peer_controller::PeerControlRequest; use crate::peer_storage_manager::{CachedPeerManager, PeerInformation}; -use defguard_wireguard_rs::{host::Host, key::Key}; +use defguard_wireguard_rs::{host::Host, key::Key, net::IpAddrMask}; use futures::channel::oneshot; use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager; use nym_task::TaskClient; @@ -13,7 +13,28 @@ use std::sync::Arc; use tokio::sync::{mpsc, RwLock}; use tokio_stream::{wrappers::IntervalStream, StreamExt}; -pub(crate) type SharedBandwidthStorageManager = Arc>; +#[derive(Clone)] +pub(crate) struct SharedBandwidthStorageManager { + inner: Arc>, + allowed_ips: Vec, +} + +impl SharedBandwidthStorageManager { + pub(crate) fn new( + inner: Arc>, + allowed_ips: Vec, + ) -> Self { + Self { inner, allowed_ips } + } + + pub(crate) fn inner(&self) -> &RwLock { + &self.inner + } + + pub(crate) fn allowed_ips(&self) -> &[IpAddrMask] { + &self.allowed_ips + } +} pub struct PeerHandle { public_key: Key, @@ -26,7 +47,7 @@ pub struct PeerHandle { } impl PeerHandle { - pub fn new( + pub(crate) fn new( public_key: Key, host_information: Arc>, cached_peer: CachedPeerManager, @@ -62,7 +83,8 @@ impl PeerHandle { let success = response_rx .await .map_err(|_| Error::Internal("peer controller didn't respond".to_string()))? - .success; + .inspect_err(|err| tracing::error!("Could not remove peer: {err:?}")) + .is_ok(); Ok(success) } @@ -119,6 +141,7 @@ impl PeerHandle { if spent_bandwidth > 0 && self .bandwidth_storage_manager + .inner() .write() .await .try_use_bandwidth(spent_bandwidth) @@ -181,7 +204,7 @@ impl PeerHandle { _ = self.task_client.recv() => { log::trace!("PeerHandle: Received shutdown"); - if let Err(e) = self.bandwidth_storage_manager.write().await.sync_storage_bandwidth().await { + if let Err(e) = self.bandwidth_storage_manager.inner().write().await.sync_storage_bandwidth().await { log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed"); } diff --git a/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json b/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json index 8d1b16afe2..8f961e9082 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json +++ b/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json @@ -5,7 +5,7 @@ }, "mixmining_reserve": { "denom": "unym", - "amount": "185782781887190" + "amount": "184339094131786" }, "vesting_tokens": { "denom": "unym", @@ -13,6 +13,6 @@ }, "circulating_supply": { "denom": "unym", - "amount": "814217218112810" + "amount": "815660905868214" } } 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 0b3ee43ca0..758aa1bb47 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 @@ -814_217_218 +815_660_905 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md index 788d05cd23..3982030435 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md @@ -1 +1 @@ -5_160 +5_120 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md index 3dec107d5c..0eec7ac32c 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md @@ -1 +1 @@ -0.73% +0.74% diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md index 322660801f..037b73c86b 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md @@ -1 +1 @@ -39.437 +38.479 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 40e1984ae2..3a671a25d2 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_038_975 +1_040_817 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 a5045462e5..26e9e10529 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 @@ -249_354_023 +249_796_152 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md index a5045462e5..26e9e10529 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md @@ -1 +1 @@ -249_354_023 +249_796_152 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 f723dc5285..4b497bab6f 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 | 185_782_781 | +| Mixmining Reserve | Tokens releasing for operators rewards | 184_339_094 | | Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 | -| Circulating Supply | Amount of unlocked tokens | 814_217_218 | -| Stake Saturation | Optimal size of node self-bond + delegation | 1_038_975 | +| Circulating Supply | Amount of unlocked tokens | 815_660_905 | +| Stake Saturation | Optimal size of node self-bond + delegation | 1_040_817 | diff --git a/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json b/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json index 107af311de..ae2d31a730 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json +++ b/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json @@ -1,10 +1,10 @@ { "interval": { - "reward_pool": "185782781887190.195531020579422623", - "staking_supply": "249354023047048.00261862494755182", + "reward_pool": "184339094131786.145886263466367605", + "staking_supply": "249796152422140.492822331813424919", "staking_supply_scale_factor": "0.30625", - "epoch_reward_budget": "5160632830.199727653639460539", - "stake_saturation_point": "1038975096029.366677577603948132", + "epoch_reward_budget": "5120530392.54961516350731851", + "stake_saturation_point": "1040817301758.918720093049222603", "sybil_resistance": "0.3", "active_set_work_factor": "10", "interval_pool_emission": "0.02" 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 7a436498af..00f2e2d18e 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, July 30th 2025, 09:32:50 UTC \ No newline at end of file +Friday, August 22nd 2025, 10:15:08 UTC \ No newline at end of file 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 1a70eacf1d..29cf2e2826 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 @@ -58,8 +58,8 @@ 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: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=] - --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-tunnel-announced-port + Tunnel 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 diff --git a/documentation/docs/data/csv/isp-sheet.csv b/documentation/docs/data/csv/isp-sheet.csv index 9a39041c20..31ea1cb40c 100644 --- a/documentation/docs/data/csv/isp-sheet.csv +++ b/documentation/docs/data/csv/isp-sheet.csv @@ -41,3 +41,4 @@ [Dataclub](https://www.dataclub.eu/),"Latvia, Sweden, Netherlands",Yes,,,07/2027 [Privex](https://www.privex.io/tor-exit-policy/),"USA, Germany, Sweden",Yes,Yes,,07/2025 [Svea](https://svea.net/vps),Sweden,Yes,,,07/2025 +[Hostraha](https://hostraha.com),"Kenya and other African countries", "No, but advertised otherwise", "Yes, USDT TRC20", "Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times.", 08/2025 diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 1289f1f2c3..b0dcb2ba61 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -39,6 +39,8 @@ export const LoadEndpointInfo = () => ( ); +{/* + # Changelog 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. @@ -47,7 +49,101 @@ This page displays a full list of all the changes during our release cycle from +## `v2025.15-gruyere` +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.15-gruyere) +- [`nym-node`](nodes/nym-node.mdx) version `1.17.0` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2025-08-20T10:37:05.965300480Z +Build Version: 1.17.0 +Commit SHA: 40e1cbc7a9f518eafbb5649c383626b096dd167d +Commit Date: 2025-08-20T12:34:04.000000000+02:00 +Commit Branch: HEAD +rustc Version: 1.86.0 +rustc Channel: stable +cargo Profile: release +``` + +### Operators Updates & Tools + +- [**NIP-3: Nym Exit Policy Update**](https://forum.nym.com/t/nip-3-nym-exit-policy-update/1462/2) resulted by operators [governance](https://governator.nym.com/proposal/prop-d0c0d398-43bd-4a6f-b008-1921b64ae4ed) is implemented. + +- If you operate a node routing wireguard, please re-run [these steps](nodes/nym-node/configuration#wireguard-exit-policy-configuration) to update your wireguard exit policy through IP tables rules. + +- [**NIP-2: Changing stake saturation**](https://governator.nym.com/proposal/prop-8dfa4a28-55b1-45a5-968f-f2897c2670df) will be implemented soon, with that we are going to adjust rules of Delegation Program. **Join [Community call ](https://www.youtube.com/watch?v=KaO6-WLWzMo) on Thursday August 21st, 14:00UTC** to find out more info. + +### Features + +- [Remove old free credential handle](https://github.com/nymtech/nym/pull/5864): And create some traits for unit testing purposes. + +- [Ecash liveness check](https://github.com/nymtech/nym/pull/5890) + +- [Basic zulip client for sending messages](https://github.com/nymtech/nym/pull/5913): In order to be able to send zulip notifications about *emergency* upgrade mode being activated, we need some sort of client. Unfortunately there isn't any rust library that's maintained (the only one had last commit 4 years ago). This simple thing now currently only supports message sending + +- [`nym-node` debug command to reset providers db](https://github.com/nymtech/nym/pull/5914) + +- [Make DNS Resolver fallback optional](https://github.com/nymtech/nym/pull/5920): Default to no dns system fallback, but keep support in the custom hickory dns resolver used for resolving internal domains. + +- [WG exit policy scripts update](https://github.com/nymtech/nym/pull/5921): This PR modifies the scripts `wireguard-exit-policy-manager.sh` and `exit-policy-tests.sh` supporting operators to easily configure and test their IP tables rules in order to have same exit policy for WG as the one for NR, adding the ports decided to be enabled in [NIP-3](https://governator.nym.com/proposal/prop-d0c0d398-43bd-4a6f-b008-1921b64ae4ed) protocol upgrade. + +### Refactors & Maintenance + +- [Allow compatibility with 'CDLA-Permissive-2.0'](https://github.com/nymtech/nym/pull/5910): This license is present in the included `webpki-roots` + +- [Migrate strum to `0.27.2`](https://github.com/nymtech/nym/pull/5960): This PR migrates strum to the latest. Notably all macros' were moved into `strum_macros`. The rest stays the same. + +*/} + +## `v2025.14-feta` + +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.14-feta) +- [`nym-node`](nodes/nym-node.mdx) version `1.16.0` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2025-08-05T09:14:30.322593213Z +Build Version: 1.16.0 +Commit SHA: 7f97f13799342f864e1b106e8cafc9f6d6c24c0f +Commit Date: 2025-07-24T11:00:58.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.86.0 +rustc Channel: stable +cargo Profile: release +``` + +### Operators Updates & Tools + +- Stark Industries is on a sanction list by EU. IP addresses managed by Stark Ind. and their subsidies (ASN 44477 / ASN 33993) had been put on [spamhaus.org](http://spamhaus.org/) [list](https://www.spamhaus.org/drop/asndrop.json). The effect on NymVPN user experience is that Exit Gateways IPs hosted on Stark Ind. are seen as a spam proxies by many online services. + +- We ask operators - especially Exit Gateways - to consider moving to another ISP. Visit an updated [ISP list](community-counsel/isp-list) and feel free to add more providers, following [these steps](community-counsel/add-content). + +### Features + +- [Allow PG database backend](https://github.com/nymtech/nym/pull/5880): + - Added PostgreSQL database support alongside existing SQLite through Cargo feature flags + - Implemented runtime query conversion from SQLite `?` placeholders to PostgreSQL `$1`, `$2`, ... format + - Single codebase now supports both databases without query duplication + +- [Support mnemonic in the NS agent](https://github.com/nymtech/nym/pull/5883) + +- [`sqlx-pool-guard`: allocate more memory on windows](https://github.com/nymtech/nym/pull/5896): + - Allocate 1.5x more memory than reported by the system to provide a safety margin + - Increase number of retry attempts to 5 + + +- [dkg epoch dealers query](https://github.com/nymtech/nym/pull/5899) + +- [dkg snapshot epoch](https://github.com/nymtech/nym/pull/5900): In order to determine if signer quorum has been down at particular height, we need to know with certainty the dkg epoch id corresponding to given block height. This PR makes it possible. Every time epoch state is changed (due to DKG progress), snapshot is saved and can be queried. This doesn't work for past data, but given mainnet has only had a single DKG instance, that's not an issue. + +- [`sqlx-pool-guard`: obtain filename from connect options](https://github.com/nymtech/nym/pull/5905): + +### Refactors & Maintenance + +- [Nym node tokio console](https://github.com/nymtech/nym/pull/5909) ## `v2025.13-emmental` diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 2a53917cb0..451fcac578 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -20,10 +20,10 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](. ```sh nym-node Binary Name: nym-node -Build Timestamp: 2025-07-22T09:24:35.790560275Z -Build Version: 1.15.0 -Commit SHA: 578c9b0567656d86812aa21eb0b4c93b5a7235bd -Commit Date: 2025-07-22T11:09:35.000000000+02:00 +Build Timestamp: 2025-08-20T10:37:05.965300480Z +Build Version: 1.17.0 +Commit SHA: 40e1cbc7a9f518eafbb5649c383626b096dd167d +Commit Date: 2025-08-20T12:34:04.000000000+02:00 Commit Branch: HEAD rustc Version: 1.86.0 rustc Channel: stable diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 33b9cf3c7b..7c93c020d7 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -11,7 +11,7 @@ authors = [ ] description = "Implementation of the Nym Mixnet Gateway" edition = "2021" -rust-version = "1.76" +rust-version = "1.77" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,13 +20,16 @@ path = "src/lib.rs" [dependencies] anyhow = { workspace = true } +bincode = { workspace = true } async-trait = { workspace = true } bip39 = { workspace = true } bs58 = { workspace = true } dashmap = { workspace = true } +fastrand = { workspace = true } futures = { workspace = true } ipnetwork = { workspace = true } rand = { workspace = true } +serde = { workspace = true, features = ["derive"] } sha2 = { workspace = true } thiserror = { workspace = true } time = { workspace = true } @@ -44,8 +47,8 @@ tracing = { workspace = true } url = { workspace = true, features = ["serde"] } zeroize = { workspace = true } + # internal -nym-authenticator = { path = "../service-providers/authenticator" } nym-api-requests = { path = "../nym-api/nym-api-requests" } nym-credentials = { path = "../common/credentials" } nym-credentials-interface = { path = "../common/credentials-interface" } @@ -69,15 +72,19 @@ 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-private-metadata-server = { path = "../common/wireguard-private-metadata/server" } nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } +nym-authenticator-requests = { path = "../common/authenticator-requests" } +nym-client-core = { path = "../common/client-core", features = ["cli"] } +nym-id = { path = "../common/nym-id" } +nym-service-provider-requests-common = { path = "../common/service-provider-requests-common" } + + defguard_wireguard_rs = { workspace = true } -[build-dependencies] -tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -sqlx = { workspace = true, features = [ - "runtime-tokio-rustls", - "sqlite", - "macros", - "migrate", -] } +[dev-dependencies] +nym-gateway-storage = { path = "../common/gateway-storage", features = ["mock"] } +nym-wireguard = { path = "../common/wireguard", features = ["mock"] } +mock_instant = "0.5.3" +time = { workspace = true } diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 62d261a2d3..849f658a26 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_authenticator::error::AuthenticatorError; +use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; use nym_gateway_stats_storage::error::StatsStorageError; use nym_gateway_storage::error::GatewayStorageError; use nym_ip_packet_router::error::IpPacketRouterError; diff --git a/gateway/src/lib.rs b/gateway/src/lib.rs index abac82f783..0664d95761 100644 --- a/gateway/src/lib.rs +++ b/gateway/src/lib.rs @@ -10,3 +10,5 @@ pub mod node; pub use error::GatewayError; pub use node::GatewayTasksBuilder; + +pub use node::internal_service_providers::authenticator as nym_authenticator; diff --git a/gateway/src/node/internal_service_providers/authenticator/config/mod.rs b/gateway/src/node/internal_service_providers/authenticator/config/mod.rs new file mode 100644 index 0000000000..1a1e542368 --- /dev/null +++ b/gateway/src/node/internal_service_providers/authenticator/config/mod.rs @@ -0,0 +1,87 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_network_defaults::{ + WG_METADATA_PORT, WG_TUNNEL_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, + WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6, +}; +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + +pub use nym_client_core::config::Config as BaseClientConfig; +pub use persistence::AuthenticatorPaths; + +pub mod persistence; + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct Config { + #[serde(flatten)] + pub base: BaseClientConfig, + + #[serde(default)] + pub authenticator: Authenticator, + + pub storage_paths: AuthenticatorPaths, +} + +impl Config { + pub fn validate(&self) -> bool { + // no other sections have explicit requirements (yet) + self.base.validate() + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct Authenticator { + /// Socket address this node will use for binding its wireguard interface. + /// default: `0.0.0.0:51822` + pub bind_address: SocketAddr, + + /// Private IP address of the wireguard gateway. + /// default: `10.1.0.1` + pub private_ipv4: Ipv4Addr, + + /// Private IP address of the wireguard gateway. + /// default: `fc01::1` + pub private_ipv6: Ipv6Addr, + + /// Tunnel port announced to external clients wishing to connect to the wireguard interface. + /// Useful in the instances where the node is behind a proxy. + pub tunnel_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, +} + +impl Default for Authenticator { + fn default() -> Self { + Self { + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT), + private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4, + private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6, + tunnel_announced_port: WG_TUNNEL_PORT, + private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4, + private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6, + } + } +} + +impl From for nym_wireguard_types::Config { + fn from(value: Authenticator) -> Self { + nym_wireguard_types::Config { + bind_address: value.bind_address, + private_ipv4: value.private_ipv4, + private_ipv6: value.private_ipv6, + announced_tunnel_port: value.tunnel_announced_port, + announced_metadata_port: WG_METADATA_PORT, + private_network_prefix_v4: value.private_network_prefix_v4, + private_network_prefix_v6: value.private_network_prefix_v6, + } + } +} diff --git a/service-providers/authenticator/src/config/persistence.rs b/gateway/src/node/internal_service_providers/authenticator/config/persistence.rs similarity index 100% rename from service-providers/authenticator/src/config/persistence.rs rename to gateway/src/node/internal_service_providers/authenticator/config/persistence.rs diff --git a/service-providers/authenticator/src/error.rs b/gateway/src/node/internal_service_providers/authenticator/error.rs similarity index 94% rename from service-providers/authenticator/src/error.rs rename to gateway/src/node/internal_service_providers/authenticator/error.rs index 7774fc1e48..ac9b4fd261 100644 --- a/service-providers/authenticator/src/error.rs +++ b/gateway/src/node/internal_service_providers/authenticator/error.rs @@ -80,9 +80,6 @@ pub enum AuthenticatorError { #[error("peers can't be interacted with anymore")] PeerInteractionStopped, - #[error("storage should have the requested bandwidth entry")] - MissingClientBandwidthEntry, - #[error("unknown version number")] UnknownVersion, @@ -104,5 +101,3 @@ pub enum AuthenticatorError { #[error("no credential received")] NoCredentialReceived, } - -pub type Result = std::result::Result; diff --git a/service-providers/authenticator/src/mixnet_client.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_client.rs similarity index 94% rename from service-providers/authenticator/src/mixnet_client.rs rename to gateway/src/node/internal_service_providers/authenticator/mixnet_client.rs index b517ac0ea0..65d9b58e7a 100644 --- a/service-providers/authenticator/src/mixnet_client.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_client.rs @@ -5,7 +5,9 @@ use nym_client_core::{config::disk_persistence::CommonClientPaths, TopologyProvi use nym_sdk::{GatewayTransceiver, NymNetworkDetails}; use nym_task::TaskClient; -use crate::{config::BaseClientConfig, error::AuthenticatorError}; +use crate::node::internal_service_providers::authenticator::{ + config::BaseClientConfig, error::AuthenticatorError, +}; // Helper function to create the mixnet client. // This is NOT in the SDK since we don't want to expose any of the client-core config types. diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs similarity index 90% rename from service-providers/authenticator/src/mixnet_listener.rs rename to gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs index b8a7221d2e..c389055696 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs @@ -7,8 +7,10 @@ use std::{ time::{Duration, SystemTime}, }; -use crate::{config::Config, error::*, seen_credential_cache::SeenCredentialCache}; -use crate::{error::AuthenticatorError, peer_manager::PeerManager}; +use crate::node::internal_service_providers::authenticator::{ + config::Config, error::AuthenticatorError, peer_manager::PeerManager, + seen_credential_cache::SeenCredentialCache, +}; use defguard_wireguard_rs::net::IpAddrMask; use defguard_wireguard_rs::{host::Peer, key::Key}; use futures::StreamExt; @@ -44,7 +46,7 @@ use rand::{prelude::IteratorRandom, thread_rng}; use tokio::sync::RwLock; use tokio_stream::wrappers::IntervalStream; -type AuthenticatorHandleResult = Result<(Vec, Option)>; +type AuthenticatorHandleResult = Result<(Vec, Option), AuthenticatorError>; const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute pub(crate) struct RegistredAndFree { @@ -110,7 +112,7 @@ impl MixnetListener { self.peer_manager.wireguard_gateway_data.keypair() } - async fn remove_stale_registrations(&self) -> Result<()> { + async fn remove_stale_registrations(&self) -> Result<(), AuthenticatorError> { let mut registred_and_free = self.registred_and_free.write().await; let registred_values: Vec<_> = registred_and_free .registration_in_progres @@ -130,7 +132,7 @@ impl MixnetListener { registred_and_free .registration_in_progres .remove(®.gateway_data.pub_key()); - log::debug!( + tracing::debug!( "Removed stale registration of {}", reg.gateway_data.pub_key() ); @@ -146,7 +148,7 @@ impl MixnetListener { registred_and_free .registration_in_progres .remove(®.gateway_data.pub_key()); - log::debug!( + tracing::debug!( "Removed stale registration of {}", reg.gateway_data.pub_key() ); @@ -289,7 +291,7 @@ impl MixnetListener { v1::registration::RegistredData { pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.announced_port, + wg_port: self.config.authenticator.tunnel_announced_port, }, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -302,7 +304,7 @@ impl MixnetListener { v2::registration::RegistredData { pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.announced_port, + wg_port: self.config.authenticator.tunnel_announced_port, }, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -315,7 +317,7 @@ impl MixnetListener { v3::registration::RegistredData { pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.announced_port, + wg_port: self.config.authenticator.tunnel_announced_port, }, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -328,7 +330,7 @@ impl MixnetListener { v4::registration::RegistredData { pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.announced_port, + wg_port: self.config.authenticator.tunnel_announced_port, }, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -341,7 +343,7 @@ impl MixnetListener { v5::registration::RegistredData { pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.announced_port, + wg_port: self.config.authenticator.tunnel_announced_port, }, request_id, ) @@ -372,7 +374,7 @@ impl MixnetListener { let registration_data = RegistrationData { nonce, gateway_data: gateway_data.clone(), - wg_port: self.config.authenticator.announced_port, + wg_port: self.config.authenticator.tunnel_announced_port, }; registred_and_free .registration_in_progres @@ -604,12 +606,12 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let bandwidth_data = self.peer_manager.query_bandwidth(msg.pub_key()).await?; + let available_bandwidth = self.peer_manager.query_bandwidth(msg.pub_key()).await?; let bytes = match AuthenticatorVersion::from(protocol) { AuthenticatorVersion::V1 => { v1::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|data| v1::registration::RemainingBandwidthData { - available_bandwidth: data as u64, + Some(v1::registration::RemainingBandwidthData { + available_bandwidth: available_bandwidth as u64, suspended: false, }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, @@ -622,10 +624,8 @@ impl MixnetListener { } AuthenticatorVersion::V2 => { v2::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|available_bandwidth| { - v2::registration::RemainingBandwidthData { - available_bandwidth, - } + Some(v2::registration::RemainingBandwidthData { + available_bandwidth, }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -637,10 +637,8 @@ impl MixnetListener { } AuthenticatorVersion::V3 => { v3::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|available_bandwidth| { - v3::registration::RemainingBandwidthData { - available_bandwidth, - } + Some(v3::registration::RemainingBandwidthData { + available_bandwidth, }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -652,10 +650,8 @@ impl MixnetListener { } AuthenticatorVersion::V4 => { v4::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|available_bandwidth| { - v4::registration::RemainingBandwidthData { - available_bandwidth, - } + Some(v4::registration::RemainingBandwidthData { + available_bandwidth, }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -667,10 +663,8 @@ impl MixnetListener { } AuthenticatorVersion::V5 => { v5::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|available_bandwidth| { - v5::registration::RemainingBandwidthData { - available_bandwidth, - } + Some(v5::registration::RemainingBandwidthData { + available_bandwidth, }), request_id, ) @@ -691,38 +685,17 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let client_id = self - .ecash_verifier - .storage() - .get_wireguard_peer(&msg.pub_key().to_string()) - .await? - .ok_or(AuthenticatorError::MissingClientBandwidthEntry)? - .client_id; - let client_bandwidth = self - .peer_manager - .query_client_bandwidth(msg.pub_key()) - .await? - .ok_or(AuthenticatorError::MissingClientBandwidthEntry)?; - let available_bandwidth = if self.received_retry(msg.as_ref()) { // don't process the credential and just return the current bandwidth - client_bandwidth.available().await + self.peer_manager.query_bandwidth(msg.pub_key()).await? } else { - let credential = msg.credential(); - let mut verifier = CredentialVerifier::new( - CredentialSpendingRequest::new(credential.clone()), - self.ecash_verifier.clone(), - BandwidthStorageManager::new( - self.ecash_verifier.storage(), - client_bandwidth, - client_id, - BandwidthFlushingBehaviourConfig::default(), - true, - ), - ); + let mut verifier = self + .peer_manager + .query_verifier_by_key(msg.pub_key(), msg.credential()) + .await?; let available_bandwidth = verifier.verify().await?; self.seen_credential_cache - .insert_credential(credential, msg.pub_key()); + .insert_credential(msg.credential(), msg.pub_key()); available_bandwidth }; @@ -777,7 +750,7 @@ impl MixnetListener { &mut self, reconstructed: ReconstructedMessage, ) -> AuthenticatorHandleResult { - log::debug!( + tracing::debug!( "Received message with sender_tag: {:?}", reconstructed.sender_tag ); @@ -830,7 +803,7 @@ impl MixnetListener { response: Vec, recipient: Option, sender_tag: Option, - ) -> Result<()> { + ) -> Result<(), AuthenticatorError> { let input_message = create_input_message(recipient, sender_tag, response)?; self.mixnet_client.send(input_message).await.map_err(|err| { AuthenticatorError::FailedToSendPacketToMixnet { @@ -839,18 +812,18 @@ impl MixnetListener { }) } - pub(crate) async fn run(mut self) -> Result<()> { - log::info!("Using authenticator version {CURRENT_VERSION}"); + pub(crate) async fn run(mut self) -> Result<(), AuthenticatorError> { + tracing::info!("Using authenticator version {CURRENT_VERSION}"); let mut task_client = self.task_handle.fork("main_loop"); while !task_client.is_shutdown() { tokio::select! { _ = task_client.recv() => { - log::debug!("Authenticator [main loop]: received shutdown"); + tracing::debug!("Authenticator [main loop]: received shutdown"); }, _ = self.timeout_check_interval.next() => { if let Err(e) = self.remove_stale_registrations().await { - log::error!("Could not clear stale registrations. The registration process might get jammed soon - {e:?}"); + tracing::error!("Could not clear stale registrations. The registration process might get jammed soon - {e:?}"); } self.seen_credential_cache.remove_stale(); } @@ -860,23 +833,23 @@ impl MixnetListener { match self.on_reconstructed_message(msg).await { Ok((response, recipient)) => { if let Err(err) = self.handle_response(response, recipient, sender_tag).await { - log::error!("Mixnet listener failed to handle response: {err}"); + tracing::error!("Mixnet listener failed to handle response: {err}"); } } Err(err) => { - log::error!("Error handling reconstructed mixnet message: {err}"); + tracing::error!("Error handling reconstructed mixnet message: {err}"); } }; } else { - log::trace!("Authenticator [main loop]: stopping since channel closed"); + tracing::trace!("Authenticator [main loop]: stopping since channel closed"); break; }; }, } } - log::debug!("Authenticator: stopping"); + tracing::debug!("Authenticator: stopping"); Ok(()) } } @@ -884,7 +857,7 @@ impl MixnetListener { pub async fn credential_storage_preparation( ecash_verifier: Arc, client_id: i64, -) -> Result { +) -> Result { ecash_verifier .storage() .create_bandwidth_entry(client_id) @@ -903,7 +876,7 @@ async fn credential_verification( ecash_verifier: Arc, credential: CredentialSpendingData, client_id: i64, -) -> Result { +) -> Result { let bandwidth = credential_storage_preparation(ecash_verifier.clone(), client_id).await?; let client_bandwidth = ClientBandwidth::new(bandwidth.into()); let mut verifier = CredentialVerifier::new( @@ -920,7 +893,9 @@ async fn credential_verification( Ok(verifier.verify().await?) } -fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result { +fn deserialize_request( + reconstructed: &ReconstructedMessage, +) -> Result { let request_version = *reconstructed .message .first_chunk::<2>() @@ -977,7 +952,7 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result { - log::info!("Received packet with invalid version: v{version}"); + tracing::info!("Received packet with invalid version: v{version}"); Err(AuthenticatorError::InvalidPacketVersion(version)) } } @@ -987,11 +962,11 @@ fn create_input_message( nym_address: Option, reply_to_tag: Option, response_packet: Vec, -) -> Result { +) -> Result { let lane = TransmissionLane::General; let packet_type = None; if let Some(reply_to_tag) = reply_to_tag { - log::debug!("Creating message using SURB"); + tracing::debug!("Creating message using SURB"); Ok(InputMessage::new_reply( reply_to_tag, response_packet, @@ -999,7 +974,7 @@ fn create_input_message( packet_type, )) } else if let Some(nym_address) = nym_address { - log::debug!("Creating message using nym_address"); + tracing::debug!("Creating message using nym_address"); Ok(InputMessage::new_regular( nym_address, response_packet, @@ -1007,7 +982,7 @@ fn create_input_message( packet_type, )) } else { - log::error!("No nym-address or sender tag provided"); + tracing::error!("No nym-address or sender tag provided"); Err(AuthenticatorError::MissingReplyToForOldClient) } } diff --git a/service-providers/authenticator/src/authenticator.rs b/gateway/src/node/internal_service_providers/authenticator/mod.rs similarity index 85% rename from service-providers/authenticator/src/authenticator.rs rename to gateway/src/node/internal_service_providers/authenticator/mod.rs index 2790ddddcb..12772b6a30 100644 --- a/service-providers/authenticator/src/authenticator.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mod.rs @@ -1,8 +1,7 @@ -// Copyright 2024 - Nym Technologies SA +// Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::{net::IpAddr, path::Path, sync::Arc, time::SystemTime}; - +use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; use futures::channel::oneshot; use ipnetwork::IpNetwork; use nym_client_core::{HardcodedTopologyProvider, TopologyProvider}; @@ -10,8 +9,16 @@ use nym_credential_verification::ecash::EcashManager; use nym_sdk::{mixnet::Recipient, GatewayTransceiver}; use nym_task::{TaskClient, TaskHandle}; use nym_wireguard::WireguardGatewayData; +use std::{net::IpAddr, path::Path, sync::Arc, time::SystemTime}; -use crate::{config::Config, error::AuthenticatorError}; +pub mod config; +pub mod error; +pub mod mixnet_client; +pub mod mixnet_listener; +mod peer_manager; +mod seen_credential_cache; + +pub use config::Config; pub struct OnStartData { // to add more fields as required @@ -26,7 +33,7 @@ impl OnStartData { pub struct Authenticator { #[allow(unused)] - config: Config, + config: crate::node::internal_service_providers::authenticator::Config, wait_for_gateway: bool, custom_topology_provider: Option>, custom_gateway_transceiver: Option>, @@ -39,7 +46,7 @@ pub struct Authenticator { impl Authenticator { pub fn new( - config: Config, + config: crate::node::internal_service_providers::authenticator::Config, wireguard_gateway_data: WireguardGatewayData, used_private_network_ips: Vec, ecash_verifier: Arc, @@ -119,7 +126,7 @@ impl Authenticator { let task_handle: TaskHandle = self.shutdown.map(Into::into).unwrap_or_default(); // Connect to the mixnet - let mixnet_client = crate::mixnet_client::create_mixnet_client( + let mixnet_client = crate::node::internal_service_providers::authenticator::mixnet_client::create_mixnet_client( &self.config.base, task_handle .get_handle() @@ -129,7 +136,7 @@ impl Authenticator { self.wait_for_gateway, &self.config.storage_paths.common_paths, ) - .await?; + .await?; let self_address = *mixnet_client.nym_address(); @@ -150,7 +157,7 @@ impl Authenticator { } }) .collect(); - let mixnet_listener = crate::mixnet_listener::MixnetListener::new( + let mixnet_listener = crate::node::internal_service_providers::authenticator::mixnet_listener::MixnetListener::new( self.config, free_private_network_ips, self.wireguard_gateway_data, @@ -159,8 +166,8 @@ impl Authenticator { self.ecash_verifier, ); - log::info!("The address of this client is: {self_address}"); - log::info!("All systems go. Press CTRL-C to stop the server."); + tracing::info!("The address of this client is: {self_address}"); + tracing::info!("All systems go. Press CTRL-C to stop the server."); if let Some(on_start) = self.on_start { if on_start.send(OnStartData::new(self_address)).is_err() { diff --git a/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs b/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs new file mode 100644 index 0000000000..fce8cf7174 --- /dev/null +++ b/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs @@ -0,0 +1,469 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; +use defguard_wireguard_rs::{host::Peer, key::Key}; +use futures::channel::oneshot; +use nym_credential_verification::{ClientBandwidth, TicketVerifier}; +use nym_credentials_interface::CredentialSpendingData; +use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData}; +use nym_wireguard_types::PeerPublicKey; + +pub struct PeerManager { + pub(crate) wireguard_gateway_data: WireguardGatewayData, +} + +impl PeerManager { + pub fn new(wireguard_gateway_data: WireguardGatewayData) -> Self { + PeerManager { + wireguard_gateway_data, + } + } + pub async fn add_peer(&mut self, peer: Peer) -> Result<(), AuthenticatorError> { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::AddPeer { peer, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| AuthenticatorError::InternalError("no response for add peer".to_string()))? + .map_err(|err| { + AuthenticatorError::InternalError(format!( + "adding peer could not be performed: {err:?}" + )) + }) + } + + pub async fn _remove_peer(&mut self, pub_key: PeerPublicKey) -> Result<(), AuthenticatorError> { + let key = Key::new(pub_key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::RemovePeer { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| { + AuthenticatorError::InternalError("no response for remove peer".to_string()) + })? + .map_err(|err| { + AuthenticatorError::InternalError(format!( + "removing peer could not be performed: {err:?}" + )) + }) + } + + pub async fn query_peer( + &mut self, + public_key: PeerPublicKey, + ) -> Result, AuthenticatorError> { + let key = Key::new(public_key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::QueryPeer { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| { + AuthenticatorError::InternalError("no response for query peer".to_string()) + })? + .map_err(|err| { + AuthenticatorError::InternalError(format!( + "querying peer could not be performed: {err:?}" + )) + }) + } + + pub async fn query_bandwidth( + &mut self, + public_key: PeerPublicKey, + ) -> Result { + let client_bandwidth = self.query_client_bandwidth(public_key).await?; + Ok(client_bandwidth.available().await) + } + + pub async fn query_client_bandwidth( + &mut self, + key: PeerPublicKey, + ) -> Result { + let key = Key::new(key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| { + AuthenticatorError::InternalError( + "no response for query client bandwidth".to_string(), + ) + })? + .map_err(|err| { + AuthenticatorError::InternalError(format!( + "querying client bandwidth could not be performed: {err:?}" + )) + }) + } + + pub async fn query_verifier_by_key( + &mut self, + key: PeerPublicKey, + credential: CredentialSpendingData, + ) -> Result, AuthenticatorError> { + let key = Key::new(key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::GetVerifierByKey { + key, + credential: Box::new(credential), + response_tx, + }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| { + AuthenticatorError::InternalError("no response for query verifier".to_string()) + })? + .map_err(|err| { + AuthenticatorError::InternalError(format!( + "querying verifier could not be performed: {err:?}" + )) + }) + } +} + +#[cfg(test)] +mod tests { + use std::{str::FromStr, sync::Arc}; + + use nym_credential_verification::{ + bandwidth_storage_manager::BandwidthStorageManager, ecash::MockEcashManager, + }; + use nym_credentials_interface::Bandwidth; + use nym_crypto::asymmetric::x25519::KeyPair; + use nym_gateway_storage::traits::{mock::MockGatewayStorage, BandwidthGatewayStorage}; + use nym_wireguard::peer_controller::{start_controller, stop_controller}; + use rand::rngs::OsRng; + use time::{Duration, OffsetDateTime}; + use tokio::sync::RwLock; + + use crate::nym_authenticator::{ + config::Authenticator, mixnet_listener::credential_storage_preparation, + }; + + use super::*; + + 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, + ]; + + #[tokio::test] + async fn add_peer() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + let peer = Peer::default(); + let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); + + assert!(peer_manager.add_peer(peer.clone()).await.is_err()); + + let client_id = storage + .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) + .await + .unwrap(); + assert!(peer_manager.add_peer(peer.clone()).await.is_err()); + + credential_storage_preparation(Arc::new(ecash_manager), client_id) + .await + .unwrap(); + peer_manager.add_peer(peer.clone()).await.unwrap(); + + stop_controller(task_manager).await; + } + + async fn helper_add_peer( + storage: &Arc>, + peer_manager: &mut PeerManager, + ) -> i64 { + let peer = Peer::default(); + let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); + let client_id = storage + .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) + .await + .unwrap(); + credential_storage_preparation(Arc::new(ecash_manager), client_id) + .await + .unwrap(); + peer_manager.add_peer(peer.clone()).await.unwrap(); + + client_id + } + + #[tokio::test] + async fn remove_peer() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + helper_add_peer(&storage, &mut peer_manager).await; + peer_manager._remove_peer(public_key).await.unwrap(); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn query_peer() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + assert!(peer_manager.query_peer(public_key).await.unwrap().is_none()); + + helper_add_peer(&storage, &mut peer_manager).await; + let peer = peer_manager.query_peer(public_key).await.unwrap().unwrap(); + assert_eq!(peer.public_key, key); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn query_bandwidth() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + assert!(peer_manager.query_bandwidth(public_key).await.is_err()); + + helper_add_peer(&storage, &mut peer_manager).await; + let available_bandwidth = peer_manager.query_bandwidth(public_key).await.unwrap(); + assert_eq!(available_bandwidth, 0); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn query_client_bandwidth() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + assert!(peer_manager + .query_client_bandwidth(public_key) + .await + .is_err()); + + helper_add_peer(&storage, &mut peer_manager).await; + let available_bandwidth = peer_manager + .query_client_bandwidth(public_key) + .await + .unwrap() + .available() + .await; + assert_eq!(available_bandwidth, 0); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn query_verifier() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); + + assert!(peer_manager + .query_verifier_by_key(public_key, credential.clone()) + .await + .is_err()); + + helper_add_peer(&storage, &mut peer_manager).await; + peer_manager + .query_verifier_by_key(public_key, credential) + .await + .unwrap(); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn increase_decrease_bandwidth() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let top_up = 42; + let consume = 4; + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + let client_id = helper_add_peer(&storage, &mut peer_manager).await; + let client_bandwidth = peer_manager + .query_client_bandwidth(public_key) + .await + .unwrap(); + + let mut bw_manager = BandwidthStorageManager::new( + Box::new(storage), + client_bandwidth.clone(), + client_id, + Default::default(), + true, + ); + bw_manager + .increase_bandwidth( + Bandwidth::new_unchecked(top_up as u64), + OffsetDateTime::now_utc() + .checked_add(Duration::minutes(1)) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(client_bandwidth.available().await, top_up); + assert_eq!( + peer_manager.query_bandwidth(public_key).await.unwrap(), + top_up + ); + + bw_manager.try_use_bandwidth(consume).await.unwrap(); + let remaining = top_up - consume; + assert_eq!(client_bandwidth.available().await, remaining); + assert_eq!( + peer_manager.query_bandwidth(public_key).await.unwrap(), + remaining + ); + + stop_controller(task_manager).await; + } +} diff --git a/service-providers/authenticator/src/seen_credential_cache.rs b/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs similarity index 99% rename from service-providers/authenticator/src/seen_credential_cache.rs rename to gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs index 9757f64471..0c2253f7c5 100644 --- a/service-providers/authenticator/src/seen_credential_cache.rs +++ b/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs @@ -55,7 +55,7 @@ impl SeenCredentialCache { let now = SystemTime::now(); self.cached_credentials.retain(|_, value| { let Ok(cache_time) = now.duration_since(value.timestamp) else { - log::warn!("Got decreasing consecutive system timestamps"); + tracing::warn!("Got decreasing consecutive system timestamps"); return false; }; cache_time < SEEN_CREDENTIAL_CACHE_TIME diff --git a/gateway/src/node/internal_service_providers.rs b/gateway/src/node/internal_service_providers/mod.rs similarity index 96% rename from gateway/src/node/internal_service_providers.rs rename to gateway/src/node/internal_service_providers/mod.rs index efa6d28e43..cd4cd051b9 100644 --- a/gateway/src/node/internal_service_providers.rs +++ b/gateway/src/node/internal_service_providers/mod.rs @@ -5,10 +5,10 @@ use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, use crate::node::client_handling::websocket::message_receiver::{ MixMessageReceiver, MixMessageSender, }; +use crate::node::internal_service_providers::authenticator::Authenticator; 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; @@ -22,6 +22,8 @@ use std::fmt::Display; use tokio::task::JoinHandle; use tracing::error; +pub mod authenticator; + pub trait LocalRecipient { fn address(&self) -> Recipient; } @@ -38,7 +40,7 @@ impl LocalRecipient for nym_ip_packet_router::OnStartData { } } -impl LocalRecipient for nym_authenticator::OnStartData { +impl LocalRecipient for authenticator::OnStartData { fn address(&self) -> Recipient { self.address } @@ -78,8 +80,8 @@ impl RunnableServiceProvider for IpPacketRouter { #[async_trait] impl RunnableServiceProvider for Authenticator { const NAME: &'static str = "authenticator"; - type OnStartData = nym_authenticator::OnStartData; - type Error = nym_authenticator::error::AuthenticatorError; + type OnStartData = authenticator::OnStartData; + type Error = authenticator::error::AuthenticatorError; async fn run_service_provider(self) -> Result<(), Self::Error> { self.run_service_provider().await diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 08c5bc886b..a89a68cb23 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -5,10 +5,10 @@ use crate::config::Config; use crate::error::GatewayError; use crate::node::client_handling::websocket; use crate::node::internal_service_providers::{ - ExitServiceProviders, ServiceProviderBeingBuilt, SpMessageRouterBuilder, + authenticator, ExitServiceProviders, ServiceProviderBeingBuilt, SpMessageRouterBuilder, }; +use crate::node::stale_data_cleaner::StaleMessagesCleaner; use futures::channel::oneshot; -use nym_authenticator::Authenticator; use nym_credential_verification::ecash::{ credential_sender::CredentialHandlerConfig, EcashManager, }; @@ -18,7 +18,8 @@ use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_defaults::NymNetworkDetails; use nym_network_requester::NRServiceProviderBuilder; use nym_node_metrics::events::MetricEventsSender; -use nym_task::TaskClient; +use nym_node_metrics::NymNodeMetrics; +use nym_task::{ShutdownToken, TaskClient}; use nym_topology::TopologyProvider; use nym_validator_client::nyxd::{Coin, CosmWasmClient}; use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; @@ -31,10 +32,10 @@ use tracing::*; use zeroize::Zeroizing; pub(crate) mod client_handling; -mod internal_service_providers; +pub(crate) mod internal_service_providers; mod stale_data_cleaner; -use crate::node::stale_data_cleaner::StaleMessagesCleaner; +use crate::node::internal_service_providers::authenticator::Authenticator; pub use client_handling::active_clients::ActiveClientsStore; pub use nym_gateway_stats_storage::PersistentStatsStorage; pub use nym_gateway_storage::{ @@ -42,7 +43,6 @@ pub use nym_gateway_storage::{ traits::{BandwidthGatewayStorage, InboxGatewayStorage}, GatewayStorage, }; -use nym_node_metrics::NymNodeMetrics; pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; #[derive(Debug, Clone)] @@ -61,7 +61,7 @@ pub struct LocalIpPacketRouterOpts { #[derive(Debug, Clone)] pub struct LocalAuthenticatorOpts { - pub config: nym_authenticator::Config, + pub config: authenticator::Config, pub custom_mixnet_path: Option, } @@ -91,7 +91,9 @@ pub struct GatewayTasksBuilder { mnemonic: Arc>, - shutdown: TaskClient, + legacy_task_client: TaskClient, + + shutdown_token: ShutdownToken, // populated and cached as necessary ecash_manager: Option>, @@ -105,7 +107,7 @@ 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(); + self.legacy_task_client.disarm(); } } @@ -119,7 +121,8 @@ impl GatewayTasksBuilder { metrics_sender: MetricEventsSender, metrics: NymNodeMetrics, mnemonic: Arc>, - shutdown: TaskClient, + legacy_task_client: TaskClient, + shutdown_token: ShutdownToken, ) -> GatewayTasksBuilder { GatewayTasksBuilder { config, @@ -133,7 +136,8 @@ impl GatewayTasksBuilder { metrics_sender, metrics, mnemonic, - shutdown, + legacy_task_client, + shutdown_token, ecash_manager: None, wireguard_peers: None, wireguard_networks: None, @@ -228,7 +232,7 @@ impl GatewayTasksBuilder { handler_config, nyxd_client, self.identity_keypair.public_key().to_bytes(), - self.shutdown.fork("ecash_manager"), + self.legacy_task_client.fork("ecash_manager"), self.storage.clone(), ) .await?, @@ -270,7 +274,7 @@ impl GatewayTasksBuilder { self.config.gateway.websocket_bind_address, self.config.debug.maximum_open_connections, shared_state, - self.shutdown.fork("websocket"), + self.legacy_task_client.fork("websocket"), )) } @@ -286,13 +290,14 @@ impl GatewayTasksBuilder { let mut message_router_builder = SpMessageRouterBuilder::new( *self.identity_keypair.public_key(), self.mix_packet_sender.clone(), - self.shutdown.fork("network_requester_message_router"), + self.legacy_task_client + .fork("network_requester_message_router"), ); let transceiver = message_router_builder.gateway_transceiver(); 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_shutdown(self.legacy_task_client.fork("network_requester_sp")) .with_custom_gateway_transceiver(transceiver) .with_wait_for_gateway(true) .with_minimum_gateway_performance(0) @@ -321,13 +326,13 @@ impl GatewayTasksBuilder { let mut message_router_builder = SpMessageRouterBuilder::new( *self.identity_keypair.public_key(), self.mix_packet_sender.clone(), - self.shutdown.fork("ipr_message_router"), + self.legacy_task_client.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_shutdown(self.legacy_task_client.fork("ipr_sp")) .with_custom_gateway_transceiver(Box::new(transceiver)) .with_wait_for_gateway(true) .with_minimum_gateway_performance(0) @@ -427,7 +432,7 @@ impl GatewayTasksBuilder { let mut message_router_builder = SpMessageRouterBuilder::new( *self.identity_keypair.public_key(), self.mix_packet_sender.clone(), - self.shutdown.fork("authenticator_message_router"), + self.legacy_task_client.fork("authenticator_message_router"), ); let transceiver = message_router_builder.gateway_transceiver(); @@ -440,7 +445,7 @@ impl GatewayTasksBuilder { ecash_manager, ) .with_custom_gateway_transceiver(transceiver) - .with_shutdown(self.shutdown.fork("authenticator_sp")) + .with_shutdown(self.legacy_task_client.fork("authenticator_sp")) .with_wait_for_gateway(true) .with_minimum_gateway_performance(0) .with_custom_topology_provider(topology_provider) @@ -460,7 +465,7 @@ impl GatewayTasksBuilder { pub fn build_stale_messages_cleaner(&self) -> StaleMessagesCleaner { StaleMessagesCleaner::new( &self.storage, - self.shutdown.fork("stale_messages_cleaner"), + self.legacy_task_client.fork("stale_messages_cleaner"), self.config.debug.stale_messages_max_age, self.config.debug.stale_messages_cleaner_run_interval, ) @@ -471,13 +476,17 @@ impl GatewayTasksBuilder { &mut self, ) -> Result, Box> { let _ = self.metrics.clone(); + let _ = self.shutdown_token.clone(); unimplemented!("wireguard is not supported on this platform") } #[cfg(target_os = "linux")] pub async fn try_start_wireguard( &mut self, - ) -> Result, Box> { + ) -> Result< + nym_wireguard_private_metadata_server::ShutdownHandles, + Box, + > { let all_peers = self.get_wireguard_peers().await?; let Some(wireguard_data) = self.wireguard_data.take() else { @@ -486,14 +495,49 @@ impl GatewayTasksBuilder { ); }; + let Some(ecash_manager) = self.ecash_manager.clone() else { + return Err( + GatewayError::InternalWireguardError("ecash manager not set".to_string()).into(), + ); + }; + + let router = nym_wireguard_private_metadata_server::RouterBuilder::with_default_routes(); + let router = router.with_state(nym_wireguard_private_metadata_server::AppState::new( + nym_wireguard_private_metadata_server::PeerControllerTransceiver::new( + wireguard_data.inner.peer_tx().clone(), + ), + )); + + let bind_address = std::net::SocketAddr::new( + wireguard_data.inner.config().private_ipv4.into(), + wireguard_data.inner.config().announced_metadata_port, + ); + let wg_handle = nym_wireguard::start_wireguard( - self.storage.clone(), + ecash_manager, self.metrics.clone(), all_peers, - self.shutdown.fork("wireguard"), + self.legacy_task_client.fork("wireguard"), wireguard_data, ) .await?; - Ok(wg_handle) + + let server = router.build_server(&bind_address).await?; + let cancel_token: tokio_util::sync::CancellationToken = (*self.shutdown_token).clone(); + let axum_shutdown_receiver = cancel_token.clone().cancelled_owned(); + let server_handle = tokio::spawn(async move { + { + info!("Started Wireguard Axum HTTP V2 server on {bind_address}"); + server.run(axum_shutdown_receiver).await + } + }); + + let shutdown_handles = nym_wireguard_private_metadata_server::ShutdownHandles::new( + server_handle, + wg_handle, + cancel_token, + ); + + Ok(shutdown_handles) } } diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 018f037dff..cabca429ba 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.64" +version = "1.1.65" authors.workspace = true edition = "2021" rust-version.workspace = true diff --git a/nym-api/migrations/20250805120000_expiration_date_signatures_epoch_fix.sql b/nym-api/migrations/20250805120000_expiration_date_signatures_epoch_fix.sql new file mode 100644 index 0000000000..67cc50a957 --- /dev/null +++ b/nym-api/migrations/20250805120000_expiration_date_signatures_epoch_fix.sql @@ -0,0 +1,51 @@ +/* + * Copyright 2025 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + +-- Change performed in this migration: +-- remove PK on expiration_date and instead use composite (epoch_id, expiration_date) PK + + +CREATE TABLE global_expiration_date_signatures_new +( + expiration_date DATE NOT NULL, + + epoch_id INTEGER NOT NULL, + + -- combined signatures for all tuples issued for given day + serialised_signatures BLOB NOT NULL, + + PRIMARY KEY (epoch_id, expiration_date) +); + +CREATE TABLE partial_expiration_date_signatures_new +( + expiration_date DATE NOT NULL, + + epoch_id INTEGER NOT NULL, + + serialised_signatures BLOB NOT NULL, + + PRIMARY KEY (epoch_id, expiration_date) +); + +-- global +INSERT INTO global_expiration_date_signatures_new +SELECT * +FROM global_expiration_date_signatures; + +DROP TABLE global_expiration_date_signatures; + +ALTER TABLE global_expiration_date_signatures_new + RENAME TO global_expiration_date_signatures; + +-- partial +INSERT INTO partial_expiration_date_signatures_new +SELECT * +FROM partial_expiration_date_signatures; + +DROP TABLE partial_expiration_date_signatures; + +ALTER TABLE partial_expiration_date_signatures_new + RENAME TO partial_expiration_date_signatures; \ No newline at end of file diff --git a/nym-api/nym-api-requests/src/models/described.rs b/nym-api/nym-api-requests/src/models/described.rs index 666cca1996..36ee97be23 100644 --- a/nym-api/nym-api-requests/src/models/described.rs +++ b/nym-api/nym-api-requests/src/models/described.rs @@ -311,7 +311,10 @@ impl From for AuthenticatorDetails { } #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] pub struct WireguardDetails { + // NOTE: the port field is deprecated in favour of tunnel_port pub port: u16, + pub tunnel_port: u16, + pub metadata_port: u16, pub public_key: String, } @@ -320,6 +323,8 @@ impl From for WireguardDetails { fn from(value: Wireguard) -> Self { WireguardDetails { port: value.port, + tunnel_port: value.tunnel_port, + metadata_port: value.metadata_port, public_key: value.public_key, } } diff --git a/nym-api/src/ecash/api_routes/aggregation.rs b/nym-api/src/ecash/api_routes/aggregation.rs index d832022d74..7d61643488 100644 --- a/nym-api/src/ecash/api_routes/aggregation.rs +++ b/nym-api/src/ecash/api_routes/aggregation.rs @@ -12,6 +12,7 @@ use nym_api_requests::ecash::models::{ AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse, }; use nym_api_requests::ecash::VerificationKeyResponse; +use nym_coconut_dkg_common::types::EpochId; use nym_ecash_time::{cred_exp_date, EcashTime}; use nym_http_api_common::{FormattedResponse, Output}; use nym_validator_client::nym_api::rfc_3339_date; @@ -71,6 +72,7 @@ async fn master_verification_key( #[derive(Deserialize, IntoParams)] struct ExpirationDateParam { expiration_date: Option, + epoch_id: Option, output: Option, } @@ -93,6 +95,7 @@ async fn expiration_date_signatures( State(state): State>, Query(ExpirationDateParam { expiration_date, + epoch_id, output, }): Query, ) -> AxumResult> { @@ -108,8 +111,13 @@ async fn expiration_date_signatures( // see if we're not in the middle of new dkg state.ensure_dkg_not_in_progress().await?; + let epoch_id = match epoch_id { + Some(epoch_id) => epoch_id, + None => state.current_dkg_epoch().await?, + }; + let expiration_date_signatures = state - .master_expiration_date_signatures(expiration_date) + .master_expiration_date_signatures(expiration_date, epoch_id) .await?; Ok( diff --git a/nym-api/src/ecash/api_routes/partial_signing.rs b/nym-api/src/ecash/api_routes/partial_signing.rs index b19e938d88..36b71535c8 100644 --- a/nym-api/src/ecash/api_routes/partial_signing.rs +++ b/nym-api/src/ecash/api_routes/partial_signing.rs @@ -13,6 +13,7 @@ use nym_api_requests::ecash::{ BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse, PartialExpirationDateSignatureResponse, }; +use nym_coconut_dkg_common::types::EpochId; use nym_ecash_time::{cred_exp_date, EcashTime}; use nym_http_api_common::{FormattedResponse, Output, OutputParams}; use nym_validator_client::nym_api::rfc_3339_date; @@ -114,6 +115,7 @@ async fn post_blind_sign( #[derive(Deserialize, IntoParams)] struct ExpirationDateParam { expiration_date: Option, + epoch_id: Option, output: Option, } @@ -137,6 +139,7 @@ async fn partial_expiration_date_signatures( State(state): State>, Query(ExpirationDateParam { expiration_date, + epoch_id, output, }): Query, ) -> AxumResult> { @@ -152,8 +155,13 @@ async fn partial_expiration_date_signatures( // see if we're not in the middle of new dkg state.ensure_dkg_not_in_progress().await?; + let epoch_id = match epoch_id { + Some(epoch_id) => epoch_id, + None => state.current_dkg_epoch().await?, + }; + let expiration_date_signatures = state - .partial_expiration_date_signatures(expiration_date) + .partial_expiration_date_signatures(expiration_date, epoch_id) .await?; Ok(output.to_response(PartialExpirationDateSignatureResponse { diff --git a/nym-api/src/ecash/comm.rs b/nym-api/src/ecash/comm.rs index ef5deceb83..4910522a48 100644 --- a/nym-api/src/ecash/comm.rs +++ b/nym-api/src/ecash/comm.rs @@ -83,7 +83,7 @@ impl QueryCommunicationChannel { } } - async fn update_epoch_cache(&self) -> Result> { + async fn update_epoch_cache(&self) -> Result> { let mut guard = self.cached_epoch.write().await; let epoch = ecash::client::Client::get_current_epoch(&self.nyxd_client).await?; diff --git a/nym-api/src/ecash/helpers.rs b/nym-api/src/ecash/helpers.rs index 62b0955331..c636d47157 100644 --- a/nym-api/src/ecash/helpers.rs +++ b/nym-api/src/ecash/helpers.rs @@ -100,7 +100,7 @@ where &self, key: K, f: F, - ) -> Result, EcashError> + ) -> Result, EcashError> where F: FnOnce() -> U, U: Future>, diff --git a/nym-api/src/ecash/keys/mod.rs b/nym-api/src/ecash/keys/mod.rs index bf315f8b1a..e7e8bf94c6 100644 --- a/nym-api/src/ecash/keys/mod.rs +++ b/nym-api/src/ecash/keys/mod.rs @@ -65,13 +65,13 @@ impl KeyPair { } } - pub async fn keys(&self) -> Result, EcashError> { + pub async fn keys(&self) -> Result, EcashError> { let keypair_guard = self.get().await.ok_or(EcashError::KeyPairNotDerivedYet)?; RwLockReadGuard::try_map(keypair_guard, |keypair| keypair.as_ref()) .map_err(|_| EcashError::KeyPairNotDerivedYet) } - pub async fn signing_key(&self) -> Result, EcashError> { + pub async fn signing_key(&self) -> Result, EcashError> { let keypair_guard = self.get().await.ok_or(EcashError::KeyPairNotDerivedYet)?; RwLockReadGuard::try_map(keypair_guard, |keypair| { @@ -80,7 +80,7 @@ impl KeyPair { .map_err(|_| EcashError::KeyPairNotDerivedYet) } - pub async fn verification_key(&self) -> Option> { + pub async fn verification_key(&self) -> Option> { RwLockReadGuard::try_map(self.get().await?, |maybe_keys| { maybe_keys.as_ref().map(|k| k.keys.verification_key_ref()) }) diff --git a/nym-api/src/ecash/state/global.rs b/nym-api/src/ecash/state/global.rs index 24d1ff00ff..d1e94d4cae 100644 --- a/nym-api/src/ecash/state/global.rs +++ b/nym-api/src/ecash/state/global.rs @@ -5,6 +5,7 @@ use crate::ecash::helpers::{ CachedImmutableEpochItem, CachedImmutableItems, IssuedCoinIndicesSignatures, IssuedExpirationDateSignatures, }; +use nym_coconut_dkg_common::types::EpochId; use nym_compact_ecash::VerificationKeyAuth; use nym_validator_client::nyxd::AccountId; use time::Date; @@ -18,7 +19,7 @@ pub(crate) struct GlobalEcachState { pub(crate) coin_index_signatures: CachedImmutableEpochItem, pub(crate) expiration_date_signatures: - CachedImmutableItems, + CachedImmutableItems<(EpochId, Date), IssuedExpirationDateSignatures>, } impl GlobalEcachState { diff --git a/nym-api/src/ecash/state/local.rs b/nym-api/src/ecash/state/local.rs index b9e54ec711..3563825942 100644 --- a/nym-api/src/ecash/state/local.rs +++ b/nym-api/src/ecash/state/local.rs @@ -9,6 +9,7 @@ use crate::ecash::helpers::{ use crate::ecash::keys::KeyPair; use crate::ecash::storage::models::IssuedHash; use nym_api_requests::ecash::models::{CommitedDeposit, DepositId}; +use nym_coconut_dkg_common::types::EpochId; use nym_crypto::asymmetric::ed25519; use nym_ticketbooks_merkle::{ IssuedTicketbook, IssuedTicketbooksFullMerkleProof, IssuedTicketbooksMerkleTree, MerkleLeaf, @@ -143,7 +144,7 @@ pub(crate) struct LocalEcashState { pub(crate) partial_coin_index_signatures: CachedImmutableEpochItem, pub(crate) partial_expiration_date_signatures: - CachedImmutableItems, + CachedImmutableItems<(EpochId, Date), IssuedExpirationDateSignatures>, // merkle trees for ticketbooks issued for particular expiration dates pub(crate) issued_merkle_trees: Arc>>, diff --git a/nym-api/src/ecash/state/mod.rs b/nym-api/src/ecash/state/mod.rs index 644a4b32fa..a4d8a39f46 100644 --- a/nym-api/src/ecash/state/mod.rs +++ b/nym-api/src/ecash/state/mod.rs @@ -198,21 +198,21 @@ impl EcashState { Ok(()) } - pub(crate) async fn ecash_signing_key(&self) -> Result> { + pub(crate) async fn ecash_signing_key(&self) -> Result> { self.local.ecash_keypair.signing_key().await } #[allow(dead_code)] pub(crate) async fn current_master_verification_key( &self, - ) -> Result> { + ) -> Result> { self.master_verification_key(None).await } pub(crate) async fn master_verification_key( &self, epoch_id: Option, - ) -> Result> { + ) -> Result> { let epoch_id = match epoch_id { Some(id) => id, None => self.aux.current_epoch().await?, @@ -258,7 +258,7 @@ impl EcashState { pub(crate) async fn master_coin_index_signatures( &self, epoch_id: Option, - ) -> Result> { + ) -> Result> { let epoch_id = match epoch_id { Some(id) => id, None => self.aux.current_epoch().await?, @@ -344,7 +344,7 @@ impl EcashState { pub(crate) async fn partial_coin_index_signatures( &self, epoch_id: Option, - ) -> Result> { + ) -> Result> { let epoch_id = match epoch_id { Some(id) => id, None => self.aux.current_epoch().await?, @@ -401,10 +401,11 @@ impl EcashState { pub(crate) async fn master_expiration_date_signatures( &self, expiration_date: Date, - ) -> Result> { + epoch_id: EpochId, + ) -> Result> { self.global .expiration_date_signatures - .get_or_init(expiration_date, || async { + .get_or_init((epoch_id, expiration_date), || async { // 1. sanity check to see if the expiration_date is not nonsense ensure_sane_expiration_date(expiration_date)?; @@ -412,7 +413,7 @@ impl EcashState { if let Some(master_sigs) = self .aux .storage - .get_master_expiration_date_signatures(expiration_date) + .get_master_expiration_date_signatures(expiration_date, epoch_id) .await? { return Ok(master_sigs); @@ -435,13 +436,16 @@ impl EcashState { // check if we're attempting to query ourselves, in that case just get local signature // rather than making the http query let partial = if Some(api.cosmos_address) == cosmos_address { - self.partial_expiration_date_signatures(expiration_date) + self.partial_expiration_date_signatures(expiration_date, epoch_id) .await? .signatures .clone() } else { api.api_client - .partial_expiration_date_signatures(Some(expiration_date)) + .partial_expiration_date_signatures( + Some(expiration_date), + Some(epoch_id), + ) .await? .signatures }; @@ -480,10 +484,11 @@ impl EcashState { pub(crate) async fn partial_expiration_date_signatures( &self, expiration_date: Date, - ) -> Result> { + epoch_id: EpochId, + ) -> Result> { self.local .partial_expiration_date_signatures - .get_or_init(expiration_date, || async { + .get_or_init((epoch_id, expiration_date), || async { // 1. sanity check to see if the expiration_date is not nonsense ensure_sane_expiration_date(expiration_date)?; @@ -491,7 +496,7 @@ impl EcashState { if let Some(partial_sigs) = self .aux .storage - .get_partial_expiration_date_signatures(expiration_date) + .get_partial_expiration_date_signatures(expiration_date, epoch_id) .await? { return Ok(partial_sigs); @@ -721,7 +726,7 @@ impl EcashState { async fn get_updated_merkle_read( &self, expiration_date: Date, - ) -> Result> { + ) -> Result> { let write_guard = self.get_updated_full_write(expiration_date).await?; // SAFETY: the entry was either not empty or we just inserted data in there, whilst never dropping the lock @@ -735,7 +740,7 @@ impl EcashState { async fn get_updated_full_write( &self, expiration_date: Date, - ) -> Result>> { + ) -> Result>> { let mut write_guard = self.local.issued_merkle_trees.write().await; // double check if it's still empty in case another task has already grabbed the write lock and performed the update diff --git a/nym-api/src/ecash/storage/manager.rs b/nym-api/src/ecash/storage/manager.rs index e714600ef6..52b5940f04 100644 --- a/nym-api/src/ecash/storage/manager.rs +++ b/nym-api/src/ecash/storage/manager.rs @@ -128,6 +128,7 @@ pub trait EcashStorageManagerExt { async fn get_partial_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: i64, ) -> Result, sqlx::Error>; async fn insert_partial_expiration_date_signatures( &self, @@ -139,6 +140,7 @@ pub trait EcashStorageManagerExt { async fn get_master_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: i64, ) -> Result, sqlx::Error>; async fn insert_master_expiration_date_signatures( &self, @@ -501,15 +503,17 @@ impl EcashStorageManagerExt for StorageManager { async fn get_partial_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: i64, ) -> Result, sqlx::Error> { sqlx::query_as!( RawExpirationDateSignatures, r#" SELECT epoch_id as "epoch_id: u32", serialised_signatures FROM partial_expiration_date_signatures - WHERE expiration_date = ? + WHERE expiration_date = ? AND epoch_id = ? "#, - expiration_date + expiration_date, + epoch_id ) .fetch_optional(&self.connection_pool) .await @@ -535,15 +539,17 @@ impl EcashStorageManagerExt for StorageManager { async fn get_master_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: i64, ) -> Result, sqlx::Error> { sqlx::query_as!( RawExpirationDateSignatures, r#" SELECT epoch_id as "epoch_id: u32", serialised_signatures FROM global_expiration_date_signatures - WHERE expiration_date = ? + WHERE expiration_date = ? AND epoch_id = ? "#, - expiration_date + expiration_date, + epoch_id ) .fetch_optional(&self.connection_pool) .await diff --git a/nym-api/src/ecash/storage/mod.rs b/nym-api/src/ecash/storage/mod.rs index 197affb6a8..1131b42ccb 100644 --- a/nym-api/src/ecash/storage/mod.rs +++ b/nym-api/src/ecash/storage/mod.rs @@ -143,6 +143,7 @@ pub trait EcashStorageExt { async fn get_partial_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: EpochId, ) -> Result, NymApiStorageError>; async fn insert_partial_expiration_date_signatures( @@ -154,6 +155,7 @@ pub trait EcashStorageExt { async fn get_master_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: EpochId, ) -> Result, NymApiStorageError>; async fn insert_master_expiration_date_signatures( @@ -456,10 +458,11 @@ impl EcashStorageExt for NymApiStorage { async fn get_partial_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: EpochId, ) -> Result, NymApiStorageError> { let Some(raw) = self .manager - .get_partial_expiration_date_signatures(expiration_date) + .get_partial_expiration_date_signatures(expiration_date, epoch_id as i64) .await? else { return Ok(None); @@ -491,10 +494,11 @@ impl EcashStorageExt for NymApiStorage { async fn get_master_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: EpochId, ) -> Result, NymApiStorageError> { let Some(raw) = self .manager - .get_master_expiration_date_signatures(expiration_date) + .get_master_expiration_date_signatures(expiration_date, epoch_id as i64) .await? else { return Ok(None); diff --git a/nym-api/src/epoch_operations/helpers.rs b/nym-api/src/epoch_operations/helpers.rs index f28021058c..5f1e5a2f6b 100644 --- a/nym-api/src/epoch_operations/helpers.rs +++ b/nym-api/src/epoch_operations/helpers.rs @@ -5,12 +5,16 @@ use crate::epoch_operations::EpochAdvancer; use crate::support::caching::Cache; use cosmwasm_std::{Decimal, Fraction}; use nym_api_requests::models::NodeAnnotation; +use nym_mixnet_contract_common::helpers::IntoBaseDecimal; use nym_mixnet_contract_common::reward_params::{NodeRewardingParameters, Performance, WorkFactor}; -use nym_mixnet_contract_common::{EpochRewardedSet, ExecuteMsg, NodeId, RewardingParams}; +use nym_mixnet_contract_common::{ + EpochRewardedSet, ExecuteMsg, NodeId, RewardedSet, RewardingParams, +}; use serde::{Deserialize, Serialize}; +use std::cmp::max; use std::collections::HashMap; use tokio::sync::RwLockReadGuard; -use tracing::error; +use tracing::{debug, error}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub(crate) struct NodeWithPerformance { @@ -73,9 +77,140 @@ pub(super) fn stake_to_f64(stake: Decimal) -> f64 { } } +struct PerNodeWork { + active: WorkFactor, + standby: WorkFactor, +} + +struct NodeWorkCalculationComponents { + active_set_size: Decimal, + standby_set_size: Decimal, + per_node_work: PerNodeWork, +} + +impl NodeWorkCalculationComponents { + fn standby_set_work_share(&self) -> Decimal { + self.standby_set_size * self.per_node_work.standby + } + + fn active_set_work_share(&self) -> Decimal { + self.active_set_size * self.per_node_work.active + } +} + +fn default_node_work_calculation( + nodes: &RewardedSet, + global_rewarding_params: RewardingParams, +) -> NodeWorkCalculationComponents { + let per_node_work = PerNodeWork { + active: global_rewarding_params.active_node_work(), + standby: global_rewarding_params.standby_node_work(), + }; + // SANITY CHECK: + // SAFETY: 0 decimal places is within the range of `Decimal` + #[allow(clippy::unwrap_used)] + let standby_set_size = Decimal::from_atomics(nodes.standby.len() as u128, 0).unwrap(); + #[allow(clippy::unwrap_used)] + let active_set_size = Decimal::from_atomics(nodes.active_set_size() as u128, 0).unwrap(); + + NodeWorkCalculationComponents { + active_set_size, + standby_set_size, + per_node_work, + } +} + +fn manual_node_work_calculation( + nodes: &RewardedSet, + global_rewarding_params: RewardingParams, +) -> NodeWorkCalculationComponents { + // calculate everything manually based on the actual rewarded set on hand + // but always attempt to minimise the node work, so take the maximum values + // of the set sizes between new and old parameters + // (more nodes = smaller per-node work as it has to be spread through more entries) + let rewarded_set_size = max( + global_rewarding_params.rewarded_set.rewarded_set_size(), + nodes.rewarded_set_size() as u32, + ); + let standby_set_size = max( + global_rewarding_params.rewarded_set.standby, + nodes.standby_set_size() as u32, + ); + // the unwraps here are fine as we're guaranteed an `u32` is going to fit in a Decimal with 0 decimal places + #[allow(clippy::unwrap_used)] + let rewarded_set_size_dec = rewarded_set_size.into_base_decimal().unwrap(); + #[allow(clippy::unwrap_used)] + let standby_set_size_dec = standby_set_size.into_base_decimal().unwrap(); + #[allow(clippy::unwrap_used)] + let active_set_size = rewarded_set_size + .saturating_sub(standby_set_size) + .into_base_decimal() + .unwrap(); + + let standby_node_work = global_rewarding_params + .interval + .standby_node_work(rewarded_set_size_dec, standby_set_size_dec); + let active_node_work = global_rewarding_params + .interval + .active_node_work(standby_node_work); + let per_node_work = PerNodeWork { + active: active_node_work, + standby: standby_node_work, + }; + + NodeWorkCalculationComponents { + active_set_size, + standby_set_size: standby_set_size_dec, + per_node_work, + } +} + +fn determine_per_node_work( + nodes: &RewardedSet, + // we only need reward parameters for active set work factor and rewarded/active set sizes; + // we do not need exact values of reward pool, staking supply, etc., so it's fine if it's slightly out of sync + global_rewarding_params: RewardingParams, +) -> PerNodeWork { + // currently we are using constant omega for nodes, but that will change with tickets + // or different reward split between entry, exit, etc. at that point this will have to be calculated elsewhere + let res = if nodes.matches_parameters(global_rewarding_params.rewarded_set) { + default_node_work_calculation(nodes, global_rewarding_params) + } else { + error!("the current rewarded set does not much current rewarding parameters. this could only be expected if rewarded set distribution has been changed mid-epoch"); + manual_node_work_calculation(nodes, global_rewarding_params) + }; + + let active_node_work_factor = res.per_node_work.active; + let standby_node_work_factor = res.per_node_work.standby; + + debug!("using {active_node_work_factor} as active node work factor and {standby_node_work_factor} as standby node work factor"); + + let standby_share = res.standby_set_work_share(); + let active_share = res.active_set_work_share(); + let total_work = standby_share + active_share; + + // this HAS TO blow up. there's no recovery + #[allow(clippy::panic)] + if total_work > Decimal::one() { + panic!("work calculation logic is flawed! somehow the total work in the system is greater than 1! \ + total work={total_work}, \ + active set share={active_share}, \ + standby share={standby_share}, \ + active node work factor={active_node_work_factor}, \ + standby node work factor={standby_node_work_factor}, \ + active set size={} \ + standby set size={}", res.active_set_size, res.standby_set_size); + } + + PerNodeWork { + active: active_node_work_factor, + standby: standby_node_work_factor, + } +} + impl EpochAdvancer { fn load_performance( - status_cache: &Option>>>, + status_cache: &Option>>>, node_id: NodeId, ) -> NodeWithPerformance { let Some(status_cache) = status_cache.as_ref() else { @@ -99,23 +234,9 @@ impl EpochAdvancer { global_rewarding_params: RewardingParams, ) -> Vec { let nodes = &nodes.assignment; - // currently we are using constant omega for nodes, but that will change with tickets - // or different reward split between entry, exit, etc. at that point this will have to be calculated elsewhere - let active_node_work_factor = global_rewarding_params.active_node_work(); - let standby_node_work_factor = global_rewarding_params.standby_node_work(); - - // SANITY CHECK: - // SAFETY: 0 decimal places is within the range of `Decimal` - #[allow(clippy::unwrap_used)] - let standby_share = Decimal::from_atomics(nodes.standby.len() as u128, 0).unwrap() - * standby_node_work_factor; - #[allow(clippy::unwrap_used)] - let active_share = Decimal::from_atomics(nodes.active_set_size() as u128, 0).unwrap() - * active_node_work_factor; - let total_work = standby_share + active_share; - - // this HAS TO blow up. there's no recovery - assert!(total_work <= Decimal::one(), "work calculation logic is flawed! somehow the total work in the system is greater than 1!"); + let nodes_work = determine_per_node_work(nodes, global_rewarding_params); + let active_node_work_factor = nodes_work.active; + let standby_node_work_factor = nodes_work.standby; let status_cache = self.status_cache.node_annotations().await; if status_cache.is_none() { @@ -161,6 +282,9 @@ impl EpochAdvancer { #[cfg(test)] mod tests { use super::*; + use nym_contracts_common::Percent; + use nym_mixnet_contract_common::reward_params::RewardedSetParams; + use nym_mixnet_contract_common::IntervalRewardParams; fn compare_large_floats(a: f64, b: f64) { // for very large floats, allow for smaller larger epsilon @@ -206,4 +330,72 @@ mod tests { compare_large_floats(expected_f64, stake_to_f64(decimal)) } } + + fn dummy_rewarding_params() -> RewardingParams { + RewardingParams { + interval: IntervalRewardParams { + reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(), + staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(), + staking_supply_scale_factor: Percent::hundred(), + epoch_reward_budget: Decimal::from_ratio(100_000_000_000_000u128, 1234u32) + * Decimal::percent(1), + stake_saturation_point: Decimal::from_ratio(123_456_000_000_000u128, 313u32), + sybil_resistance: Percent::from_percentage_value(23).unwrap(), + active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(), + interval_pool_emission: Percent::from_percentage_value(1).unwrap(), + }, + rewarded_set: RewardedSetParams { + entry_gateways: 50, + exit_gateways: 70, + mixnodes: 120, + standby: 20, + }, + } + } + + #[test] + fn determining_nodes_work() { + let params = dummy_rewarding_params(); + // matched parameters + let rewarded_set = RewardedSet { + entry_gateways: (1..) + .take(params.rewarded_set.entry_gateways as usize) + .collect(), + exit_gateways: (1000..) + .take(params.rewarded_set.exit_gateways as usize) + .collect(), + layer1: (2000..) + .take(params.rewarded_set.mixnodes as usize / 3) + .collect(), + layer2: (3000..) + .take(params.rewarded_set.mixnodes as usize / 3) + .collect(), + layer3: (4000..) + .take(params.rewarded_set.mixnodes as usize / 3) + .collect(), + standby: (5000..) + .take(params.rewarded_set.standby as usize) + .collect(), + }; + + let work = determine_per_node_work(&rewarded_set, params); + assert_eq!(work.active, params.active_node_work()); + assert_eq!(work.standby, params.standby_node_work()); + + // updated + // here we're interested in the fact that the calculation does not panic, i.e. total work <= 1 + let params = dummy_rewarding_params(); + let rewarded_set = RewardedSet { + entry_gateways: (1..).take(250).collect(), + exit_gateways: (1000..).take(100).collect(), + layer1: (2000..).take(10).collect(), + layer2: (3000..).take(10).collect(), + layer3: (4000..).take(10).collect(), + standby: (5000..).take(5).collect(), + }; + + let work = determine_per_node_work(&rewarded_set, params); + assert_ne!(work.active, params.active_node_work()); + assert_ne!(work.standby, params.standby_node_work()); + } } diff --git a/nym-api/src/mixnet_contract_cache/cache/mod.rs b/nym-api/src/mixnet_contract_cache/cache/mod.rs index 39915845e8..9279c989ec 100644 --- a/nym-api/src/mixnet_contract_cache/cache/mod.rs +++ b/nym-api/src/mixnet_contract_cache/cache/mod.rs @@ -66,7 +66,7 @@ impl MixnetContractCache { pub async fn all_cached_legacy_mixnodes( &self, - ) -> Option>> { + ) -> Option>> { self.get(|c| &c.legacy_mixnodes).await.ok() } @@ -84,11 +84,11 @@ impl MixnetContractCache { pub async fn all_cached_legacy_gateways( &self, - ) -> Option>> { + ) -> Option>> { self.get(|c| &c.legacy_gateways).await.ok() } - pub async fn all_cached_nym_nodes(&self) -> Option>> { + pub async fn all_cached_nym_nodes(&self) -> Option>> { self.get(|c| &c.nym_nodes).await.ok() } @@ -125,7 +125,7 @@ impl MixnetContractCache { Ok(Cache::as_mapped(&cache, |c| c.rewarded_set.clone())) } - pub async fn rewarded_set(&self) -> Option> { + pub async fn rewarded_set(&self) -> Option> { self.get(|c| &c.rewarded_set).await.ok() } diff --git a/nym-api/src/node_status_api/cache/mod.rs b/nym-api/src/node_status_api/cache/mod.rs index 73aca9c848..8791341b7a 100644 --- a/nym-api/src/node_status_api/cache/mod.rs +++ b/nym-api/src/node_status_api/cache/mod.rs @@ -112,7 +112,7 @@ impl NodeStatusCache { pub(crate) async fn node_annotations( &self, - ) -> Option>>> { + ) -> Option>>> { self.get(|c| &c.node_annotations).await } @@ -127,7 +127,7 @@ impl NodeStatusCache { pub(crate) async fn annotated_legacy_mixnodes( &self, - ) -> Option>>> { + ) -> Option>>> { self.get(|c| &c.mixnodes_annotated).await } @@ -150,7 +150,7 @@ impl NodeStatusCache { pub(crate) async fn annotated_legacy_gateways( &self, - ) -> Option>>> { + ) -> Option>>> { self.get(|c| &c.gateways_annotated).await } diff --git a/nym-api/src/node_status_api/helpers.rs b/nym-api/src/node_status_api/helpers.rs index 91076e5963..e4d6257ace 100644 --- a/nym-api/src/node_status_api/helpers.rs +++ b/nym-api/src/node_status_api/helpers.rs @@ -8,31 +8,13 @@ use crate::{MixnetContractCache, NodeStatusCache}; use nym_api_requests::models::{ ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, GatewayUptimeResponse, - MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatus, MixnodeStatusReportResponse, + MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, }; use nym_mixnet_contract_common::rewarding::RewardEstimate; use nym_mixnet_contract_common::NodeId; -pub(crate) enum RewardedSetStatus { - Active, - Standby, - Inactive, -} - -impl From for RewardedSetStatus { - fn from(value: MixnodeStatus) -> Self { - match value { - MixnodeStatus::Active => RewardedSetStatus::Active, - MixnodeStatus::Standby => RewardedSetStatus::Standby, - // for all intents and purposes, missing node is treated as inactive for rewarding (since it wouldn't get anything - MixnodeStatus::Inactive => RewardedSetStatus::Inactive, - MixnodeStatus::NotFound => RewardedSetStatus::Inactive, - } - } -} - async fn gateway_identity_to_node_id( cache: &NodeStatusCache, identity: &str, diff --git a/nym-api/src/support/http/state/mod.rs b/nym-api/src/support/http/state/mod.rs index 7a9bb8abb9..bad1b7f4ed 100644 --- a/nym-api/src/support/http/state/mod.rs +++ b/nym-api/src/support/http/state/mod.rs @@ -148,7 +148,7 @@ impl AppState { impl AppState { pub(crate) async fn describe_nodes_cache_data( &self, - ) -> Result>, AxumErrorResponse> { + ) -> Result>, AxumErrorResponse> { Ok(self.described_nodes_cache().get().await?) } @@ -160,7 +160,8 @@ impl AppState { pub(crate) async fn node_annotations( &self, - ) -> Result>>, AxumErrorResponse> { + ) -> Result>>, AxumErrorResponse> + { self.node_status_cache() .node_annotations() .await @@ -169,7 +170,7 @@ impl AppState { pub(crate) async fn legacy_mixnode_annotations( &self, - ) -> Result>>, AxumErrorResponse> + ) -> Result>>, AxumErrorResponse> { self.node_status_cache() .annotated_legacy_mixnodes() @@ -179,7 +180,7 @@ impl AppState { pub(crate) async fn legacy_gateways_annotations( &self, - ) -> Result>>, AxumErrorResponse> + ) -> Result>>, AxumErrorResponse> { self.node_status_cache() .annotated_legacy_gateways() diff --git a/nym-credential-proxy/nym-credential-proxy/Cargo.toml b/nym-credential-proxy/nym-credential-proxy/Cargo.toml index 0d322ad34e..25fafef798 100644 --- a/nym-credential-proxy/nym-credential-proxy/Cargo.toml +++ b/nym-credential-proxy/nym-credential-proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-credential-proxy" -version = "0.1.7" +version = "0.2.0" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -11,15 +11,12 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait.workspace = true axum.workspace = true anyhow.workspace = true bip39 = { workspace = true, features = ["zeroize"] } bs58.workspace = true cfg-if = { workspace = true } -colored.workspace = true clap = { workspace = true, features = ["derive", "env"] } -dotenvy.workspace = true futures.workspace = true humantime.workspace = true rand.workspace = true @@ -33,7 +30,6 @@ time.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] } tokio-util = { workspace = true, features = ["rt"] } -tower.workspace = true tower-http = { workspace = true, features = ["cors"], optional = true } tracing.workspace = true url.workspace = true @@ -49,7 +45,7 @@ nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand", " nym-credentials = { path = "../../common/credentials" } nym-credentials-interface = { path = "../../common/credentials-interface" } nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" } -nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa"] } +nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa", "middleware"] } nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-network-defaults = { path = "../../common/network-defaults" } diff --git a/nym-credential-proxy/nym-credential-proxy/migrations/04_global_expiration_date_signatures_epoch_fix.sql b/nym-credential-proxy/nym-credential-proxy/migrations/04_global_expiration_date_signatures_epoch_fix.sql new file mode 100644 index 0000000000..78a3fad093 --- /dev/null +++ b/nym-credential-proxy/nym-credential-proxy/migrations/04_global_expiration_date_signatures_epoch_fix.sql @@ -0,0 +1,18 @@ +/* + * Copyright 2025 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + +DROP TABLE global_expiration_date_signatures; + +CREATE TABLE global_expiration_date_signatures +( + expiration_date DATE NOT NULL, + epoch_id INTEGER NOT NULL, + serialization_revision INTEGER NOT NULL, + + -- combined signatures for all tuples issued for given day + serialised_signatures BLOB NOT NULL, + + PRIMARY KEY (epoch_id, expiration_date) +) \ No newline at end of file diff --git a/nym-credential-proxy/nym-credential-proxy/src/credentials/ticketbook/mod.rs b/nym-credential-proxy/nym-credential-proxy/src/credentials/ticketbook/mod.rs index 5e2c858b26..33ad66391e 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/credentials/ticketbook/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/credentials/ticketbook/mod.rs @@ -83,7 +83,7 @@ pub(crate) async fn try_obtain_wallet_shares( let _ = state.master_verification_key(Some(epoch)).await?; let _ = state.master_coin_index_signatures(Some(epoch)).await?; let _ = state - .master_expiration_date_signatures(expiration_date) + .master_expiration_date_signatures(epoch, expiration_date) .await?; let ecash_api_clients = state.ecash_clients(epoch).await?.clone(); 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 392fa71992..4579b677fa 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 @@ -97,7 +97,9 @@ impl ApiState { let _ = self.ecash_threshold(epoch_id).await?; let _ = self.ecash_clients(epoch_id).await?; let _ = self.master_coin_index_signatures(Some(epoch_id)).await?; - let _ = self.master_expiration_date_signatures(today).await?; + let _ = self + .master_expiration_date_signatures(epoch_id, today) + .await?; Ok(()) } @@ -143,7 +145,7 @@ impl ApiState { availability: finish_dt, }) } else if epoch.state.is_waiting_initialisation() { - return Err(VpnApiError::UninitialisedDkg); + Err(VpnApiError::UninitialisedDkg) } else { Err(VpnApiError::UnknownEcashFailure) } @@ -203,7 +205,7 @@ impl ApiState { Ok(epoch.epoch_id) } - pub(crate) async fn query_chain(&self) -> RwLockReadGuard { + pub(crate) async fn query_chain(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> { self.inner.client.query_chain().await } @@ -253,7 +255,7 @@ impl ApiState { let aggregated_expiration_date_signatures = if include_expiration_date_signatures { debug!("including expiration date signatures in the response"); Some( - self.master_expiration_date_signatures(expiration_date) + self.master_expiration_date_signatures(epoch_id, expiration_date) .await .map(|signatures| AggregatedExpirationDateSignaturesResponse { signatures: signatures.clone(), @@ -336,7 +338,7 @@ impl ApiState { pub(crate) async fn ecash_clients( &self, epoch_id: EpochId, - ) -> Result>, VpnApiError> { + ) -> Result>, VpnApiError> { self.inner .ecash_state .epoch_clients @@ -386,7 +388,7 @@ impl ApiState { pub(crate) async fn master_verification_key( &self, epoch_id: Option, - ) -> Result, VpnApiError> { + ) -> Result, VpnApiError> { let epoch_id = match epoch_id { Some(id) => id, None => self.current_epoch_id().await?, @@ -440,7 +442,7 @@ impl ApiState { pub(crate) async fn master_coin_index_signatures( &self, epoch_id: Option, - ) -> Result, VpnApiError> { + ) -> Result, VpnApiError> { let epoch_id = match epoch_id { Some(id) => id, None => self.current_epoch_id().await?, @@ -515,12 +517,13 @@ impl ApiState { pub(crate) async fn master_expiration_date_signatures( &self, + epoch_id: EpochId, expiration_date: Date, - ) -> Result, VpnApiError> { + ) -> Result, VpnApiError> { self.inner .ecash_state .expiration_date_signatures - .get_or_init(expiration_date, || async { + .get_or_init((epoch_id, expiration_date), || async { // 1. sanity check to see if the expiration_date is not nonsense ensure_sane_expiration_date(expiration_date)?; @@ -528,7 +531,7 @@ impl ApiState { if let Some(master_sigs) = self .inner .storage - .get_master_expiration_date_signatures(expiration_date) + .get_master_expiration_date_signatures(expiration_date, epoch_id) .await? { return Ok(master_sigs); @@ -536,7 +539,7 @@ impl ApiState { info!( - "attempting to establish master expiration date signatures for {expiration_date}..." + "attempting to establish master expiration date signatures for {expiration_date} and epoch {epoch_id}..." ); // 3. go around APIs and attempt to aggregate the data @@ -553,7 +556,7 @@ impl ApiState { let partial = api .api_client - .partial_expiration_date_signatures(Some(expiration_date)) + .partial_expiration_date_signatures(Some(expiration_date), Some(epoch_id)) .await? .signatures; Ok(ExpirationDateSignatureShare { @@ -619,12 +622,12 @@ impl ChainClient { Ok(ChainClient(Arc::new(RwLock::new(client)))) } - pub(crate) async fn query_chain(&self) -> ChainReadPermit { + pub(crate) async fn query_chain(&self) -> ChainReadPermit<'_> { let _acquire_timer = LockTimer::new("acquire chain query permit"); self.0.read().await } - pub(crate) async fn start_chain_tx(&self) -> ChainWritePermit { + pub(crate) async fn start_chain_tx(&self) -> ChainWritePermit<'_> { let _acquire_timer = LockTimer::new("acquire exclusive chain write permit"); ChainWritePermit { @@ -697,7 +700,7 @@ pub(crate) struct EcashState { pub(crate) coin_index_signatures: CachedImmutableEpochItem, pub(crate) expiration_date_signatures: - CachedImmutableItems, + CachedImmutableItems<(EpochId, Date), AggregatedExpirationDateSignatures>, } pub(crate) type ChainReadPermit<'a> = RwLockReadGuard<'a, DirectSigningHttpRpcNyxdClient>; diff --git a/nym-credential-proxy/nym-credential-proxy/src/nym_api_helpers.rs b/nym-credential-proxy/nym-credential-proxy/src/nym_api_helpers.rs index 3268ddb50c..72d0e2a467 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/nym_api_helpers.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/nym_api_helpers.rs @@ -86,7 +86,11 @@ impl CachedImmutableItems where K: Eq + Hash, { - pub(crate) async fn get_or_init(&self, key: K, f: F) -> Result, E> + pub(crate) async fn get_or_init( + &self, + key: K, + f: F, + ) -> Result, E> where F: FnOnce() -> U, U: Future>, diff --git a/nym-credential-proxy/nym-credential-proxy/src/storage/manager.rs b/nym-credential-proxy/nym-credential-proxy/src/storage/manager.rs index 5942d4e9cc..b5fefae449 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/storage/manager.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/storage/manager.rs @@ -332,18 +332,20 @@ impl SqliteStorageManager { pub(crate) async fn get_master_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: i64, ) -> Result, sqlx::Error> { sqlx::query_as!( RawExpirationDateSignatures, r#" - SELECT epoch_id as "epoch_id: u32", serialised_signatures, serialization_revision as "serialization_revision: u8" + SELECT serialised_signatures, serialization_revision as "serialization_revision: u8" FROM global_expiration_date_signatures - WHERE expiration_date = ? + WHERE expiration_date = ? AND epoch_id = ? "#, - expiration_date + expiration_date, + epoch_id ) - .fetch_optional(&self.connection_pool) - .await + .fetch_optional(&self.connection_pool) + .await } pub(crate) async fn insert_master_expiration_date_signatures( 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 1759ccdfc3..437e62c870 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/storage/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/storage/mod.rs @@ -331,10 +331,11 @@ impl VpnApiStorage { pub(crate) async fn get_master_expiration_date_signatures( &self, expiration_date: Date, + epoch_id: EpochId, ) -> Result, VpnApiError> { let Some(raw) = self .storage_manager - .get_master_expiration_date_signatures(expiration_date) + .get_master_expiration_date_signatures(expiration_date, epoch_id as i64) .await? else { return Ok(None); diff --git a/nym-credential-proxy/nym-credential-proxy/src/storage/models.rs b/nym-credential-proxy/nym-credential-proxy/src/storage/models.rs index 5bc443bf45..8118890b07 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/storage/models.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/storage/models.rs @@ -36,8 +36,6 @@ pub struct FullBlindedShares { #[derive(FromRow)] pub struct RawExpirationDateSignatures { - #[allow(dead_code)] - pub epoch_id: u32, pub serialised_signatures: Vec, pub serialization_revision: u8, } diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index f6671e611b..6c6cab2ca9 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -17,15 +17,10 @@ readme.workspace = true anyhow = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } futures = { workspace = true } -# nym-bin-common = { path = "../../common/bin-common", features = ["models"] } -nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ - "models", -] } +nym-bin-common = { path = "../../common/bin-common", features = ["models"] } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } + nym-node-status-client = { path = "../nym-node-status-client" } -nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ - "asymmetric", - "rand", -] } rand = { workspace = true } tokio = { workspace = true, features = [ "macros", 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 d0f3018fdd..1061ad5f95 100755 --- a/nym-node-status-api/nym-node-status-agent/run.sh +++ b/nym-node-status-api/nym-node-status-agent/run.sh @@ -16,12 +16,17 @@ set -a source "${monorepo_root}/envs/${ENVIRONMENT}.env" set +a +if [ -z "$NYM_NODE_MNEMONICS" ]; then + echo "NYM_NODE_MNEMONICS is required to run an agent" + exit 1 +fi + export RUST_LOG="info" -export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" -export NODE_STATUS_AGENT_SERVER_PORT="8000" -export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe" +NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" +NODE_STATUS_AGENT_SERVER_PORT="8000" +SERVER="${NODE_STATUS_AGENT_SERVER_ADDRESS}|${NODE_STATUS_AGENT_SERVER_PORT}" export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT" -export NODE_STATUS_AGENT_PROBE_MNEMONIC="$MNEMONIC" +export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe" export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1" workers=${1:-1} @@ -48,7 +53,7 @@ function swarm() { local workers=$1 for ((i = 1; i <= workers; i++)); do - ${monorepo_root}/target/release/nym-node-status-agent run-probe & + ${monorepo_root}/target/release/nym-node-status-agent run-probe --server ${SERVER} & done wait diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs b/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs index 24283b081d..bc5c055c76 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs @@ -50,7 +50,7 @@ pub(crate) struct Args { #[derive(Subcommand, Debug)] pub(crate) enum Command { RunProbe { - /// Server configurations in format "address:port:auth_key" + /// Server configurations in format "address|port" /// Can be specified multiple times for multiple servers #[arg(short, long, required = true)] server: Vec, 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 8ff0de8917..60c998d561 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 = "3.2.2" +version = "3.3.0" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -20,42 +20,25 @@ bip39 = { workspace = true } celes = { workspace = true } clap = { workspace = true, features = ["cargo", "derive", "env", "string"] } cosmwasm-std = { workspace = true } -envy = { workspace = true } futures-util = { workspace = true } +humantime = { workspace = true } itertools = { workspace = true } moka = { workspace = true, features = ["future"] } -# TODO dz had to switch to cheddar versions because develop is ahead of current -# Nym API: revert after Cheddar is out -nym-contracts-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-mixnet-contract-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ - "openapi", -] } + +nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } +nym-bin-common = { path = "../../common/bin-common", features = ["models"] } nym-node-status-client = { path = "../nym-node-status-client" } -nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-http-api-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ - "middleware", -] } -nym-network-defaults = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-serde-helpers = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-statistics-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -# nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -# nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -# 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-http-api-client = { path = "../../common/http-api-client" } -# nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"]} -# 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-validator-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -# nym-task = { path = "../../common/task" } -# nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } -nym-task = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-node-requests = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } +nym-http-api-client = { path = "../../common/http-api-client" } +nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"] } +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" } +nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } + rand = { workspace = true } rand_chacha = { workspace = true } regex = { workspace = true } 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 bd21bee53d..9f49c1e895 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 @@ -23,7 +23,7 @@ function run_bare() { echo "RUST_LOG=${RUST_LOG}" # --conection-url is provided in build.rs - cargo run --package nym-node-status-api + cargo run --package nym-node-status-api --no-default-features --features sqlite } function run_docker() { 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 0adf49af40..d3cb0f9c15 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 @@ -38,7 +38,7 @@ pub(crate) struct Cli { /// Nym api client timeout. #[clap(long, default_value = "15", env = "NYM_API_CLIENT_TIMEOUT")] - #[arg(value_parser = parse_duration)] + #[arg(value_parser = parse_duration_std)] pub(crate) nym_api_client_timeout: Duration, /// Connection url for the database. @@ -46,7 +46,7 @@ pub(crate) struct Cli { pub(crate) database_url: String, #[clap(long, default_value = "5", env = "SQLX_BUSY_TIMEOUT_S")] - #[arg(value_parser = parse_duration)] + #[arg(value_parser = parse_duration_std)] pub(crate) sqlx_busy_timeout_s: Duration, #[clap( @@ -54,7 +54,7 @@ pub(crate) struct Cli { default_value = "300", env = "NODE_STATUS_API_MONITOR_REFRESH_INTERVAL" )] - #[arg(value_parser = parse_duration)] + #[arg(value_parser = parse_duration_std)] pub(crate) monitor_refresh_interval: Duration, #[clap( @@ -62,17 +62,21 @@ pub(crate) struct Cli { default_value = "300", env = "NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL" )] - #[arg(value_parser = parse_duration)] + #[arg(value_parser = parse_duration_std)] pub(crate) testruns_refresh_interval: Duration, #[clap(long, default_value = "86400", env = "NODE_STATUS_API_GEODATA_TTL")] - #[arg(value_parser = parse_duration)] + #[arg(value_parser = parse_duration_std)] pub(crate) geodata_ttl: Duration, #[clap(env = "NODE_STATUS_API_AGENT_KEY_LIST")] #[arg(value_delimiter = ',')] pub(crate) agent_key_list: Vec, + #[clap(long, default_value = "120s", env = "AGENT_REQUEST_FRESHNESS")] + #[arg(value_parser = parse_duration_humantime)] + pub(crate) agent_request_freshness: time::Duration, + #[clap( long, default_value_t = 10, @@ -92,7 +96,40 @@ pub(crate) struct Cli { pub(crate) max_agent_count: i64, } -fn parse_duration(arg: &str) -> Result { +fn parse_duration_humantime(arg: &str) -> Result { + let std_duration = match humantime::parse_duration(arg) { + Ok(duration) => duration, + // assume old format (seconds) as a fallback + Err(_) => parse_duration_std(arg)?, + }; + + Ok(time::Duration::seconds(std_duration.as_secs() as i64)) +} + +fn parse_duration_std(arg: &str) -> Result { let seconds = arg.parse()?; Ok(std::time::Duration::from_secs(seconds)) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn humantime_should_work() { + let should_parse = [("120s", 120), ("120", 120), ("0s", 0), ("0", 0)]; + + for (raw, expected) in should_parse { + if let Ok(parsed) = parse_duration_humantime(raw) { + assert_eq!(parsed.whole_seconds(), expected); + } else { + panic!("Failed to parse {raw}") + } + } + + let should_not_parse = ["0.1s", "-15s"]; + for raw in should_not_parse { + assert!(parse_duration_humantime(raw).is_err()); + } + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs b/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs index d557bcb854..3292de1ad8 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs @@ -16,12 +16,8 @@ use axum::{ extract::{Path, State}, Router, }; -use nym_node_status_client::{ - auth::VerifiableRequest, - models::{get_testrun, submit_results, submit_results_v2}, -}; +use nym_node_status_client::models::{get_testrun, submit_results, submit_results_v2}; use reqwest::StatusCode; -use tracing::warn; // TODO dz consider adding endpoint to trigger testrun scan for a given gateway_id // like in H< src/http/testruns.rs @@ -40,8 +36,8 @@ async fn request_testrun( Json(request): Json, ) -> HttpResult> { // TODO dz log agent's network probe version - authenticate(&request, &state)?; - is_fresh(&request.payload.timestamp)?; + state.authenticate_agent_submission(&request)?; + state.is_fresh(&request.payload.timestamp)?; tracing::debug!("Agent requested testrun"); @@ -87,7 +83,7 @@ async fn submit_testrun( State(state): State, Json(submitted_result): Json, ) -> HttpResult { - authenticate(&submitted_result, &state)?; + state.authenticate_agent_submission(&submitted_result)?; let db = state.db_pool(); let mut conn = db @@ -189,8 +185,7 @@ async fn submit_testrun_v2( State(state): State, Json(submission): Json, ) -> HttpResult { - authenticate(&submission, &state)?; - is_fresh(&submission.payload.assigned_at_utc)?; + state.authenticate_agent_submission(&submission)?; let db = state.db_pool(); let mut conn = db @@ -249,44 +244,6 @@ async fn submit_testrun_v2( } } -// TODO dz this should be middleware -#[tracing::instrument(level = "debug", skip_all)] -fn authenticate(request: &impl VerifiableRequest, state: &AppState) -> HttpResult<()> { - if !state.is_registered(request.public_key()) { - tracing::warn!("Public key not registered with NS API, rejecting"); - return Err(HttpError::unauthorized()); - }; - - request.verify_signature().map_err(|_| { - tracing::warn!("Signature verification failed, rejecting"); - HttpError::unauthorized() - })?; - - Ok(()) -} - -static FRESHNESS_CUTOFF: time::Duration = time::Duration::minutes(2); - -fn is_fresh(request_time: &i64) -> HttpResult<()> { - // if a request took longer than N minutes to reach NS API, something is very wrong - let request_time = time::UtcDateTime::from_unix_timestamp(*request_time).map_err(|e| { - warn!("Failed to parse request time: {e}"); - HttpError::unauthorized() - })?; - - let cutoff_timestamp = now_utc() - FRESHNESS_CUTOFF; - if request_time < cutoff_timestamp { - warn!( - "Request time {} is older than cutoff {} ({}s ago), rejecting", - request_time, - cutoff_timestamp, - FRESHNESS_CUTOFF.whole_seconds() - ); - return Err(HttpError::unauthorized()); - } - Ok(()) -} - fn get_result_from_log(log: &str) -> String { static RE: std::sync::LazyLock = std::sync::LazyLock::new(|| regex::Regex::new(r"\n\{\s").expect("Invalid regex pattern")); diff --git a/nym-node-status-api/nym-node-status-api/src/http/server.rs b/nym-node-status-api/nym-node-status-api/src/http/server.rs index 548fc6206d..f197929d9f 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/server.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/server.rs @@ -13,12 +13,14 @@ use crate::{ /// Return handles that allow for graceful shutdown of server + awaiting its /// background tokio task +#[allow(clippy::too_many_arguments)] pub(crate) async fn start_http_api( db_pool: DbPool, http_port: u16, nym_http_cache_ttl: u64, agent_key_list: Vec, agent_max_count: i64, + agent_request_freshness_requirement: time::Duration, node_geocache: NodeGeoCache, node_delegations: Arc>, ) -> anyhow::Result { @@ -29,6 +31,7 @@ pub(crate) async fn start_http_api( nym_http_cache_ttl, agent_key_list, agent_max_count, + agent_request_freshness_requirement, node_geocache, node_delegations, ) 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 03f48cb0d6..35a3799202 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 @@ -5,6 +5,7 @@ use nym_bin_common::bin_info_owned; use nym_contracts_common::NaiveFloat; use nym_crypto::asymmetric::ed25519::PublicKey; use nym_mixnet_contract_common::NodeId; +use nym_node_status_client::auth::VerifiableRequest; use nym_validator_client::nym_api::SkimmedNode; use semver::Version; use serde::{Deserialize, Serialize}; @@ -17,8 +18,11 @@ use utoipa::ToSchema; use super::models::SessionStats; use crate::{ db::{queries, DbPool}, - http::models::{ - DVpnGateway, DailyStats, ExtendedNymNode, Gateway, Mixnode, NodeGeoData, SummaryHistory, + http::{ + error::{HttpError, HttpResult}, + models::{ + DVpnGateway, DailyStats, ExtendedNymNode, Gateway, Mixnode, NodeGeoData, SummaryHistory, + }, }, monitor::{DelegationsCache, NodeGeoCache}, }; @@ -31,6 +35,7 @@ pub(crate) struct AppState { cache: HttpCache, agent_key_list: Vec, agent_max_count: i64, + agent_request_freshness_requirement: time::Duration, node_geocache: NodeGeoCache, node_delegations: Arc>, bin_info: BinaryInfo, @@ -42,6 +47,7 @@ impl AppState { cache_ttl: u64, agent_key_list: Vec, agent_max_count: i64, + agent_request_freshness_requirement: time::Duration, node_geocache: NodeGeoCache, node_delegations: Arc>, ) -> Self { @@ -50,6 +56,7 @@ impl AppState { cache: HttpCache::new(cache_ttl).await, agent_key_list, agent_max_count, + agent_request_freshness_requirement, node_geocache, node_delegations, bin_info: BinaryInfo::new(), @@ -94,6 +101,44 @@ impl AppState { pub(crate) fn build_information(&self) -> &BinaryBuildInformationOwned { &self.bin_info.build_info } + + #[tracing::instrument(level = "debug", skip_all)] + pub(crate) fn authenticate_agent_submission( + &self, + request: &impl VerifiableRequest, + ) -> HttpResult<()> { + if !self.is_registered(request.public_key()) { + tracing::warn!("Public key not registered with NS API, rejecting"); + return Err(HttpError::unauthorized()); + }; + + request.verify_signature().map_err(|_| { + tracing::warn!("Signature verification failed, rejecting"); + HttpError::unauthorized() + })?; + + Ok(()) + } + + pub(crate) fn is_fresh(&self, request_time: &i64) -> HttpResult<()> { + // if a request took longer than N minutes to reach NS API, something is very wrong + let request_time = time::UtcDateTime::from_unix_timestamp(*request_time).map_err(|e| { + warn!("Failed to parse request time: {e}"); + HttpError::unauthorized() + })?; + + let cutoff_timestamp = crate::utils::now_utc() - self.agent_request_freshness_requirement; + if request_time < cutoff_timestamp { + warn!( + "Request time {} is older than cutoff {} ({}s ago), rejecting", + request_time, + cutoff_timestamp, + self.agent_request_freshness_requirement.whole_seconds() + ); + return Err(HttpError::unauthorized()); + } + Ok(()) + } } static GATEWAYS_LIST_KEY: &str = "gateways"; 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 60ebb3bb06..2723222415 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 @@ -98,6 +98,7 @@ async fn main() -> anyhow::Result<()> { args.nym_http_cache_ttl, agent_key_list.to_owned(), args.max_agent_count, + args.agent_request_freshness, geocache, delegations_cache, ) diff --git a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs index 62439d394c..bc3cd23fe7 100644 --- a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs @@ -57,7 +57,7 @@ async fn run( .clone() .expect("rust sdk mainnet default missing api_url"); - let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url) + let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()]) .no_hickory_dns() .with_timeout(nym_api_client_timeout) .build::<&str>()?; 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 2bd1745a68..d41d0cedc9 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 @@ -105,10 +105,11 @@ impl Monitor { .clone() .expect("rust sdk mainnet default missing api_url"); - let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url) - .no_hickory_dns() - .with_timeout(self.nym_api_client_timeout) - .build::<&str>()?; + let nym_api = + nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()]) + .no_hickory_dns() + .with_timeout(self.nym_api_client_timeout) + .build::<&str>()?; let api_client = NymApiClient::from(nym_api); diff --git a/nym-node-status-api/nym-node-status-client/Cargo.toml b/nym-node-status-api/nym-node-status-client/Cargo.toml index ef56a68c8b..c5ee68d501 100644 --- a/nym-node-status-api/nym-node-status-client/Cargo.toml +++ b/nym-node-status-api/nym-node-status-client/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-client" -version = "0.1.2" +version = "0.2.0" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -16,12 +16,8 @@ readme.workspace = true [dependencies] anyhow = { workspace = true } bincode = { workspace = true } -nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ - "asymmetric", - "serde", -] } -nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -reqwest = { workspace = true } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde", ] } +reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } time = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-client/src/lib.rs b/nym-node-status-api/nym-node-status-client/src/lib.rs index 6451b88c4a..e46897d8e4 100644 --- a/nym-node-status-api/nym-node-status-client/src/lib.rs +++ b/nym-node-status-api/nym-node-status-client/src/lib.rs @@ -2,7 +2,7 @@ use crate::models::{get_testrun, submit_results, submit_results_v2, TestrunAssig use anyhow::bail; use api::ApiPaths; use nym_crypto::asymmetric::ed25519::{PrivateKey, Signature}; -use tracing::instrument; +use tracing::{instrument, warn}; mod api; pub mod auth; @@ -18,7 +18,18 @@ 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(); + let user_agent = format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); + let client = reqwest::Client::builder() + .user_agent(user_agent) + .build() + .inspect_err(|err| { + warn!( + "Failed to create client with user agent, falling back to default ({})", + err + ) + }) + // failing to set user agent shouldn't be a critical error + .unwrap_or_default(); Self { api, diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 0744ebc28b..fcb45a818d 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.17.0" +version = "1.18.0" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -102,7 +102,6 @@ nym-node-metrics = { path = "nym-node-metrics" } # nodes: 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" } diff --git a/nym-node/nym-node-metrics/src/entry.rs b/nym-node/nym-node-metrics/src/entry.rs index 59200b5991..d236270b70 100644 --- a/nym-node/nym-node-metrics/src/entry.rs +++ b/nym-node/nym-node-metrics/src/entry.rs @@ -16,7 +16,7 @@ impl EntryStats { *self.sessions.write().await = new } - pub async fn client_sessions(&self) -> RwLockReadGuard { + pub async fn client_sessions(&self) -> RwLockReadGuard<'_, ClientSessions> { self.sessions.read().await } } diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index 0d1628f34b..82119f47c8 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -18,6 +18,7 @@ schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } strum = { workspace = true, features = ["derive"] } +strum_macros = { workspace = true } time = { workspace = true, features = ["serde", "formatting", "parsing"] } thiserror = { workspace = true } diff --git a/nym-node/nym-node-requests/src/api/v1/gateway/models.rs b/nym-node/nym-node-requests/src/api/v1/gateway/models.rs index 24597847ca..259bd35efa 100644 --- a/nym-node/nym-node-requests/src/api/v1/gateway/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/gateway/models.rs @@ -16,9 +16,16 @@ pub struct Gateway { #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct Wireguard { + #[deprecated(note = "use specific port instead (tunnel or metadata service)")] #[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))] pub port: u16, + #[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))] + pub tunnel_port: u16, + + #[cfg_attr(feature = "openapi", schema(example = 51830, default = 51830))] + pub metadata_port: u16, + pub public_key: String, } diff --git a/nym-node/nym-node-requests/src/api/v1/node_load/models.rs b/nym-node/nym-node-requests/src/api/v1/node_load/models.rs index ecbca83eeb..b4b1f0ad84 100644 --- a/nym-node/nym-node-requests/src/api/v1/node_load/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node_load/models.rs @@ -3,7 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use strum::{Display, EnumString}; +use strum_macros::{Display, EnumString}; #[derive( Display, diff --git a/nym-node/src/cli/helpers.rs b/nym-node/src/cli/helpers.rs index 3ff2388c45..cf6d18b683 100644 --- a/nym-node/src/cli/helpers.rs +++ b/nym-node/src/cli/helpers.rs @@ -276,13 +276,13 @@ pub(crate) struct WireguardArgs { )] pub(crate) wireguard_bind_address: Option, - /// Port announced to external clients wishing to connect to the wireguard interface. + /// Tunnel port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. #[clap( long, env = NYMNODE_WG_ANNOUNCED_PORT_ARG )] - pub(crate) wireguard_announced_port: Option, + pub(crate) wireguard_tunnel_announced_port: Option, /// 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 @@ -311,8 +311,8 @@ impl WireguardArgs { section.bind_address = bind_address } - if let Some(announced_port) = self.wireguard_announced_port { - section.announced_port = announced_port + if let Some(announced_tunnel_port) = self.wireguard_tunnel_announced_port { + section.announced_tunnel_port = announced_tunnel_port } if let Some(private_network_prefix) = self.wireguard_private_network_prefix { diff --git a/nym-node/src/cli/mod.rs b/nym-node/src/cli/mod.rs index b480743922..ad72438f41 100644 --- a/nym-node/src/cli/mod.rs +++ b/nym-node/src/cli/mod.rs @@ -6,7 +6,6 @@ use crate::cli::commands::{ test_throughput, }; use crate::env::vars::{NYMNODE_CONFIG_ENV_FILE_ARG, NYMNODE_NO_BANNER_ARG}; -use crate::logging::setup_tracing_logger; use clap::{Args, Parser, Subcommand}; use nym_bin_common::bin_info; use std::future::Future; diff --git a/nym-node/src/config/helpers.rs b/nym-node/src/config/helpers.rs index 0dae4e17db..53769f9d80 100644 --- a/nym-node/src/config/helpers.rs +++ b/nym-node/src/config/helpers.rs @@ -7,6 +7,7 @@ use clap::crate_version; use nym_gateway::node::{ LocalAuthenticatorOpts, LocalIpPacketRouterOpts, LocalNetworkRequesterOpts, }; +use nym_gateway::nym_authenticator; // a temporary solution until further refactoring is made fn ephemeral_gateway_config(config: &Config) -> nym_gateway::config::Config { @@ -182,7 +183,6 @@ pub fn gateway_tasks_config(config: &Config) -> GatewayTasksConfig { .authenticator .to_common_client_paths(), }, - logging: config.logging, }, custom_mixnet_path: None, }; @@ -202,7 +202,8 @@ pub fn gateway_tasks_config(config: &Config) -> GatewayTasksConfig { bind_address: config.wireguard.bind_address, private_ipv4: config.wireguard.private_ipv4, private_ipv6: config.wireguard.private_ipv6, - announced_port: config.wireguard.announced_port, + announced_tunnel_port: config.wireguard.announced_tunnel_port, + announced_metadata_port: config.wireguard.announced_metadata_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(), diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 257fc0f862..e401de4e20 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -10,7 +10,7 @@ use human_repr::HumanCount; use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::{ mainnet, var_names, DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT, - DEFAULT_VERLOC_LISTENING_PORT, WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, + DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_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}; @@ -21,6 +21,7 @@ use nym_config::{ must_get_home, parse_urls, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, }; +use nym_gateway::nym_authenticator; use serde::{Deserialize, Serialize}; use std::env; use std::fmt::{Display, Formatter}; @@ -930,9 +931,13 @@ pub struct Wireguard { /// default: `fc01::1` pub private_ipv6: Ipv6Addr, - /// Port announced to external clients wishing to connect to the wireguard interface. + /// Tunnel port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. - pub announced_port: u16, + pub announced_tunnel_port: u16, + + /// Metadata port announced to external clients wishing to connect to the metadata endpoint. + /// Useful in the instances where the node is behind a proxy. + pub announced_metadata_port: u16, /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. /// The maximum value for IPv4 is 32 @@ -950,10 +955,11 @@ impl Wireguard { pub fn new_default>(data_dir: P) -> Self { Wireguard { enabled: false, - bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT), + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT), private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4, private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6, - announced_port: WG_PORT, + announced_tunnel_port: WG_TUNNEL_PORT, + announced_metadata_port: WG_METADATA_PORT, private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4, private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6, storage_paths: persistence::WireguardPaths::new(data_dir), @@ -967,7 +973,8 @@ impl From for nym_wireguard_types::Config { bind_address: value.bind_address, private_ipv4: value.private_ipv4, private_ipv6: value.private_ipv6, - announced_port: value.announced_port, + announced_tunnel_port: value.announced_tunnel_port, + announced_metadata_port: value.announced_metadata_port, private_network_prefix_v4: value.private_network_prefix_v4, private_network_prefix_v6: value.private_network_prefix_v6, } @@ -980,7 +987,7 @@ impl From for nym_authenticator::config::Authenticator { bind_address: value.bind_address, private_ipv4: value.private_ipv4, private_ipv6: value.private_ipv6, - announced_port: value.announced_port, + tunnel_announced_port: value.announced_tunnel_port, private_network_prefix_v4: value.private_network_prefix_v4, private_network_prefix_v6: value.private_network_prefix_v6, } diff --git a/nym-node/src/config/old_configs/mod.rs b/nym-node/src/config/old_configs/mod.rs index 48c1ede17b..e67f2a83e9 100644 --- a/nym-node/src/config/old_configs/mod.rs +++ b/nym-node/src/config/old_configs/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only mod old_config_v1; +mod old_config_v10; mod old_config_v2; mod old_config_v3; mod old_config_v4; @@ -12,6 +13,7 @@ mod old_config_v8; mod old_config_v9; pub use old_config_v1::try_upgrade_config_v1; +pub use old_config_v10::try_upgrade_config_v10; 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; diff --git a/nym-node/src/config/old_configs/old_config_v10.rs b/nym-node/src/config/old_configs/old_config_v10.rs new file mode 100644 index 0000000000..a55b5b1113 --- /dev/null +++ b/nym-node/src/config/old_configs/old_config_v10.rs @@ -0,0 +1,1695 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::authenticator::{Authenticator, AuthenticatorDebug}; +use crate::config::gateway_tasks::{ + ClientBandwidthDebug, StaleMessageDebug, ZkNymTicketHandlerDebug, +}; +use crate::config::persistence::{ + AuthenticatorPaths, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, NetworkRequesterPaths, + NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, WireguardPaths, +}; +use crate::config::service_providers::{ + IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug, +}; +use crate::config::{ + gateway_tasks, service_providers, Config, GatewayTasksConfig, Host, Http, KeyRotation, + KeyRotationDebug, Mixnet, MixnetDebug, NodeModes, ReplayProtection, ReplayProtectionDebug, + ServiceProvidersConfig, Verloc, VerlocDebug, Wireguard, DEFAULT_HTTP_PORT, +}; +use crate::error::NymNodeError; +use celes::Country; +use clap::ValueEnum; +use nym_bin_common::logging::LoggingSettings; +use nym_client_core_config_types::DebugConfig as ClientDebugConfig; +use nym_config::defaults::{DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT}; +use nym_config::helpers::{in6addr_any_init, inaddr_any}; +use nym_config::{ + defaults::TICKETBOOK_VALIDITY_DAYS, + read_config_from_toml_file, + serde_helpers::{de_maybe_port, de_maybe_stringified}, +}; +use serde::{Deserialize, Serialize}; +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 WireguardPathsV10 { + 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 WireguardV10 { + /// Specifies whether the wireguard service is enabled on this node. + pub enabled: bool, + + /// Socket address this node will use for binding its wireguard interface. + /// default: `[::]:51822` + pub bind_address: SocketAddr, + + /// Private IPv4 address of the wireguard gateway. + /// default: `10.1.0.1` + pub private_ipv4: Ipv4Addr, + + /// Private IPv6 address of the wireguard gateway. + /// default: `fc01::1` + pub private_ipv6: Ipv6Addr, + + /// 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: WireguardPathsV10, +} + +// 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 NodeModeV10 { + #[default] + #[clap(alias = "mix")] + Mixnode, + + #[clap(alias = "entry", alias = "gateway")] + EntryGateway, + + // to not break existing behaviour, this means exit capabilities AND entry capabilities + #[clap(alias = "exit")] + ExitGateway, + + // will start only SP needed for exit capabilities WITHOUT entry routing + ExitProvidersOnly, +} + +impl From for NodeModes { + fn from(config: NodeModeV10) -> Self { + match config { + NodeModeV10::Mixnode => *NodeModes::default().with_mixnode(), + NodeModeV10::EntryGateway => *NodeModes::default().with_entry(), + // in old version exit implied entry + NodeModeV10::ExitGateway => *NodeModes::default().with_entry().with_exit(), + NodeModeV10::ExitProvidersOnly => *NodeModes::default().with_exit(), + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy)] +pub struct NodeModesV10 { + /// 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? +} + +// 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 HostV10 { + /// 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, Copy, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct KeyRotationDebugV10 { + /// Specifies how often the node should poll for any changes in the key rotation global state. + #[serde(with = "humantime_serde")] + pub rotation_state_poling_interval: Duration, +} + +impl KeyRotationDebugV10 { + pub const DEFAULT_ROTATION_STATE_POLLING_INTERVAL: Duration = Duration::from_secs(4 * 60 * 60); +} + +impl Default for KeyRotationDebugV10 { + fn default() -> Self { + KeyRotationDebugV10 { + rotation_state_poling_interval: Self::DEFAULT_ROTATION_STATE_POLLING_INTERVAL, + } + } +} + +#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct KeyRotationV10 { + pub debug: KeyRotationDebugV10, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetDebugV10 { + /// 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")] + 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, + + /// Specify whether any framed packets between nodes should use the legacy format (v7) + /// as opposed to the current (v8) one. + /// The legacy format has to be used until sufficient number of nodes on the network has upgraded and understand the new variant. + /// This will allow for optimisations to indicate which [sphinx] key is meant to be used when + /// processing received packets. + pub use_legacy_packet_encoding: bool, + + /// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented) + pub unsafe_disable_noise: bool, +} + +impl MixnetDebugV10 { + // 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 + pub(crate) const DEFAULT_MAXIMUM_FORWARD_PACKET_DELAY: Duration = Duration::from_secs(10); + pub(crate) const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = + Duration::from_millis(10_000); + pub(crate) const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = + Duration::from_millis(300_000); + pub(crate) const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); + pub(crate) const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; +} + +impl Default for MixnetDebugV10 { + fn default() -> Self { + MixnetDebugV10 { + 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, + maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + // TODO: update this in few releases... + use_legacy_packet_encoding: true, + unsafe_disable_noise: false, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixnetV10 { + /// Address this node will bind to for listening for mixnet packets + /// default: `[::]: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")] + #[serde(default)] + 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, + + /// Settings for controlling replay detection + pub replay_protection: ReplayProtectionV10, + + #[serde(default)] + pub key_rotation: KeyRotationV10, + + #[serde(default)] + pub debug: MixnetDebugV10, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct ReplayProtectionV10 { + /// Paths for current bloomfilters + pub storage_paths: ReplayProtectionPathsV10, + + #[serde(default)] + pub debug: ReplayProtectionDebugV10, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ReplayProtectionPathsV10 { + /// Path to the directory storing currently used bloomfilter(s). + pub current_bloomfilters_directory: PathBuf, +} + +#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct ReplayProtectionDebugV10 { + /// Specifies whether this node should **NOT** use replay protection + pub unsafe_disabled: bool, + + /// How long the processing task is willing to skip mutex acquisition before it will block the thread + /// until it actually obtains it + pub maximum_replay_detection_deferral: Duration, + + /// How many packets the processing task is willing to queue before it will block the thread + /// until it obtains the mutex + pub maximum_replay_detection_pending_packets: usize, + + /// Probability of false positives, fraction between 0 and 1 or a number indicating 1-in-p + pub false_positive_rate: f64, + + /// Defines initial expected number of packets this node will process a second, + /// so that an initial bloomfilter could be established. + /// As the node is running and BF are cleared, the value will be adjusted dynamically + pub initial_expected_packets_per_second: usize, + + /// Defines minimum expected number of packets this node will process a second + /// when used for calculating the BF size after reset. + /// This is to avoid degenerate cases where node receives 0 packets (because say it's misconfigured) + /// and it constructs an empty bloomfilter. + pub bloomfilter_minimum_packets_per_second_size: usize, + + /// Specifies the amount the bloomfilter size is going to get multiplied by after each reset. + /// It's performed in case the traffic rates increase before the next bloomfilter update. + pub bloomfilter_size_multiplier: f64, + + /// Specifies how often the bloomfilter is flushed to disk for recovery in case of a crash + #[serde(with = "humantime_serde")] + pub bloomfilter_disk_flushing_rate: Duration, +} + +impl ReplayProtectionDebugV10 { + pub const DEFAULT_MAXIMUM_REPLAY_DETECTION_DEFERRAL: Duration = Duration::from_millis(50); + + pub const DEFAULT_MAXIMUM_REPLAY_DETECTION_PENDING_PACKETS: usize = 100; + + // 12% (completely arbitrary) + pub const DEFAULT_BLOOMFILTER_SIZE_MULTIPLIER: f64 = 1.12; + + // 10^-5 + pub const DEFAULT_REPLAY_DETECTION_FALSE_POSITIVE_RATE: f64 = 1e-5; + + // we must have some reasonable balance between losing values and trashing the disk. + // since on average HDD it would take ~30s to save a 2GB bloomfilter + pub const DEFAULT_BF_DISK_FLUSHING_RATE: Duration = Duration::from_secs(10 * 60); + + // this value will have to be adjusted in the future + pub const DEFAULT_INITIAL_EXPECTED_PACKETS_PER_SECOND: usize = 2000; + + pub const DEFAULT_BLOOMFILTER_MINIMUM_PACKETS_PER_SECOND_SIZE: usize = 200; +} + +impl Default for ReplayProtectionDebugV10 { + fn default() -> Self { + ReplayProtectionDebugV10 { + unsafe_disabled: false, + maximum_replay_detection_deferral: Self::DEFAULT_MAXIMUM_REPLAY_DETECTION_DEFERRAL, + maximum_replay_detection_pending_packets: + Self::DEFAULT_MAXIMUM_REPLAY_DETECTION_PENDING_PACKETS, + false_positive_rate: Self::DEFAULT_REPLAY_DETECTION_FALSE_POSITIVE_RATE, + initial_expected_packets_per_second: Self::DEFAULT_INITIAL_EXPECTED_PACKETS_PER_SECOND, + bloomfilter_minimum_packets_per_second_size: + Self::DEFAULT_BLOOMFILTER_MINIMUM_PACKETS_PER_SECOND_SIZE, + bloomfilter_size_multiplier: Self::DEFAULT_BLOOMFILTER_SIZE_MULTIPLIER, + bloomfilter_disk_flushing_rate: Self::DEFAULT_BF_DISK_FLUSHING_RATE, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct KeysPathsV10 { + /// 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 the primary x25519 sphinx private key. + pub primary_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing the secondary x25519 sphinx private key. + pub secondary_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, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NymNodePathsV10 { + pub keys: KeysPathsV10, + + /// 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 HttpV10 { + /// Socket address this node will use for binding its http API. + /// default: `[::]: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, + + /// Specify the cache ttl of the node load. + /// default: 30s + #[serde(with = "humantime_serde")] + pub node_load_cache_ttl: Duration, +} + +impl HttpV10 { + pub const DEFAULT_NODE_LOAD_CACHE_TTL: Duration = Duration::from_secs(30); +} + +impl Default for HttpV10 { + fn default() -> Self { + HttpV10 { + 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, + node_load_cache_ttl: Self::DEFAULT_NODE_LOAD_CACHE_TTL, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodePathsV10 {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DebugV10 { + /// 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 VerlocDebugV10 { + /// 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 VerlocV10 { + /// Socket address this node will use for binding its verloc API. + /// default: `[::]: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: VerlocDebugV10, +} + +impl VerlocV10 { + pub const DEFAULT_VERLOC_PORT: u16 = DEFAULT_VERLOC_LISTENING_PORT; +} + +impl Default for VerlocV10 { + fn default() -> Self { + VerlocV10 { + bind_address: SocketAddr::new(in6addr_any_init(), Self::DEFAULT_VERLOC_PORT), + announce_port: None, + debug: Default::default(), + } + } +} + +impl VerlocDebugV10 { + 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 VerlocDebugV10 { + fn default() -> Self { + VerlocDebugV10 { + 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 MixnodeConfigV10 { + pub storage_paths: MixnodePathsV10, + + pub verloc: VerlocV10, + + #[serde(default)] + pub debug: DebugV10, +} + +impl DebugV10 { + 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 DebugV10 { + fn default() -> Self { + DebugV10 { + 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 EntryGatewayPathsV10 { + /// 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: AuthenticatorPathsV10, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct ZkNymTicketHandlerDebugV10 { + /// 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 ZkNymTicketHandlerDebugV10 { + 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 ZkNymTicketHandlerDebugV10 { + fn default() -> Self { + ZkNymTicketHandlerDebugV10 { + 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 EntryGatewayConfigDebugV10 { + /// 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: ZkNymTicketHandlerDebugV10, +} + +impl EntryGatewayConfigDebugV10 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for EntryGatewayConfigDebugV10 { + fn default() -> Self { + EntryGatewayConfigDebugV10 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + zk_nym_tickets: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfigV10 { + pub storage_paths: EntryGatewayPathsV10, + + /// 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: `[::]: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: EntryGatewayConfigDebugV10, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NetworkRequesterPathsV10 { + /// 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 IpPacketRouterPathsV10 { + /// 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 AuthenticatorPathsV10 { + /// 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 +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayPathsV10 { + pub clients_storage: PathBuf, + + pub stats_storage: PathBuf, + + pub network_requester: NetworkRequesterPathsV10, + + pub ip_packet_router: IpPacketRouterPathsV10, + + pub authenticator: AuthenticatorPathsV10, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct AuthenticatorV10 { + #[serde(default)] + pub debug: AuthenticatorDebugV10, +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct AuthenticatorDebugV10 { + /// 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 AuthenticatorDebugV10 { + fn default() -> Self { + AuthenticatorDebugV10 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[allow(clippy::derivable_impls)] +impl Default for AuthenticatorV10 { + fn default() -> Self { + AuthenticatorV10 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IpPacketRouterDebugV10 { + /// 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 IpPacketRouterDebugV10 { + fn default() -> Self { + IpPacketRouterDebugV10 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct IpPacketRouterV10 { + #[serde(default)] + pub debug: IpPacketRouterDebugV10, +} + +#[allow(clippy::derivable_impls)] +impl Default for IpPacketRouterV10 { + fn default() -> Self { + IpPacketRouterV10 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterDebugV10 { + /// 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 NetworkRequesterDebugV10 { + fn default() -> Self { + NetworkRequesterDebugV10 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterV10 { + #[serde(default)] + pub debug: NetworkRequesterDebugV10, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequesterV10 { + fn default() -> Self { + NetworkRequesterV10 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayDebugV10 { + /// 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 ExitGatewayDebugV10 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for ExitGatewayDebugV10 { + fn default() -> Self { + ExitGatewayDebugV10 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayConfigV10 { + pub storage_paths: ExitGatewayPathsV10, + + /// 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: NetworkRequesterV10, + + pub ip_packet_router: IpPacketRouterV10, + + #[serde(default)] + pub debug: ExitGatewayDebugV10, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct GatewayTasksPathsV10 { + /// 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, + + /// Path to sqlite database containing all persistent stats data. + pub stats_storage: PathBuf, + + /// Path to file containing cosmos account mnemonic used for zk-nym redemption. + pub cosmos_mnemonic: PathBuf, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct StaleMessageDebugV10 { + /// Specifies how often the clean-up task should check for stale data. + #[serde(with = "humantime_serde")] + pub cleaner_run_interval: Duration, + + /// Specifies maximum age of stored messages before they are removed from the storage + #[serde(with = "humantime_serde")] + pub max_age: Duration, +} + +impl StaleMessageDebugV10 { + const DEFAULT_STALE_MESSAGES_CLEANER_RUN_INTERVAL: Duration = Duration::from_secs(60 * 60); + const DEFAULT_STALE_MESSAGES_MAX_AGE: Duration = Duration::from_secs(24 * 60 * 60); +} + +impl Default for StaleMessageDebugV10 { + fn default() -> Self { + StaleMessageDebugV10 { + cleaner_run_interval: Self::DEFAULT_STALE_MESSAGES_CLEANER_RUN_INTERVAL, + max_age: Self::DEFAULT_STALE_MESSAGES_MAX_AGE, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ClientBandwidthDebugV10 { + /// Defines maximum delay between client bandwidth information being flushed to the persistent storage. + pub max_flushing_rate: Duration, + + /// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage. + pub max_delta_flushing_amount: i64, +} + +impl ClientBandwidthDebugV10 { + const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5); + const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB +} + +impl Default for ClientBandwidthDebugV10 { + fn default() -> Self { + ClientBandwidthDebugV10 { + max_flushing_rate: Self::DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE, + max_delta_flushing_amount: Self::DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct GatewayTasksConfigDebugV10 { + /// 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, + + /// The maximum number of client connections the gateway will keep open at once. + pub maximum_open_connections: usize, + + /// Specifies the minimum performance of mixnodes in the network that are to be used in internal topologies + /// of the services providers + pub minimum_mix_performance: u8, + + /// Defines the timestamp skew of a signed authentication request before it's deemed too excessive to process. + #[serde(alias = "maximum_auth_request_age")] + pub max_request_timestamp_skew: Duration, + + pub stale_messages: StaleMessageDebugV10, + + pub client_bandwidth: ClientBandwidthDebugV10, + + pub zk_nym_tickets: ZkNymTicketHandlerDebugV10, +} + +impl GatewayTasksConfigDebugV10 { + pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; + pub const DEFAULT_MINIMUM_MIX_PERFORMANCE: u8 = 50; + pub const DEFAULT_MAXIMUM_AUTH_REQUEST_TIMESTAMP_SKEW: Duration = Duration::from_secs(120); + pub const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192; +} + +impl Default for GatewayTasksConfigDebugV10 { + fn default() -> Self { + GatewayTasksConfigDebugV10 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + maximum_open_connections: Self::DEFAULT_MAXIMUM_OPEN_CONNECTIONS, + max_request_timestamp_skew: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_TIMESTAMP_SKEW, + minimum_mix_performance: Self::DEFAULT_MINIMUM_MIX_PERFORMANCE, + stale_messages: Default::default(), + client_bandwidth: Default::default(), + zk_nym_tickets: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct GatewayTasksConfigV10 { + pub storage_paths: GatewayTasksPathsV10, + + /// 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, + + /// Socket address this node will use for binding its client websocket API. + /// default: `[::]:9000` + pub ws_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: GatewayTasksConfigDebugV10, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ServiceProvidersPathsV10 { + /// 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, + + /// Path to sqlite database containing all persistent stats data. + pub stats_storage: PathBuf, + + pub network_requester: NetworkRequesterPathsV10, + + pub ip_packet_router: IpPacketRouterPathsV10, + + pub authenticator: AuthenticatorPathsV10, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ServiceProvidersConfigDebugV10 { + /// 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 ServiceProvidersConfigDebugV10 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for ServiceProvidersConfigDebugV10 { + fn default() -> Self { + ServiceProvidersConfigDebugV10 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ServiceProvidersConfigV10 { + pub storage_paths: ServiceProvidersPathsV10, + + /// 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: NetworkRequesterV10, + + pub ip_packet_router: IpPacketRouterV10, + + pub authenticator: AuthenticatorV10, + + #[serde(default)] + pub debug: ServiceProvidersConfigDebugV10, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MetricsConfigV10 { + #[serde(default)] + pub debug: MetricsDebugV10, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MetricsDebugV10 { + /// 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 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, + + /// 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 MetricsDebugV10 { + 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_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 MetricsDebugV10 { + fn default() -> Self { + MetricsDebugV10 { + 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_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, + } + } +} + +#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingSettingsV10 { + // well, we need to implement something here at some point... +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV10 { + // 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 modes of this nym-node. + pub modes: NodeModesV10, + + pub host: HostV10, + + pub mixnet: MixnetV10, + + /// Storage paths to persistent nym-node data, such as its long term keys. + pub storage_paths: NymNodePathsV10, + + #[serde(default)] + pub http: HttpV10, + + #[serde(default)] + pub verloc: VerlocV10, + + pub wireguard: WireguardV10, + + #[serde(alias = "entry_gateway")] + pub gateway_tasks: GatewayTasksConfigV10, + + #[serde(alias = "exit_gateway")] + pub service_providers: ServiceProvidersConfigV10, + + #[serde(default)] + pub metrics: MetricsConfigV10, + + #[serde(default)] + pub logging: LoggingSettingsV10, + + #[serde(default)] + pub debug: DebugV10, +} + +impl ConfigV10 { + // 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: ConfigV10 = + 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_v10>( + path: P, + prev_config: Option, +) -> Result { + debug!("attempting to load v10 config..."); + + let old_cfg = if let Some(prev_config) = prev_config { + prev_config + } else { + ConfigV10::read_from_path(&path)? + }; + + let cfg = Config { + save_path: old_cfg.save_path, + id: old_cfg.id, + modes: NodeModes { + mixnode: old_cfg.modes.mixnode, + entry: old_cfg.modes.entry, + exit: old_cfg.modes.exit, + }, + host: Host { + public_ips: old_cfg.host.public_ips, + hostname: old_cfg.host.hostname, + location: old_cfg.host.location, + }, + mixnet: Mixnet { + bind_address: old_cfg.mixnet.bind_address, + announce_port: old_cfg.mixnet.announce_port, + nym_api_urls: old_cfg.mixnet.nym_api_urls, + nyxd_urls: old_cfg.mixnet.nyxd_urls, + replay_protection: ReplayProtection { + storage_paths: ReplayProtectionPaths { + current_bloomfilters_directory: old_cfg + .mixnet + .replay_protection + .storage_paths + .current_bloomfilters_directory, + }, + debug: ReplayProtectionDebug { + unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled, + maximum_replay_detection_deferral: old_cfg + .mixnet + .replay_protection + .debug + .maximum_replay_detection_deferral, + maximum_replay_detection_pending_packets: old_cfg + .mixnet + .replay_protection + .debug + .maximum_replay_detection_pending_packets, + false_positive_rate: old_cfg.mixnet.replay_protection.debug.false_positive_rate, + initial_expected_packets_per_second: old_cfg + .mixnet + .replay_protection + .debug + .initial_expected_packets_per_second, + bloomfilter_minimum_packets_per_second_size: old_cfg + .mixnet + .replay_protection + .debug + .bloomfilter_minimum_packets_per_second_size, + bloomfilter_size_multiplier: old_cfg + .mixnet + .replay_protection + .debug + .bloomfilter_size_multiplier, + bloomfilter_disk_flushing_rate: old_cfg + .mixnet + .replay_protection + .debug + .bloomfilter_disk_flushing_rate, + }, + }, + key_rotation: KeyRotation { + debug: KeyRotationDebug { + rotation_state_poling_interval: old_cfg + .mixnet + .key_rotation + .debug + .rotation_state_poling_interval, + }, + }, + debug: MixnetDebug { + maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay, + packet_forwarding_initial_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_maximum_backoff, + initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout, + maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size, + unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, + use_legacy_packet_encoding: old_cfg.mixnet.debug.use_legacy_packet_encoding, + }, + }, + storage_paths: NymNodePaths { + keys: KeysPaths { + private_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .public_ed25519_identity_key_file, + primary_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .primary_x25519_sphinx_key_file, + private_x25519_noise_key_file: old_cfg + .storage_paths + .keys + .private_x25519_noise_key_file, + public_x25519_noise_key_file: old_cfg + .storage_paths + .keys + .public_x25519_noise_key_file, + secondary_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .secondary_x25519_sphinx_key_file, + }, + description: old_cfg.storage_paths.description, + }, + http: Http { + bind_address: old_cfg.http.bind_address, + landing_page_assets_path: old_cfg.http.landing_page_assets_path, + access_token: old_cfg.http.access_token, + expose_system_info: old_cfg.http.expose_system_info, + expose_system_hardware: old_cfg.http.expose_system_hardware, + expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, + node_load_cache_ttl: old_cfg.http.node_load_cache_ttl, + }, + verloc: Verloc { + bind_address: old_cfg.verloc.bind_address, + announce_port: old_cfg.verloc.announce_port, + debug: VerlocDebug { + packets_per_node: old_cfg.verloc.debug.packets_per_node, + connection_timeout: old_cfg.verloc.debug.connection_timeout, + packet_timeout: old_cfg.verloc.debug.packet_timeout, + delay_between_packets: old_cfg.verloc.debug.delay_between_packets, + tested_nodes_batch_size: old_cfg.verloc.debug.tested_nodes_batch_size, + testing_interval: old_cfg.verloc.debug.testing_interval, + retry_timeout: old_cfg.verloc.debug.retry_timeout, + }, + }, + wireguard: Wireguard { + enabled: old_cfg.wireguard.enabled, + bind_address: old_cfg.wireguard.bind_address, + private_ipv4: old_cfg.wireguard.private_ipv4, + private_ipv6: old_cfg.wireguard.private_ipv6, + announced_tunnel_port: old_cfg.wireguard.announced_port, + announced_metadata_port: WG_METADATA_PORT, + private_network_prefix_v4: old_cfg.wireguard.private_network_prefix_v4, + private_network_prefix_v6: old_cfg.wireguard.private_network_prefix_v6, + storage_paths: WireguardPaths { + private_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .private_diffie_hellman_key_file, + public_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .public_diffie_hellman_key_file, + }, + }, + gateway_tasks: GatewayTasksConfig { + storage_paths: GatewayTasksPaths { + clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage, + stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage, + cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic, + }, + enforce_zk_nyms: old_cfg.gateway_tasks.enforce_zk_nyms, + ws_bind_address: old_cfg.gateway_tasks.ws_bind_address, + announce_ws_port: old_cfg.gateway_tasks.announce_ws_port, + announce_wss_port: old_cfg.gateway_tasks.announce_wss_port, + debug: gateway_tasks::Debug { + message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit, + maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections, + minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance, + max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew, + stale_messages: StaleMessageDebug { + cleaner_run_interval: old_cfg + .gateway_tasks + .debug + .stale_messages + .cleaner_run_interval, + max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age, + }, + client_bandwidth: ClientBandwidthDebug { + max_flushing_rate: old_cfg + .gateway_tasks + .debug + .client_bandwidth + .max_flushing_rate, + max_delta_flushing_amount: old_cfg + .gateway_tasks + .debug + .client_bandwidth + .max_delta_flushing_amount, + }, + zk_nym_tickets: ZkNymTicketHandlerDebug { + revocation_bandwidth_penalty: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .revocation_bandwidth_penalty, + pending_poller: old_cfg.gateway_tasks.debug.zk_nym_tickets.pending_poller, + minimum_api_quorum: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .minimum_api_quorum, + minimum_redemption_tickets: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .minimum_redemption_tickets, + maximum_time_between_redemption: old_cfg + .gateway_tasks + .debug + .zk_nym_tickets + .maximum_time_between_redemption, + }, + }, + }, + service_providers: ServiceProvidersConfig { + storage_paths: ServiceProvidersPaths { + clients_storage: old_cfg.service_providers.storage_paths.clients_storage, + stats_storage: old_cfg.service_providers.storage_paths.stats_storage, + network_requester: NetworkRequesterPaths { + private_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .service_providers + .storage_paths + .network_requester + .ack_key_file, + reply_surb_database: old_cfg + .service_providers + .storage_paths + .network_requester + .reply_surb_database, + gateway_registrations: old_cfg + .service_providers + .storage_paths + .network_requester + .gateway_registrations, + }, + ip_packet_router: IpPacketRouterPaths { + private_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .ack_key_file, + reply_surb_database: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .reply_surb_database, + gateway_registrations: old_cfg + .service_providers + .storage_paths + .ip_packet_router + .gateway_registrations, + }, + authenticator: AuthenticatorPaths { + private_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .service_providers + .storage_paths + .authenticator + .ack_key_file, + reply_surb_database: old_cfg + .service_providers + .storage_paths + .authenticator + .reply_surb_database, + gateway_registrations: old_cfg + .service_providers + .storage_paths + .authenticator + .gateway_registrations, + }, + }, + open_proxy: old_cfg.service_providers.open_proxy, + upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url, + network_requester: NetworkRequester { + debug: NetworkRequesterDebug { + enabled: old_cfg.service_providers.network_requester.debug.enabled, + disable_poisson_rate: old_cfg + .service_providers + .network_requester + .debug + .disable_poisson_rate, + client_debug: old_cfg + .service_providers + .network_requester + .debug + .client_debug, + }, + }, + ip_packet_router: IpPacketRouter { + debug: IpPacketRouterDebug { + enabled: old_cfg.service_providers.ip_packet_router.debug.enabled, + disable_poisson_rate: old_cfg + .service_providers + .ip_packet_router + .debug + .disable_poisson_rate, + client_debug: old_cfg + .service_providers + .ip_packet_router + .debug + .client_debug, + }, + }, + authenticator: Authenticator { + debug: AuthenticatorDebug { + enabled: old_cfg.service_providers.authenticator.debug.enabled, + disable_poisson_rate: old_cfg + .service_providers + .authenticator + .debug + .disable_poisson_rate, + client_debug: old_cfg.service_providers.authenticator.debug.client_debug, + }, + }, + debug: service_providers::Debug { + message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit, + }, + }, + metrics: Default::default(), + logging: LoggingSettings {}, + debug: Default::default(), + }; + Ok(cfg) +} diff --git a/nym-node/src/config/old_configs/old_config_v9.rs b/nym-node/src/config/old_configs/old_config_v9.rs index 94b9559844..839a54b679 100644 --- a/nym-node/src/config/old_configs/old_config_v9.rs +++ b/nym-node/src/config/old_configs/old_config_v9.rs @@ -1,29 +1,26 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::authenticator::{Authenticator, AuthenticatorDebug}; -use crate::config::gateway_tasks::{ - ClientBandwidthDebug, StaleMessageDebug, ZkNymTicketHandlerDebug, +use crate::config::old_configs::old_config_v10::{ + AuthenticatorDebugV10, AuthenticatorPathsV10, AuthenticatorV10, ClientBandwidthDebugV10, + ConfigV10, GatewayTasksConfigDebugV10, GatewayTasksConfigV10, GatewayTasksPathsV10, HostV10, + HttpV10, IpPacketRouterDebugV10, IpPacketRouterPathsV10, IpPacketRouterV10, KeysPathsV10, + LoggingSettingsV10, MixnetDebugV10, MixnetV10, NetworkRequesterDebugV10, + NetworkRequesterPathsV10, NetworkRequesterV10, NodeModesV10, NymNodePathsV10, + ReplayProtectionDebugV10, ReplayProtectionPathsV10, ReplayProtectionV10, + ServiceProvidersConfigDebugV10, ServiceProvidersConfigV10, ServiceProvidersPathsV10, + StaleMessageDebugV10, VerlocDebugV10, VerlocV10, WireguardPathsV10, WireguardV10, + ZkNymTicketHandlerDebugV10, }; use crate::config::persistence::{ - AuthenticatorPaths, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, NetworkRequesterPaths, - NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, WireguardPaths, DEFAULT_PRIMARY_X25519_SPHINX_KEY_FILENAME, DEFAULT_SECONDARY_X25519_SPHINX_KEY_FILENAME, }; -use crate::config::service_providers::{ - IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug, -}; -use crate::config::{ - gateway_tasks, service_providers, Config, GatewayTasksConfig, Host, Http, Mixnet, MixnetDebug, - NodeModes, ReplayProtection, ReplayProtectionDebug, ServiceProvidersConfig, Verloc, - VerlocDebug, Wireguard, DEFAULT_HTTP_PORT, -}; +use crate::config::{NodeModes, DEFAULT_HTTP_PORT}; use crate::error::{KeyIOFailure, NymNodeError}; use crate::node::helpers::{get_current_rotation_id, load_key, store_key}; use crate::node::key_rotation::key::SphinxPrivateKey; use celes::Country; use clap::ValueEnum; -use nym_bin_common::logging::LoggingSettings; use nym_client_core_config_types::DebugConfig as ClientDebugConfig; use nym_config::defaults::DEFAULT_VERLOC_LISTENING_PORT; use nym_config::helpers::{in6addr_any_init, inaddr_any}; @@ -1330,7 +1327,7 @@ async fn upgrade_sphinx_key(old_cfg: &ConfigV9) -> Result<(PathBuf, PathBuf), Ny pub async fn try_upgrade_config_v9>( path: P, prev_config: Option, -) -> Result { +) -> Result { debug!("attempting to load v9 config..."); let old_cfg = if let Some(prev_config) = prev_config { @@ -1342,33 +1339,33 @@ pub async fn try_upgrade_config_v9>( let (primary_x25519_sphinx_key_file, secondary_x25519_sphinx_key_file) = upgrade_sphinx_key(&old_cfg).await?; - let cfg = Config { + let cfg = ConfigV10 { save_path: old_cfg.save_path, id: old_cfg.id, - modes: NodeModes { + modes: NodeModesV10 { mixnode: old_cfg.modes.mixnode, entry: old_cfg.modes.entry, exit: old_cfg.modes.exit, }, - host: Host { + host: HostV10 { public_ips: old_cfg.host.public_ips, hostname: old_cfg.host.hostname, location: old_cfg.host.location, }, - mixnet: Mixnet { + mixnet: MixnetV10 { bind_address: old_cfg.mixnet.bind_address, announce_port: old_cfg.mixnet.announce_port, nym_api_urls: old_cfg.mixnet.nym_api_urls, nyxd_urls: old_cfg.mixnet.nyxd_urls, - replay_protection: ReplayProtection { - storage_paths: ReplayProtectionPaths { + replay_protection: ReplayProtectionV10 { + storage_paths: ReplayProtectionPathsV10 { current_bloomfilters_directory: old_cfg .mixnet .replay_protection .storage_paths .current_bloomfilters_directory, }, - debug: ReplayProtectionDebug { + debug: ReplayProtectionDebugV10 { unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled, maximum_replay_detection_deferral: old_cfg .mixnet @@ -1404,7 +1401,7 @@ pub async fn try_upgrade_config_v9>( }, }, key_rotation: Default::default(), - debug: MixnetDebug { + debug: MixnetDebugV10 { maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay, packet_forwarding_initial_backoff: old_cfg .mixnet @@ -1420,8 +1417,8 @@ pub async fn try_upgrade_config_v9>( ..Default::default() }, }, - storage_paths: NymNodePaths { - keys: KeysPaths { + storage_paths: NymNodePathsV10 { + keys: KeysPathsV10 { private_ed25519_identity_key_file: old_cfg .storage_paths .keys @@ -1443,7 +1440,7 @@ pub async fn try_upgrade_config_v9>( }, description: old_cfg.storage_paths.description, }, - http: Http { + http: HttpV10 { 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, @@ -1452,10 +1449,10 @@ pub async fn try_upgrade_config_v9>( expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, node_load_cache_ttl: old_cfg.http.node_load_cache_ttl, }, - verloc: Verloc { + verloc: VerlocV10 { bind_address: old_cfg.verloc.bind_address, announce_port: old_cfg.verloc.announce_port, - debug: VerlocDebug { + debug: VerlocDebugV10 { packets_per_node: old_cfg.verloc.debug.packets_per_node, connection_timeout: old_cfg.verloc.debug.connection_timeout, packet_timeout: old_cfg.verloc.debug.packet_timeout, @@ -1465,7 +1462,7 @@ pub async fn try_upgrade_config_v9>( retry_timeout: old_cfg.verloc.debug.retry_timeout, }, }, - wireguard: Wireguard { + wireguard: WireguardV10 { enabled: old_cfg.wireguard.enabled, bind_address: old_cfg.wireguard.bind_address, private_ipv4: old_cfg.wireguard.private_ipv4, @@ -1473,7 +1470,7 @@ pub async fn try_upgrade_config_v9>( 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 { + storage_paths: WireguardPathsV10 { private_diffie_hellman_key_file: old_cfg .wireguard .storage_paths @@ -1484,8 +1481,8 @@ pub async fn try_upgrade_config_v9>( .public_diffie_hellman_key_file, }, }, - gateway_tasks: GatewayTasksConfig { - storage_paths: GatewayTasksPaths { + gateway_tasks: GatewayTasksConfigV10 { + storage_paths: GatewayTasksPathsV10 { clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage, stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage, cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic, @@ -1494,12 +1491,12 @@ pub async fn try_upgrade_config_v9>( ws_bind_address: old_cfg.gateway_tasks.ws_bind_address, announce_ws_port: old_cfg.gateway_tasks.announce_ws_port, announce_wss_port: old_cfg.gateway_tasks.announce_wss_port, - debug: gateway_tasks::Debug { + debug: GatewayTasksConfigDebugV10 { message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit, maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections, minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance, max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew, - stale_messages: StaleMessageDebug { + stale_messages: StaleMessageDebugV10 { cleaner_run_interval: old_cfg .gateway_tasks .debug @@ -1507,7 +1504,7 @@ pub async fn try_upgrade_config_v9>( .cleaner_run_interval, max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age, }, - client_bandwidth: ClientBandwidthDebug { + client_bandwidth: ClientBandwidthDebugV10 { max_flushing_rate: old_cfg .gateway_tasks .debug @@ -1519,7 +1516,7 @@ pub async fn try_upgrade_config_v9>( .client_bandwidth .max_delta_flushing_amount, }, - zk_nym_tickets: ZkNymTicketHandlerDebug { + zk_nym_tickets: ZkNymTicketHandlerDebugV10 { revocation_bandwidth_penalty: old_cfg .gateway_tasks .debug @@ -1544,11 +1541,11 @@ pub async fn try_upgrade_config_v9>( }, }, }, - service_providers: ServiceProvidersConfig { - storage_paths: ServiceProvidersPaths { + service_providers: ServiceProvidersConfigV10 { + storage_paths: ServiceProvidersPathsV10 { clients_storage: old_cfg.service_providers.storage_paths.clients_storage, stats_storage: old_cfg.service_providers.storage_paths.stats_storage, - network_requester: NetworkRequesterPaths { + network_requester: NetworkRequesterPathsV10 { private_ed25519_identity_key_file: old_cfg .service_providers .storage_paths @@ -1585,7 +1582,7 @@ pub async fn try_upgrade_config_v9>( .network_requester .gateway_registrations, }, - ip_packet_router: IpPacketRouterPaths { + ip_packet_router: IpPacketRouterPathsV10 { private_ed25519_identity_key_file: old_cfg .service_providers .storage_paths @@ -1622,7 +1619,7 @@ pub async fn try_upgrade_config_v9>( .ip_packet_router .gateway_registrations, }, - authenticator: AuthenticatorPaths { + authenticator: AuthenticatorPathsV10 { private_ed25519_identity_key_file: old_cfg .service_providers .storage_paths @@ -1662,8 +1659,8 @@ pub async fn try_upgrade_config_v9>( }, open_proxy: old_cfg.service_providers.open_proxy, upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url, - network_requester: NetworkRequester { - debug: NetworkRequesterDebug { + network_requester: NetworkRequesterV10 { + debug: NetworkRequesterDebugV10 { enabled: old_cfg.service_providers.network_requester.debug.enabled, disable_poisson_rate: old_cfg .service_providers @@ -1677,8 +1674,8 @@ pub async fn try_upgrade_config_v9>( .client_debug, }, }, - ip_packet_router: IpPacketRouter { - debug: IpPacketRouterDebug { + ip_packet_router: IpPacketRouterV10 { + debug: IpPacketRouterDebugV10 { enabled: old_cfg.service_providers.ip_packet_router.debug.enabled, disable_poisson_rate: old_cfg .service_providers @@ -1692,8 +1689,8 @@ pub async fn try_upgrade_config_v9>( .client_debug, }, }, - authenticator: Authenticator { - debug: AuthenticatorDebug { + authenticator: AuthenticatorV10 { + debug: AuthenticatorDebugV10 { enabled: old_cfg.service_providers.authenticator.debug.enabled, disable_poisson_rate: old_cfg .service_providers @@ -1703,12 +1700,12 @@ pub async fn try_upgrade_config_v9>( client_debug: old_cfg.service_providers.authenticator.debug.client_debug, }, }, - debug: service_providers::Debug { + debug: ServiceProvidersConfigDebugV10 { message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit, }, }, metrics: Default::default(), - logging: LoggingSettings {}, + logging: LoggingSettingsV10 {}, debug: Default::default(), }; Ok(cfg) diff --git a/nym-node/src/config/template.rs b/nym-node/src/config/template.rs index bfe413a62b..bf8ed72710 100644 --- a/nym-node/src/config/template.rs +++ b/nym-node/src/config/template.rs @@ -145,7 +145,11 @@ private_ipv6 = '{{ wireguard.private_ipv6 }}' # Port announced to external clients wishing to connect to the wireguard interface. # Useful in the instances where the node is behind a proxy. -announced_port = {{ wireguard.announced_port }} +announced_tunnel_port = {{ wireguard.announced_tunnel_port }} + +# Port announced to external clients wishing to connect to the metadata service. +# Useful in the instances where the node is behind a proxy. +announced_metadata_port = {{ wireguard.announced_metadata_port }} # The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. # The maximum value for IPv4 is 32 diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs index 85e2cced9c..bdb10ec8b7 100644 --- a/nym-node/src/config/upgrade_helpers.rs +++ b/nym-node/src/config/upgrade_helpers.rs @@ -16,7 +16,8 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { let cfg = try_upgrade_config_v6(path, cfg).await.ok(); let cfg = try_upgrade_config_v7(path, cfg).await.ok(); let cfg = try_upgrade_config_v8(path, cfg).await.ok(); - match try_upgrade_config_v9(path, cfg).await { + let cfg = try_upgrade_config_v9(path, cfg).await.ok(); + match try_upgrade_config_v10(path, cfg).await { Ok(cfg) => cfg.save(), Err(e) => { tracing::error!("Failed to finish upgrade: {e}"); diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 6ec9105482..2fca246997 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -605,7 +605,8 @@ impl NymNode { metrics_sender: MetricEventsSender, active_clients_store: ActiveClientsStore, mix_packet_sender: MixForwardingSender, - task_client: TaskClient, + legacy_task_client: TaskClient, + shutdown_token: ShutdownToken, ) -> Result<(), NymNodeError> { let config = gateway_tasks_config(&self.config); @@ -623,7 +624,8 @@ impl NymNode { metrics_sender, self.metrics.clone(), self.entry_gateway.mnemonic.clone(), - task_client, + legacy_task_client, + shutdown_token, ); // if we're running in entry mode, start the websocket @@ -716,8 +718,11 @@ impl NymNode { // entry gateway info let wireguard = if self.config.wireguard.enabled { + #[allow(deprecated)] Some(api_requests::v1::gateway::models::Wireguard { - port: self.config.wireguard.announced_port, + port: self.config.wireguard.announced_tunnel_port, + tunnel_port: self.config.wireguard.announced_tunnel_port, + metadata_port: self.config.wireguard.announced_metadata_port, public_key: self.x25519_wireguard_key()?.to_string(), }) } else { @@ -1183,6 +1188,7 @@ impl NymNode { active_clients_store, mix_packet_sender, self.shutdown_manager.subscribe_legacy("gateway-tasks"), + self.shutdown_manager.child_token("gateway-tasks"), ) .await?; diff --git a/nym-statistics-api/Cargo.toml b/nym-statistics-api/Cargo.toml index 4d37557118..5d070a1dcd 100644 --- a/nym-statistics-api/Cargo.toml +++ b/nym-statistics-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-statistics-api" -version = "0.1.4" +version = "0.1.5" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -48,8 +48,8 @@ nym-statistics-common = { path = "../common/statistics", features = [ ] } nym-task = { path = "../common/task" } -nym-http-api-client = { git = "https://github.com/nymtech/nym", branch = "release/2025.11-cheddar" } -nym-validator-client = { git = "https://github.com/nymtech/nym", branch = "release/2025.11-cheddar" } # current develop is incompatible with current deployed API +nym-http-api-client = { path = "../common/http-api-client" } +nym-validator-client = { path = "../common/client-libs/validator-client" } [build-dependencies] anyhow = { workspace = true } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 2b9ac7375d..f4bff02a23 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -40,7 +40,7 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "strum 0.23.0", + "strum", "tap", "tauri", "tauri-build", @@ -2799,15 +2799,6 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -4123,7 +4114,8 @@ dependencies = [ "nym-network-defaults", "rand 0.8.5", "serde", - "strum 0.26.3", + "strum", + "strum_macros", "thiserror 2.0.12", "time", "utoipa", @@ -4304,7 +4296,8 @@ dependencies = [ "schemars", "serde", "serde_json", - "strum 0.26.3", + "strum", + "strum_macros", "thiserror 2.0.12", "time", "utoipa", @@ -4410,7 +4403,8 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "strum 0.26.3", + "strum", + "strum_macros", "thiserror 2.0.12", "ts-rs", "url", @@ -4508,7 +4502,8 @@ dependencies = [ "nym-vesting-contract-common", "serde", "serde_json", - "strum 0.23.0", + "strum", + "strum_macros", "ts-rs", ] @@ -6630,45 +6625,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.23.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.23.1", -] - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", + "strum_macros", ] [[package]] name = "strum_macros" -version = "0.23.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "rustversion", "syn 2.0.100", ] diff --git a/nym-wallet/nym-wallet-types/Cargo.toml b/nym-wallet/nym-wallet-types/Cargo.toml index 83522fa191..d29f2467dd 100644 --- a/nym-wallet/nym-wallet-types/Cargo.toml +++ b/nym-wallet/nym-wallet-types/Cargo.toml @@ -9,7 +9,9 @@ license = "Apache-2.0" hex-literal = "0.3.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -strum = { version = "0.23", features = ["derive"] } +strum = { version = "0.27.2", features = ["derive"] } +strum_macros = "0.27.2" + ts-rs = "10.0.0" cosmwasm-std = "2.2.1" @@ -26,6 +28,3 @@ nym-types = { path = "../../common/types" } [features] default = [] generate-ts = ["nym-mixnet-contract-common/generate-ts", "nym-types/generate-ts"] - - - diff --git a/nym-wallet/nym-wallet-types/src/network.rs b/nym-wallet/nym-wallet-types/src/network.rs index 7da9efa4f3..b01c3768e3 100644 --- a/nym-wallet/nym-wallet-types/src/network.rs +++ b/nym-wallet/nym-wallet-types/src/network.rs @@ -5,7 +5,7 @@ use nym_config::defaults::{mainnet, DenomDetails, NymNetworkDetails}; use nym_types::{currency::DecCoin, error::TypesError}; use serde::{Deserialize, Serialize}; use std::{fmt, ops::Not, str::FromStr}; -use strum::EnumIter; +use strum_macros::EnumIter; mod sandbox; diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 52cbac1d9e..7ea924a9e8 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -39,7 +39,7 @@ reqwest = { version = "0.12.4", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" -strum = { version = "0.23", features = ["derive"] } +strum = { version = "0.27.2", features = ["derive"] } tap = "1" tauri = { version = "2", features = [] } #tendermint-rpc = "0.23.0" diff --git a/sdk/rust/nym-sdk/src/mixnet/native_client.rs b/sdk/rust/nym-sdk/src/mixnet/native_client.rs index fb940c5b4d..7c8089d324 100644 --- a/sdk/rust/nym-sdk/src/mixnet/native_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/native_client.rs @@ -181,7 +181,9 @@ impl MixnetClient { } /// Gets the value of the currently used network topology. - pub async fn read_current_route_provider(&self) -> Option> { + pub async fn read_current_route_provider( + &self, + ) -> Option> { self.client_state .topology_accessor .current_route_provider() diff --git a/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs b/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs index 1de4318ed6..1e8085aa83 100644 --- a/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs +++ b/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs @@ -47,7 +47,7 @@ impl MessageBuffer { self.buffer.is_empty() } - pub fn iter(&self) -> std::slice::Iter> { + pub fn iter(&self) -> std::slice::Iter<'_, DecayWrapper> { self.buffer.iter() } diff --git a/service-providers/authenticator/Cargo.toml b/service-providers/authenticator/Cargo.toml deleted file mode 100644 index 85e4dc7021..0000000000 --- a/service-providers/authenticator/Cargo.toml +++ /dev/null @@ -1,59 +0,0 @@ -[package] -name = "nym-authenticator" -version = "0.1.0" -authors.workspace = true -repository.workspace = true -homepage.workspace = true -documentation.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -anyhow = { workspace = true } -bincode = { workspace = true } -bs58 = { workspace = true } -bytes = { workspace = true } -clap = { workspace = true, features = ["cargo", "derive"] } -defguard_wireguard_rs = { workspace = true } -fastrand = { workspace = true } -futures = { workspace = true } -ipnetwork = { workspace = true } -log = { workspace = true } -rand = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "net"] } -tokio-stream = { workspace = true } -tokio-util = { workspace = true, features = ["codec"] } -url = { workspace = true } - -nym-authenticator-requests = { path = "../../common/authenticator-requests" } -nym-bin-common = { path = "../../common/bin-common", features = [ - "clap", - "output_format", - "basic_tracing", -] } -nym-client-core = { path = "../../common/client-core", features = ["cli"] } -nym-config = { path = "../../common/config" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-credential-verification = { path = "../../common/credential-verification" } -nym-crypto = { path = "../../common/crypto" } -nym-gateway-requests = { path = "../../common/gateway-requests" } -nym-gateway-storage = { path = "../../common/gateway-storage" } -nym-id = { path = "../../common/nym-id" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-service-providers-common = { path = "../common" } -nym-service-provider-requests-common = { path = "../../common/service-provider-requests-common" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-task = { path = "../../common/task" } -nym-types = { path = "../../common/types" } -nym-wireguard = { path = "../../common/wireguard" } -nym-wireguard-types = { path = "../../common/wireguard-types" } - -[dev-dependencies] -mock_instant = "0.5.3" -time = { workspace = true } - -nym-wireguard = { path = "../../common/wireguard", features = ["mock"] } diff --git a/service-providers/authenticator/src/config/helpers.rs b/service-providers/authenticator/src/config/helpers.rs deleted file mode 100644 index 35a3e51159..0000000000 --- a/service-providers/authenticator/src/config/helpers.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use log::{info, trace}; -use std::path::Path; - -use crate::{config::old_config_v1_1_54::ConfigV1_1_54, error::AuthenticatorError}; - -async fn try_upgrade_v1_1_54_config>(id: P) -> Result { - // explicitly load it as v1.1.54 (which is incompatible with the current one, i.e. +1.1.55) - let Ok(old_config) = ConfigV1_1_54::read_from_default_path(id) else { - // if we failed to load it, there might have been nothing to upgrade - // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(false); - }; - info!("It seems the client is using <= v1.1.54 config template."); - info!("It is going to get updated to the current specification."); - - let updated = old_config.try_upgrade()?; - - updated.save_to_default_location()?; - Ok(true) -} - -pub async fn try_upgrade_config>(id: P) -> Result<(), AuthenticatorError> { - trace!("Attempting to upgrade config"); - if try_upgrade_v1_1_54_config(id).await? { - return Ok(()); - } - - Ok(()) -} diff --git a/service-providers/authenticator/src/config/mod.rs b/service-providers/authenticator/src/config/mod.rs deleted file mode 100644 index 90c4a0be60..0000000000 --- a/service-providers/authenticator/src/config/mod.rs +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_bin_common::logging::LoggingSettings; -pub use nym_client_core::config::Config as BaseClientConfig; -use nym_client_core::{cli_helpers::CliClientConfig, config::disk_persistence::CommonClientPaths}; -use nym_config::{ - must_get_home, save_formatted_config_to_file, NymConfigTemplate, OptionalSet, - DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, -}; -use nym_network_defaults::{ - WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, WG_TUN_DEVICE_NETMASK_V4, - WG_TUN_DEVICE_NETMASK_V6, -}; -use nym_service_providers_common::DEFAULT_SERVICE_PROVIDERS_DIR; -pub use persistence::AuthenticatorPaths; -use serde::{Deserialize, Serialize}; -use std::{ - io, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, - path::{Path, PathBuf}, - str::FromStr, -}; -use template::CONFIG_TEMPLATE; - -pub mod helpers; -pub mod old_config_v1_1_54; -pub mod persistence; -pub mod template; - -const DEFAULT_AUTHENTICATOR_DIR: &str = "authenticator"; - -/// Derive default path to authenticator's config directory. -/// It should get resolved to `$HOME/.nym/service-providers/authenticator//config` -pub fn default_config_directory>(id: P) -> PathBuf { - must_get_home() - .join(NYM_DIR) - .join(DEFAULT_SERVICE_PROVIDERS_DIR) - .join(DEFAULT_AUTHENTICATOR_DIR) - .join(id) - .join(DEFAULT_CONFIG_DIR) -} - -/// Derive default path to authenticator's config file. -/// It should get resolved to `$HOME/.nym/service-providers/authenticator//config/config.toml` -pub fn default_config_filepath>(id: P) -> PathBuf { - default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) -} - -/// Derive default path to authenticator's data directory where files, such as keys, are stored. -/// It should get resolved to `$HOME/.nym/service-providers/authenticator//data` -pub fn default_data_directory>(id: P) -> PathBuf { - must_get_home() - .join(NYM_DIR) - .join(DEFAULT_SERVICE_PROVIDERS_DIR) - .join(DEFAULT_AUTHENTICATOR_DIR) - .join(id) - .join(DEFAULT_DATA_DIR) -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(flatten)] - pub base: BaseClientConfig, - - #[serde(default)] - pub authenticator: Authenticator, - - pub storage_paths: AuthenticatorPaths, - - pub logging: LoggingSettings, -} - -impl NymConfigTemplate for Config { - fn template(&self) -> &'static str { - CONFIG_TEMPLATE - } -} - -impl CliClientConfig for Config { - fn common_paths(&self) -> &CommonClientPaths { - &self.storage_paths.common_paths - } - - fn core_config(&self) -> &BaseClientConfig { - &self.base - } - - fn default_store_location(&self) -> PathBuf { - self.default_location() - } - - fn save_to>(&self, path: P) -> io::Result<()> { - save_formatted_config_to_file(self, path) - } -} - -impl Config { - pub fn new>(id: S) -> Self { - Config { - base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")), - authenticator: Default::default(), - storage_paths: AuthenticatorPaths::new_base(default_data_directory(id.as_ref())), - logging: Default::default(), - } - } - - #[allow(unused)] - pub fn with_data_directory>(mut self, data_directory: P) -> Self { - self.storage_paths = AuthenticatorPaths::new_base(data_directory); - self - } - - pub fn read_from_toml_file>(path: P) -> io::Result { - nym_config::read_config_from_toml_file(path) - } - - pub fn read_from_default_path>(id: P) -> io::Result { - Self::read_from_toml_file(default_config_filepath(id)) - } - - pub fn default_location(&self) -> PathBuf { - default_config_filepath(&self.base.client.id) - } - - #[allow(unused)] - pub fn save_to_default_location(&self) -> io::Result<()> { - let config_save_location: PathBuf = self.default_location(); - save_formatted_config_to_file(self, config_save_location) - } - - pub fn validate(&self) -> bool { - // no other sections have explicit requirements (yet) - self.base.validate() - } - - #[doc(hidden)] - pub fn set_no_poisson_process(&mut self) { - self.base.set_no_poisson_process() - } - - // poor man's 'builder' method - #[allow(unused)] - pub fn with_base(mut self, f: F, val: T) -> Self - where - F: Fn(BaseClientConfig, T) -> BaseClientConfig, - { - self.base = f(self.base, val); - self - } - - // helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config - // (plz, lets refactor it) - pub fn with_optional_base(mut self, f: F, val: Option) -> Self - where - F: Fn(BaseClientConfig, T) -> BaseClientConfig, - { - self.base = self.base.with_optional(f, val); - self - } - - #[allow(unused)] - pub fn with_optional_base_env(mut self, f: F, val: Option, env_var: &str) -> Self - where - F: Fn(BaseClientConfig, T) -> BaseClientConfig, - T: FromStr, - ::Err: std::fmt::Debug, - { - self.base = self.base.with_optional_env(f, val, env_var); - self - } - - pub fn with_optional_base_custom_env( - mut self, - f: F, - val: Option, - env_var: &str, - parser: G, - ) -> Self - where - F: Fn(BaseClientConfig, T) -> BaseClientConfig, - G: Fn(&str) -> T, - { - self.base = self.base.with_optional_custom_env(f, val, env_var, parser); - self - } -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct Authenticator { - /// Socket address this node will use for binding its wireguard interface. - /// default: `0.0.0.0:51822` - pub bind_address: SocketAddr, - - /// Private IP address of the wireguard gateway. - /// default: `10.1.0.1` - pub private_ipv4: Ipv4Addr, - - /// Private IP 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, -} - -impl Default for Authenticator { - fn default() -> Self { - Self { - bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT), - private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4, - private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6, - announced_port: WG_PORT, - private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4, - private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6, - } - } -} - -impl From for nym_wireguard_types::Config { - fn from(value: Authenticator) -> Self { - nym_wireguard_types::Config { - bind_address: value.bind_address, - private_ipv4: value.private_ipv4, - private_ipv6: value.private_ipv6, - announced_port: value.announced_port, - private_network_prefix_v4: value.private_network_prefix_v4, - private_network_prefix_v6: value.private_network_prefix_v6, - } - } -} diff --git a/service-providers/authenticator/src/config/old_config_v1_1_54.rs b/service-providers/authenticator/src/config/old_config_v1_1_54.rs deleted file mode 100644 index 2cbb3d6c4f..0000000000 --- a/service-providers/authenticator/src/config/old_config_v1_1_54.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::{io, path::Path}; - -use nym_bin_common::logging::LoggingSettings; -pub use nym_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as BaseConfigV1_1_54; -use nym_config::read_config_from_toml_file; -use serde::{Deserialize, Serialize}; - -use crate::{config::Config, error::AuthenticatorError}; - -use super::{default_config_filepath, Authenticator, AuthenticatorPaths}; - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ConfigV1_1_54 { - #[serde(flatten)] - pub base: BaseConfigV1_1_54, - - #[serde(default)] - pub authenticator: Authenticator, - - pub storage_paths: AuthenticatorPaths, - - pub logging: LoggingSettings, -} - -impl ConfigV1_1_54 { - pub fn read_from_toml_file>(path: P) -> io::Result { - read_config_from_toml_file(path) - } - - pub fn read_from_default_path>(id: P) -> io::Result { - Self::read_from_toml_file(default_config_filepath(id)) - } - - pub fn try_upgrade(self) -> Result { - Ok(Config { - base: self.base.into(), - authenticator: self.authenticator, - storage_paths: self.storage_paths, - logging: self.logging, - }) - } -} diff --git a/service-providers/authenticator/src/config/template.rs b/service-providers/authenticator/src/config/template.rs deleted file mode 100644 index d8b30f2227..0000000000 --- a/service-providers/authenticator/src/config/template.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -pub(crate) const CONFIG_TEMPLATE: &str = - // 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. - r#" -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -##### main base client config options ##### - -[client] -# Version of the client for which this configuration was created. -version = '{{ client.version }}' - -# Human readable ID of this particular client. -id = '{{ 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 = {{ client.disabled_credentials_mode }} - -# Addresses to nyxd validators via which the client can communicate with the chain. -nyxd_urls = [ - {{#each 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 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 }}' - -# Location of the file containing our allow.list -allowed_list_location = '{{ storage_paths.allowed_list_location }}' - -# Location of the file containing our unknown.list -unknown_list_location = '{{ storage_paths.unknown_list_location }}' - - -##### 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. - -[debug] - -[debug.acknowledgements] -average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}' - -[debug.cover_traffic] -loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}' - -"#; diff --git a/service-providers/authenticator/src/lib.rs b/service-providers/authenticator/src/lib.rs deleted file mode 100644 index 048f91f4f2..0000000000 --- a/service-providers/authenticator/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -pub use authenticator::{Authenticator, OnStartData}; -pub use config::Config; - -pub mod authenticator; -pub mod config; -pub mod error; -pub mod mixnet_client; -pub mod mixnet_listener; -mod peer_manager; -mod seen_credential_cache; diff --git a/service-providers/authenticator/src/peer_manager.rs b/service-providers/authenticator/src/peer_manager.rs deleted file mode 100644 index 3d4c86a95d..0000000000 --- a/service-providers/authenticator/src/peer_manager.rs +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::*; -use defguard_wireguard_rs::{host::Peer, key::Key}; -use futures::channel::oneshot; -use nym_credential_verification::ClientBandwidth; -use nym_wireguard::{ - peer_controller::{ - AddPeerControlResponse, GetClientBandwidthControlResponse, PeerControlRequest, - QueryPeerControlResponse, RemovePeerControlResponse, - }, - WireguardGatewayData, -}; -use nym_wireguard_types::PeerPublicKey; - -pub struct PeerManager { - pub(crate) wireguard_gateway_data: WireguardGatewayData, -} - -impl PeerManager { - pub fn new(wireguard_gateway_data: WireguardGatewayData) -> Self { - PeerManager { - wireguard_gateway_data, - } - } - pub async fn add_peer(&mut self, peer: Peer) -> Result<()> { - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::AddPeer { peer, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - let AddPeerControlResponse { success } = response_rx.await.map_err(|_| { - AuthenticatorError::InternalError("no response for add peer".to_string()) - })?; - if !success { - return Err(AuthenticatorError::InternalError( - "adding peer could not be performed".to_string(), - )); - } - Ok(()) - } - - pub async fn _remove_peer(&mut self, pub_key: PeerPublicKey) -> Result<()> { - let key = Key::new(pub_key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::RemovePeer { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - let RemovePeerControlResponse { success } = response_rx.await.map_err(|_| { - AuthenticatorError::InternalError("no response for remove peer".to_string()) - })?; - if !success { - return Err(AuthenticatorError::InternalError( - "removing peer could not be performed".to_string(), - )); - } - Ok(()) - } - - pub async fn query_peer(&mut self, public_key: PeerPublicKey) -> Result> { - let key = Key::new(public_key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::QueryPeer { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - let QueryPeerControlResponse { success, peer } = response_rx.await.map_err(|_| { - AuthenticatorError::InternalError("no response for query peer".to_string()) - })?; - if !success { - return Err(AuthenticatorError::InternalError( - "querying peer could not be performed".to_string(), - )); - } - Ok(peer) - } - - pub async fn query_bandwidth(&mut self, public_key: PeerPublicKey) -> Result> { - let res = if let Some(client_bandwidth) = self.query_client_bandwidth(public_key).await? { - Some(client_bandwidth.available().await) - } else { - None - }; - Ok(res) - } - - pub async fn query_client_bandwidth( - &mut self, - key: PeerPublicKey, - ) -> Result> { - let key = Key::new(key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::GetClientBandwidth { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - let GetClientBandwidthControlResponse { client_bandwidth } = - response_rx.await.map_err(|_| { - AuthenticatorError::InternalError( - "no response for query client bandwidth".to_string(), - ) - })?; - Ok(client_bandwidth) - } -} - -#[cfg(test)] -mod tests { - use std::{str::FromStr, sync::Arc}; - - use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, ecash::MockEcashManager, - }; - use nym_credentials_interface::Bandwidth; - use nym_crypto::asymmetric::x25519::KeyPair; - use nym_gateway_storage::traits::{mock::MockGatewayStorage, BandwidthGatewayStorage}; - use nym_wireguard::peer_controller::{start_controller, stop_controller}; - use rand::rngs::OsRng; - use time::{Duration, OffsetDateTime}; - use tokio::sync::RwLock; - - use crate::{config::Authenticator, mixnet_listener::credential_storage_preparation}; - - use super::*; - - #[tokio::test] - async fn add_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - let peer = Peer::default(); - let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); - - assert!(peer_manager.add_peer(peer.clone()).await.is_err()); - - let client_id = storage - .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) - .await - .unwrap(); - assert!(peer_manager.add_peer(peer.clone()).await.is_err()); - - credential_storage_preparation(Arc::new(ecash_manager), client_id) - .await - .unwrap(); - peer_manager.add_peer(peer.clone()).await.unwrap(); - - stop_controller(task_manager).await; - } - - async fn helper_add_peer( - storage: &Arc>, - peer_manager: &mut PeerManager, - ) -> i64 { - let peer = Peer::default(); - let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); - let client_id = storage - .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) - .await - .unwrap(); - credential_storage_preparation(Arc::new(ecash_manager), client_id) - .await - .unwrap(); - peer_manager.add_peer(peer.clone()).await.unwrap(); - - client_id - } - - #[tokio::test] - async fn remove_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - helper_add_peer(&storage, &mut peer_manager).await; - peer_manager._remove_peer(public_key).await.unwrap(); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn query_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - assert!(peer_manager.query_peer(public_key).await.unwrap().is_none()); - - helper_add_peer(&storage, &mut peer_manager).await; - let peer = peer_manager.query_peer(public_key).await.unwrap().unwrap(); - assert_eq!(peer.public_key, key); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn query_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - assert!(peer_manager - .query_bandwidth(public_key) - .await - .unwrap() - .is_none()); - - helper_add_peer(&storage, &mut peer_manager).await; - let available_bandwidth = peer_manager - .query_bandwidth(public_key) - .await - .unwrap() - .unwrap(); - assert_eq!(available_bandwidth, 0); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn query_client_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - assert!(peer_manager - .query_client_bandwidth(public_key) - .await - .unwrap() - .is_none()); - - helper_add_peer(&storage, &mut peer_manager).await; - let available_bandwidth = peer_manager - .query_client_bandwidth(public_key) - .await - .unwrap() - .unwrap() - .available() - .await; - assert_eq!(available_bandwidth, 0); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn increase_decrease_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let top_up = 42; - let consume = 4; - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - let client_id = helper_add_peer(&storage, &mut peer_manager).await; - let client_bandwidth = peer_manager - .query_client_bandwidth(public_key) - .await - .unwrap() - .unwrap(); - - let mut bw_manager = BandwidthStorageManager::new( - Box::new(storage), - client_bandwidth.clone(), - client_id, - Default::default(), - true, - ); - bw_manager - .increase_bandwidth( - Bandwidth::new_unchecked(top_up as u64), - OffsetDateTime::now_utc() - .checked_add(Duration::minutes(1)) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(client_bandwidth.available().await, top_up); - assert_eq!( - peer_manager - .query_bandwidth(public_key) - .await - .unwrap() - .unwrap(), - top_up - ); - - bw_manager.try_use_bandwidth(consume).await.unwrap(); - let remaining = top_up - consume; - assert_eq!(client_bandwidth.available().await, remaining); - assert_eq!( - peer_manager - .query_bandwidth(public_key) - .await - .unwrap() - .unwrap(), - remaining - ); - - stop_controller(task_manager).await; - } -} diff --git a/service-providers/ip-packet-router/src/non_linux_dummy.rs b/service-providers/ip-packet-router/src/non_linux_dummy.rs index abac0ad1d3..9197ae4cd1 100644 --- a/service-providers/ip-packet-router/src/non_linux_dummy.rs +++ b/service-providers/ip-packet-router/src/non_linux_dummy.rs @@ -6,6 +6,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +#[allow(dead_code)] pub(crate) struct DummyDevice; impl AsyncRead for DummyDevice { diff --git a/service-providers/ip-packet-router/src/util/parse_ip.rs b/service-providers/ip-packet-router/src/util/parse_ip.rs index 6b99a2ba89..0b9972b3f2 100644 --- a/service-providers/ip-packet-router/src/util/parse_ip.rs +++ b/service-providers/ip-packet-router/src/util/parse_ip.rs @@ -9,7 +9,7 @@ pub(crate) struct ParsedPacket<'a> { pub(crate) dst: Option, } -pub(crate) fn parse_packet(packet: &[u8]) -> Result { +pub(crate) fn parse_packet(packet: &[u8]) -> Result, IpPacketRouterError> { let headers = etherparse::SlicedPacket::from_ip(packet).map_err(|err| { log::warn!("Unable to parse incoming data as IP packet: {err}"); IpPacketRouterError::PacketParseFailed { source: err } diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index ad7436e5de..e9829ca4ef 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.62" +version = "1.1.63" 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 a30c16a255..8b46302ef6 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.61" +version = "1.1.62" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index f7331cda2d..e2752b4095 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.26" +version = "0.1.27" authors.workspace = true repository.workspace = true homepage.workspace = true