Compare commits

...

36 Commits

Author SHA1 Message Date
durch a880b59f03 Force IPv4 2025-09-04 12:47:45 +02:00
import this 4062734a31 [DOCs/operators]: NIP-2 tokenomics update & fix csv2md bug (#6008) 2025-09-04 11:29:44 +02:00
import this ccd8ff26a3 Feature: Delegation program stake checker and adjuster (#5980)
* initialise stake adjustment program

* add readme file with a simple guide

* syntax

* syntax

* FINISHED: faster and returning more data

* change dwl link to develop branch
2025-09-03 16:06:06 +00:00
import this 43d043a9cd Feature: Nym node autorun CLI (#5916)
* initial commit - add prereqs install script

* add env vars prompt

* automate latest binary url env var

* add install node script

* add modes to nym-node install script

* start main cli framework

* adding branch var for easier deployment and testing

* add systemd config

* add proxy and wss setup script

* add landing page stub and fix nginx script

* add nginx setup

* fix typo

* add checks for existing dir and wg prompt

* add nginx commands

* add service file check

* add service file check

* convention alignment

* add checks to nginx setup

* cleanup old code

* add bonding prompt and nym node run fns

* fix syntax

* fix syntax

* fix syntax

* fix syntax

* fix syntax

* fix syntax

* fix syntax

* fix syntax

* add service script to init

* fix syntax

* fix syntax

* add chmod

* fix script logic

* syntax fix

* syntax fix

* silent mode trial

* fix evn prompt script

* make scripts interactive

* indent fix

* correct node-install script

* initial mixnode setup working - gws need more love

* fix bonding function

* syntax fix

* improve run noide as service script

* improve service script

* improve run service fn

* fix logic

* beautify

* beautify

* create run node as service script

* syntax fix

* attempt to resolve memory running out issue

* attempt to resolve memory running out issue

* attempt to resolve memory running out issue

* attempt to resolve memory running out issue

* attempt to resolve memory running out issue

* attempt to resolve memory running out issue

* attempt to resolve memory running out issue

* attempt to resolve memory running out issue

* setting wireguard

* solved memory issues

* rename landing page template

* modify wireguard enabled fn

* layout change

* syntax fix

* modify node setup script

* sync up envs

* return missing function

* fix urls

* fix network manager script execution

* fix wss and nginx

* fix layout

* tweak WG contion

* syntax fix

* add init placeholder

* syntax fix

* redefine wireguard check logic

* check if node exists

* add argparse and dev option

* styling

* add panic

* add error message

* improve logic

* improve logic

* add arg

* add dev arg for all levels

* add confirmation loop

* styling

* fix bonding question

* syntax edit

* syntax edit

* syntax edit

* refactor for already bonded nodes

* add default branch on top and define metavar

* fix node install script

* clean and prepare for review

* indentation fix

* fix nginx setup

* fix nginx setup

* style cleanup

* fix try error logic

* tune --dev option to run before command correctly

* fix y/n convention across the modules

* add explorer URL to the message

* minor layout fixes
2025-09-02 20:34:24 +00:00
Drazen Urch 3d6cf730c2 NS-API: Cast to BIGINT to make i64 work (#6003) 2025-09-02 18:35:25 +01:00
Jędrzej Stuczyński c0f8d98b63 bugfix: return from MixTrafficController if client request channel has closed (#6002) 2025-09-02 10:23:25 +01:00
Jędrzej Stuczyński 91995da4f1 chore: use updated version of simulate endpoint (#5988) 2025-09-02 10:12:52 +01:00
Jędrzej Stuczyński 01fa1df66c feat: shared library for attempting to retrieve update mode attestation (#5954)
* feat: shared library for attempting to retrieve update mode attestation

* clippy

* add nym- prefix to the crate name

* use pure-rust impl for jwt-simple
2025-09-02 09:28:32 +01:00
Jędrzej Stuczyński baddaaac22 feat: nym signers monitor (#5933)
* initialise nym-signers-monitor

* creating nyxd client

* performing checks

* sending notifications on failure

* rate limitting on notifications + clippy
2025-09-02 09:27:09 +01:00
elsirion 2c4b5f168b fix: use WASM compatible time API in client (#5948) 2025-09-02 09:26:06 +01:00
Bogdan-Ștefan Neacşu a557ac22c7 Revert "Create an axum_test client for more integrated unit testing (#5956)" (#5999)
This reverts commit efd61eb47c.
2025-09-01 15:37:10 +03:00
Jędrzej Stuczyński 55ef89178b chore: upgraded syn to 2.0 and removed nym-execute (#5998) 2025-09-01 12:59:13 +01:00
Jędrzej Stuczyński d97be2d8ef bugfix: Recipient deserialisation for deserialisers missing bytes specialisation (#5991)
* bugfix: Recipient deserialisation for deserialisers missing bytes specialisation

for example toml or json will just default to visit_seq ignoring bytes related optimisations

* clippy
2025-09-01 11:30:35 +01:00
Bogdan-Ștefan Neacşu efd61eb47c Create an axum_test client for more integrated unit testing (#5956) 2025-09-01 13:27:06 +03:00
benedetta davico 4a01973b31 Merge pull request #5981 from nymtech/benny/ns-api-ci-fix
Fix the ns api ci workflow
2025-09-01 11:02:21 +02:00
Mark Sinclair 9ad9c3b8e7 Bug fix: NS API monikers (#5990)
* node-status-api: fix missing monikers because of deserialisation issues from unstructured data

* node-status-api: bump version after bug fix monikers

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-09-01 09:48:37 +01:00
dependabot[bot] 6706500132 build(deps): bump pbkdf2 from 3.1.2 to 3.1.3 (#5869)
Bumps [pbkdf2](https://github.com/crypto-browserify/pbkdf2) from 3.1.2 to 3.1.3.
- [Changelog](https://github.com/browserify/pbkdf2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/pbkdf2/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: pbkdf2
  dependency-version: 3.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 15:52:10 +01:00
dependabot[bot] 33fe059c28 Bump console from 0.15.11 to 0.16.0 (#5931)
Bumps [console](https://github.com/console-rs/console) from 0.15.11 to 0.16.0.
- [Release notes](https://github.com/console-rs/console/releases)
- [Changelog](https://github.com/console-rs/console/blob/main/CHANGELOG.md)
- [Commits](https://github.com/console-rs/console/compare/0.15.11...0.16.0)

---
updated-dependencies:
- dependency-name: console
  dependency-version: 0.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 14:24:03 +01:00
dependabot[bot] d6ed2b770b Bump indicatif from 0.17.11 to 0.18.0 (#5924)
Bumps [indicatif](https://github.com/console-rs/indicatif) from 0.17.11 to 0.18.0.
- [Release notes](https://github.com/console-rs/indicatif/releases)
- [Commits](https://github.com/console-rs/indicatif/compare/0.17.11...0.18.0)

---
updated-dependencies:
- dependency-name: indicatif
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 14:23:39 +01:00
dependabot[bot] 7c18a3dced Bump mock_instant from 0.5.3 to 0.6.0 (#5930)
Bumps [mock_instant](https://github.com/museun/mock_instant) from 0.5.3 to 0.6.0.
- [Commits](https://github.com/museun/mock_instant/commits/v0.6.0)

---
updated-dependencies:
- dependency-name: mock_instant
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 14:20:22 +01:00
dependabot[bot] 09475ab4e0 build(deps): bump mikefarah/yq from 4.45.4 to 4.47.1 (#5911)
Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.45.4 to 4.47.1.
- [Release notes](https://github.com/mikefarah/yq/releases)
- [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt)
- [Commits](https://github.com/mikefarah/yq/compare/v4.45.4...v4.47.1)

---
updated-dependencies:
- dependency-name: mikefarah/yq
  dependency-version: 4.47.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:36:39 +01:00
dependabot[bot] b7606cd2ef Bump defguard_wireguard_rs from v0.4.7 to v0.7.5 (#5928)
Bumps [defguard_wireguard_rs](https://github.com/DefGuard/wireguard-rs) from v0.4.7 to v0.7.5.
- [Release notes](https://github.com/DefGuard/wireguard-rs/releases)
- [Commits](https://github.com/DefGuard/wireguard-rs/compare/ef1cf3714629bf5016fb38cbb7320451dc69fb09...d090d2249e5bb3d4154f07de098387e2ab69bfdc)

---
updated-dependencies:
- dependency-name: defguard_wireguard_rs
  dependency-version: d090d2249e5bb3d4154f07de098387e2ab69bfdc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:35:42 +01:00
dependabot[bot] 006a57312d Bump tokio from 1.46.1 to 1.47.1 (#5929)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.46.1 to 1.47.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.46.1...tokio-1.47.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.47.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:35:18 +01:00
dependabot[bot] 9b5aded8a5 build(deps): bump actions/download-artifact from 4 to 5 (#5939)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:33:31 +01:00
dependabot[bot] f4a69636fe build(deps): bump actions/first-interaction from 1 to 3 (#5950)
Bumps [actions/first-interaction](https://github.com/actions/first-interaction) from 1 to 3.
- [Release notes](https://github.com/actions/first-interaction/releases)
- [Commits](https://github.com/actions/first-interaction/compare/v1...v3)

---
updated-dependencies:
- dependency-name: actions/first-interaction
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:31:29 +01:00
dependabot[bot] 0463d88646 Bump slab from 0.4.10 to 0.4.11 (#5952)
Bumps [slab](https://github.com/tokio-rs/slab) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/tokio-rs/slab/releases)
- [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/slab/compare/v0.4.10...v0.4.11)

---
updated-dependencies:
- dependency-name: slab
  dependency-version: 0.4.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:31:02 +01:00
dependabot[bot] 534bf5d824 build(deps): bump actions/setup-java from 4 to 5 (#5975)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:30:00 +01:00
dependabot[bot] 34684b14db Bump sha.js from 2.4.11 to 2.4.12 (#5983)
Bumps [sha.js](https://github.com/crypto-browserify/sha.js) from 2.4.11 to 2.4.12.
- [Changelog](https://github.com/browserify/sha.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/sha.js/compare/v2.4.11...v2.4.12)

---
updated-dependencies:
- dependency-name: sha.js
  dependency-version: 2.4.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 13:29:18 +01:00
Jędrzej Stuczyński b2266d04ef chore: internal hidden command to force advance nyx epoch (#5964) 2025-08-29 11:41:22 +01:00
Jędrzej Stuczyński 911b365609 chore: purge temp databases on build (#5984)
* purge any temp databases on build

* updated min rust version

* fixed clippy::manual_abs_diff' in verloc due to updated msrv

* wasm
2025-08-29 11:41:08 +01:00
Jędrzej Stuczyński e9acc014ed feat: credential proxy deposit pool (#5945)
* chore: rename VpnApiError to CredentialProxyError

* reorganise deposit flow

* updated sql tables et al.

* insert information about deposit usage failure

* remove old deposit maker

* nym credential proxy to monitor quorum state to stop issuance if it'd fail

* clippy

* target lock new modules

* windows clippy

* renamed migration file due to rebasing
2025-08-29 09:39:57 +01:00
Jędrzej Stuczyński 0f66e5a154 bugfix: make sure tables are removed in correct order to not trigger FK constraint issue (#5987) 2025-08-29 09:03:22 +01:00
Bogdan-Ștefan Neacşu f8337d9b38 Wireguard metadata client library (#5943) 2025-08-28 15:43:46 +03:00
Bogdan-Ștefan Neacşu 4fb252c44b Wireguard private metadata (#5915) 2025-08-28 15:14:52 +03:00
benedettadavico 66d0296f47 update dockerfile to pg 2025-08-26 16:02:11 +02:00
benedettadavico 03bbbf44e9 ns api ci fix 2025-08-26 16:02:11 +02:00
196 changed files with 10623 additions and 1656 deletions
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -6,7 +6,7 @@ jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
- uses: actions/first-interaction@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Thank you for raising this issue'
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4
- name: Download report from previous job
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: report
path: .github/workflows/support-files/notifications
@@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v4
- name: Install Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "17"
@@ -91,7 +91,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Download binary artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: nyms5-apk-arch64
path: apk
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
+9 -5
View File
@@ -34,18 +34,22 @@ 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: Set GIT_TAG variable
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
- name: Set RELEASE_TAG variable
- name: Initialise 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 }}" >> $GITHUB_ENV
- name: New env vars
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
@@ -65,6 +69,6 @@ jobs:
- name: BuildAndPushImageOnHarbor
run: |
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile-pg . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
Generated
+291 -70
View File
@@ -847,6 +847,12 @@ dependencies = [
"serde",
]
[[package]]
name = "binstring"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef"
[[package]]
name = "bip32"
version = "0.5.3"
@@ -942,6 +948,17 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "blake2b_simd"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]]
name = "blake3"
version = "1.8.2"
@@ -1332,6 +1349,17 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "coarsetime"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4"
dependencies = [
"libc",
"wasix",
"wasm-bindgen",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
@@ -1345,7 +1373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -1368,19 +1396,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width 0.2.1",
"windows-sys 0.59.0",
]
[[package]]
name = "console"
version = "0.16.0"
@@ -1868,6 +1883,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "ct-codecs"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8"
[[package]]
name = "ctr"
version = "0.9.2"
@@ -1898,7 +1919,7 @@ dependencies = [
"openssl-probe",
"openssl-sys",
"schannel",
"socket2",
"socket2 0.5.10",
"windows-sys 0.59.0",
]
@@ -2370,7 +2391,7 @@ version = "0.1.0"
dependencies = [
"cosmwasm-std",
"quote",
"syn 1.0.109",
"syn 2.0.104",
]
[[package]]
@@ -2424,6 +2445,16 @@ dependencies = [
"signature",
]
[[package]]
name = "ed25519-compact"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190"
dependencies = [
"ct-codecs",
"getrandom 0.2.16",
]
[[package]]
name = "ed25519-consensus"
version = "2.1.0"
@@ -2488,6 +2519,8 @@ dependencies = [
"ff",
"generic-array 0.14.7",
"group",
"hkdf",
"pem-rfc7468",
"pkcs8",
"rand_core 0.6.4",
"sec1",
@@ -3349,6 +3382,30 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "hmac-sha1-compact"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3"
[[package]]
name = "hmac-sha256"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"
dependencies = [
"digest 0.10.7",
]
[[package]]
name = "hmac-sha512"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280"
dependencies = [
"digest 0.10.7",
]
[[package]]
name = "home"
version = "0.5.11"
@@ -3492,7 +3549,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
@@ -3582,7 +3639,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
@@ -3827,26 +3884,13 @@ dependencies = [
"serde",
]
[[package]]
name = "indicatif"
version = "0.17.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
dependencies = [
"console 0.15.11",
"number_prefix",
"portable-atomic",
"unicode-width 0.2.1",
"web-time",
]
[[package]]
name = "indicatif"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd"
dependencies = [
"console 0.16.0",
"console",
"portable-atomic",
"unicode-width 0.2.1",
"unit-prefix",
@@ -3947,7 +3991,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
"socket2",
"socket2 0.5.10",
"widestring",
"windows-sys 0.48.0",
"winreg",
@@ -3986,7 +4030,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -4088,6 +4132,32 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jwt-simple"
version = "0.12.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a"
dependencies = [
"anyhow",
"binstring",
"blake2b_simd",
"coarsetime",
"ct-codecs",
"ed25519-compact",
"hmac-sha1-compact",
"hmac-sha256",
"hmac-sha512",
"k256",
"p256",
"p384",
"rand 0.8.5",
"serde",
"serde_json",
"superboring",
"thiserror 2.0.12",
"zeroize",
]
[[package]]
name = "k256"
version = "0.13.4"
@@ -4519,9 +4589,9 @@ dependencies = [
[[package]]
name = "mock_instant"
version = "0.5.3"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1d4c44418358edcac6e1d9ce59cea7fb38052429c7704033f1196f0c179e6a"
checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6"
[[package]]
name = "moka"
@@ -4817,12 +4887,6 @@ dependencies = [
"libc",
]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.64"
@@ -5211,6 +5275,7 @@ dependencies = [
name = "nym-client-core-gateways-storage"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"cosmrs",
"nym-crypto",
@@ -5229,6 +5294,7 @@ dependencies = [
name = "nym-client-core-surb-storage"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"dashmap",
"nym-crypto",
@@ -5371,7 +5437,7 @@ dependencies = [
[[package]]
name = "nym-credential-proxy"
version = "0.1.7"
version = "0.1.8"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -5389,6 +5455,7 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-signer-check",
"nym-http-api-common",
"nym-network-defaults",
"nym-validator-client",
@@ -5439,6 +5506,7 @@ dependencies = [
name = "nym-credential-storage"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"bincode",
"log",
@@ -5553,6 +5621,7 @@ dependencies = [
"generic-array 0.14.7",
"hkdf",
"hmac",
"jwt-simple",
"nym-pemstore",
"nym-sphinx-types",
"rand 0.8.5",
@@ -5641,14 +5710,6 @@ dependencies = [
"time",
]
[[package]]
name = "nym-execute"
version = "0.1.0"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "nym-exit-policy"
version = "0.1.0"
@@ -5718,6 +5779,7 @@ dependencies = [
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-private-metadata-server",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
@@ -5807,6 +5869,7 @@ dependencies = [
name = "nym-gateway-stats-storage"
version = "0.1.0"
dependencies = [
"anyhow",
"nym-node-metrics",
"nym-sphinx",
"nym-statistics-common",
@@ -5822,6 +5885,7 @@ dependencies = [
name = "nym-gateway-storage"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"bincode",
"defguard_wireguard_rs",
@@ -6234,7 +6298,7 @@ dependencies = [
"hkdf",
"human-repr",
"humantime-serde",
"indicatif 0.17.11",
"indicatif",
"ipnetwork",
"lioness",
"nym-bin-common",
@@ -6350,7 +6414,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "3.3.0"
version = "3.3.1"
dependencies = [
"ammonia",
"anyhow",
@@ -6672,6 +6736,26 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-signers-monitor"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"humantime",
"itertools 0.14.0",
"nym-bin-common",
"nym-ecash-signer-check",
"nym-network-defaults",
"nym-task",
"nym-validator-client",
"time",
"tokio",
"tracing",
"url",
"zulip-client",
]
[[package]]
name = "nym-socks5-client"
version = "1.1.61"
@@ -6815,10 +6899,12 @@ dependencies = [
name = "nym-sphinx-addressing"
version = "0.1.0"
dependencies = [
"bincode",
"nym-crypto",
"nym-sphinx-types",
"rand 0.8.5",
"serde",
"serde_json",
"thiserror 2.0.12",
]
@@ -7108,6 +7194,22 @@ dependencies = [
"x25519-dalek",
]
[[package]]
name = "nym-upgrade-mode-check"
version = "0.1.0"
dependencies = [
"anyhow",
"jwt-simple",
"nym-crypto",
"nym-http-api-client",
"reqwest 0.12.22",
"serde",
"serde_json",
"thiserror 2.0.12",
"time",
"tracing",
]
[[package]]
name = "nym-validator-client"
version = "0.1.0"
@@ -7310,6 +7412,68 @@ 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"
@@ -7390,6 +7554,7 @@ dependencies = [
name = "nyxd-scraper"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"const_format",
"cosmrs",
@@ -7613,6 +7778,18 @@ dependencies = [
"sha2 0.10.9",
]
[[package]]
name = "p384"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2 0.10.9",
]
[[package]]
name = "pairing"
version = "0.23.0"
@@ -8181,7 +8358,7 @@ dependencies = [
"quinn-udp",
"rustc-hash",
"rustls 0.23.29",
"socket2",
"socket2 0.5.10",
"thiserror 2.0.12",
"tokio",
"tracing",
@@ -8218,9 +8395,9 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"socket2 0.5.10",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -8593,6 +8770,7 @@ dependencies = [
"pkcs1",
"pkcs8",
"rand_core 0.6.4",
"sha2 0.10.9",
"signature",
"spki",
"subtle 2.6.1",
@@ -8704,7 +8882,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -9426,9 +9604,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "sluice"
@@ -9504,6 +9682,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "sphinx-packet"
version = "0.6.0"
@@ -9872,6 +10060,19 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142"
[[package]]
name = "superboring"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f"
dependencies = [
"getrandom 0.2.16",
"hmac-sha256",
"hmac-sha512",
"rand 0.8.5",
"rsa",
]
[[package]]
name = "syn"
version = "1.0.109"
@@ -9988,7 +10189,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix 1.0.8",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -10112,11 +10313,11 @@ dependencies = [
"bip39",
"bs58",
"clap",
"console 0.15.11",
"console",
"cw-utils",
"dkg-bypass-contract",
"humantime",
"indicatif 0.17.11",
"indicatif",
"nym-bin-common",
"nym-coconut-dkg-common",
"nym-compact-ecash",
@@ -10296,9 +10497,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.46.1"
version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [
"backtrace",
"bytes",
@@ -10309,10 +10510,10 @@ dependencies = [
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"socket2 0.6.0",
"tokio-macros",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -10346,7 +10547,7 @@ dependencies = [
"postgres-protocol",
"postgres-types",
"rand 0.9.2",
"socket2",
"socket2 0.5.10",
"tokio",
"tokio-util",
"whoami",
@@ -10522,7 +10723,7 @@ dependencies = [
"percent-encoding",
"pin-project",
"prost",
"socket2",
"socket2 0.5.10",
"tokio",
"tokio-stream",
"tower 0.4.13",
@@ -10673,7 +10874,7 @@ version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200"
dependencies = [
"indicatif 0.18.0",
"indicatif",
"tracing",
"tracing-core",
"tracing-subscriber",
@@ -11369,6 +11570,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasix"
version = "0.12.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d"
dependencies = [
"wasi 0.11.1+wasi-snapshot-preview1",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
@@ -11669,7 +11879,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -11762,6 +11972,17 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-registry"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.3.4"
+12 -8
View File
@@ -53,7 +53,6 @@ members = [
"common/ecash-signer-check",
"common/ecash-signer-check-types",
"common/ecash-time",
"common/execute",
"common/exit-policy",
"common/gateway-requests",
"common/gateway-stats-storage",
@@ -96,12 +95,16 @@ members = [
"common/ticketbooks-merkle",
"common/topology",
"common/tun",
"common/types",
"common/types", "common/upgrade-mode-check",
"common/verloc",
"common/wasm/client-core",
"common/wasm/storage",
"common/wasm/utils",
"common/wireguard",
"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",
@@ -119,7 +122,7 @@ members = [
"nym-node-status-api/nym-node-status-client",
"nym-node/nym-node-metrics",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-outfox", "nym-signers-monitor",
"nym-statistics-api",
"nym-validator-rewarder",
"nyx-chain-watcher",
@@ -178,7 +181,7 @@ homepage = "https://nymtech.net"
documentation = "https://nymtech.net"
edition = "2021"
license = "Apache-2.0"
rust-version = "1.80"
rust-version = "1.81"
readme = "README.md"
[workspace.dependencies]
@@ -219,7 +222,7 @@ clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.2"
comfy-table = "7.1.4"
console = "0.15.11"
console = "0.16.0"
console-subscriber = "0.4.1"
console_error_panic_hook = "0.1"
const-str = "0.5.6"
@@ -266,11 +269,12 @@ humantime = "2.2.0"
humantime-serde = "1.1.1"
hyper = "1.6.0"
hyper-util = "0.1"
indicatif = "0.17.11"
indicatif = "0.18.0"
inquire = "0.6.2"
ip_network = "0.4.1"
ipnetwork = "0.20"
itertools = "0.14.0"
jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] }
k256 = "0.13"
lazy_static = "1.5.0"
ledger-transport = "0.10.0"
@@ -322,14 +326,14 @@ sqlx = "0.8.6"
strum = "0.27.2"
strum_macros = "0.27.2"
subtle-encoding = "0.5"
syn = "1"
syn = "2"
sysinfo = "0.37.0"
tap = "1.0.1"
tar = "0.4.44"
tempfile = "3.20"
thiserror = "2.0"
time = "0.3.41"
tokio = "1.45"
tokio = "1.47"
tokio-postgres = "0.7"
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
@@ -3,6 +3,7 @@ name = "nym-client-core-gateways-storage"
version = "0.1.0"
edition = "2021"
license.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -26,6 +27,7 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"]
optional = true
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+13 -4
View File
@@ -2,23 +2,30 @@
// SPDX-License-Identifier: Apache-2.0
#[tokio::main]
async fn main() {
async fn main() -> anyhow::Result<()> {
#[cfg(feature = "fs-gateways-storage")]
{
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/gateways-storage-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.expect("Failed to create SQLx database connection");
.context("Failed to create SQLx database connection")?;
sqlx::migrate!("./fs_gateways_migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
.context("Failed to perform SQLx migrations")?;
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -28,4 +35,6 @@ async fn main() {
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
}
Ok(())
}
@@ -175,6 +175,7 @@ impl MixTrafficController {
},
None => {
tracing::trace!("MixTrafficController, client request channel closed");
break
}
},
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::helpers::get_time_now;
use crate::client::replies::{
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
};
@@ -22,7 +23,7 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
use nym_task::TaskClient;
use std::collections::HashSet;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::time::Duration;
use tracing::*;
// The interval at which we check for stale buffers
@@ -54,7 +55,7 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
stats_tx: ClientStatsSender,
// Periodically check for stale buffers to clean up
last_stale_check: Instant,
last_stale_check: crate::client::helpers::Instant,
}
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
@@ -154,7 +155,7 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
}
fn cleanup_stale_buffers(&mut self) {
let now = Instant::now();
let now = get_time_now();
if now - self.last_stale_check > STALE_BUFFER_CHECK_INTERVAL {
self.last_stale_check = now;
self.message_receiver
@@ -190,7 +191,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
message_sender: None,
recently_reconstructed: HashSet::new(),
stats_tx,
last_stale_check: Instant::now(),
last_stale_check: get_time_now(),
})),
reply_key_storage,
reply_controller_sender,
@@ -30,6 +30,7 @@ optional = true
path = "../../../sqlx-pool-guard"
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+7 -4
View File
@@ -2,23 +2,24 @@
// SPDX-License-Identifier: Apache-2.0
#[tokio::main]
async fn main() {
async fn main() -> anyhow::Result<()> {
#[cfg(feature = "fs-surb-storage")]
{
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/fs-surbs-example.sqlite");
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.expect("Failed to create SQLx database connection");
.context("Failed to create SQLx database connection")?;
sqlx::migrate!("./fs_surbs_migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
.context("Failed to perform SQLx migrations")?;
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -28,4 +29,6 @@ async fn main() {
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
}
Ok(())
}
@@ -28,7 +28,7 @@ use cosmrs::proto::cosmwasm::wasm::v1::{
QueryRawContractStateResponse, QuerySmartContractStateRequest, QuerySmartContractStateResponse,
};
use cosmrs::tendermint::{block, chain, Hash};
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
use cosmrs::{AccountId, Coin as CosmosCoin};
use prost::Message;
use serde::{Deserialize, Serialize};
@@ -556,23 +556,12 @@ pub trait CosmWasmClient: TendermintRpcClient {
Ok(serde_json::from_slice(&res.data)?)
}
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
// of writing this, the option does not work
// TODO: we should really stop using the `tx` argument here and use `tx_bytes` exlusively,
// however, at the time of writing this update, while our QA and mainnet networks do support it,
// sandbox is still running old version of wasmd that lacks support for `tx_bytes`
#[allow(deprecated)]
async fn query_simulate(
&self,
tx: Option<Tx>,
tx_bytes: Vec<u8>,
) -> Result<SimulateResponse, NyxdError> {
async fn query_simulate(&self, tx_bytes: Vec<u8>) -> Result<SimulateResponse, NyxdError> {
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".to_owned());
let req = SimulateRequest {
tx: tx.map(Into::into),
tx_bytes,
..Default::default()
};
let res = self
@@ -81,17 +81,14 @@ where
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
signatures: vec![Vec::new()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
// for completion sake, once we're able to transition into using `tx_bytes`,
// we might want to use something like this instead:
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
// body_bytes: partial_tx.body.into_bytes().unwrap(),
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
// signatures: partial_tx.signatures,
// }
// .into();
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
body_bytes: partial_tx.body.into_bytes()?,
auth_info_bytes: partial_tx.auth_info.into_bytes()?,
signatures: partial_tx.signatures,
}
.into();
self.query_simulate(tx_raw.to_bytes()?).await
}
async fn upload(
+3
View File
@@ -4,6 +4,7 @@
use clap::{Args, Subcommand};
pub mod ecash;
pub mod nyx;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -16,4 +17,6 @@ pub struct Internal {
pub enum InternalCommands {
/// Ecash related internal commands
Ecash(ecash::InternalEcash),
Nyx(nyx::InternalNyx),
}
@@ -0,0 +1,116 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use anyhow::bail;
use clap::Parser;
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::reward_params::NodeRewardingParameters;
use nym_mixnet_contract_common::{
EpochRewardedSet, EpochState, NodeId, RewardingParams, RoleAssignment,
};
use nym_validator_client::nyxd::contract_traits::mixnet_query_client::MixnetQueryClientExt;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
use rand::prelude::*;
use rand::thread_rng;
#[derive(Debug, Parser)]
pub struct Args {}
fn choose_new_nodes(
params: &RewardingParams,
rewarded_set: &EpochRewardedSet,
role: Role,
) -> Vec<NodeId> {
let mut rng = thread_rng();
match role {
Role::EntryGateway => rewarded_set
.assignment
.entry_gateways
.choose_multiple(&mut rng, params.rewarded_set.entry_gateways as usize)
.copied()
.collect(),
Role::Layer1 => rewarded_set
.assignment
.layer1
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
.copied()
.collect(),
Role::Layer2 => rewarded_set
.assignment
.layer2
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
.copied()
.collect(),
Role::Layer3 => rewarded_set
.assignment
.layer3
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
.copied()
.collect(),
Role::ExitGateway => rewarded_set
.assignment
.exit_gateways
.choose_multiple(&mut rng, params.rewarded_set.exit_gateways as usize)
.copied()
.collect(),
Role::Standby => rewarded_set
.assignment
.standby
.choose_multiple(&mut rng, params.rewarded_set.standby as usize)
.copied()
.collect(),
}
}
pub async fn force_advance_epoch(_: Args, client: SigningClient) -> anyhow::Result<()> {
let current_epoch = client.get_current_interval_details().await?;
let epoch_status = client.get_current_epoch_status().await?;
if epoch_status.being_advanced_by.as_str() != client.address().to_string() {
bail!(
"this client is not authorised to perform any epoch operations. we need {}",
client.address()
)
}
let rewarding_params = client.get_rewarding_parameters().await?;
let current_rewarded_set = client.get_rewarded_set().await?;
if !current_epoch.is_current_epoch_over {
println!("the current epoch is not over yet - there's nothing to do")
}
// is this most efficient? no. but it's simple
loop {
let epoch_status = client.get_current_epoch_status().await?;
match epoch_status.state {
EpochState::InProgress => break,
EpochState::Rewarding { final_node_id, .. } => {
println!("rewarding {final_node_id} with big fat 0...");
client
.reward_node(
final_node_id,
NodeRewardingParameters::new(Default::default(), Default::default()),
None,
)
.await?;
}
EpochState::ReconcilingEvents => {
println!("trying to reconcile events...");
client.reconcile_epoch_events(None, None).await?;
}
EpochState::RoleAssignment { next } => {
let nodes = choose_new_nodes(&rewarding_params, &current_rewarded_set, next);
println!("assigning {nodes:?} as {next}");
client
.assign_roles(RoleAssignment { role: next, nodes }, None)
.await?;
}
}
}
Ok(())
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod force_advance_epoch;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct InternalNyx {
#[clap(subcommand)]
pub command: InternalNyxCommands,
}
#[derive(Debug, Subcommand)]
pub enum InternalNyxCommands {
/// Attempt to force advance the current epoch
ForceAdvanceEpoch(force_advance_epoch::Args),
}
+2
View File
@@ -3,6 +3,7 @@ name = "nym-credential-storage"
version = "0.1.0"
edition = "2021"
license.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -33,6 +34,7 @@ features = ["rt-multi-thread", "net", "signal", "fs"]
[build-dependencies]
anyhow = { workspace = true }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
+13 -4
View File
@@ -3,22 +3,29 @@
* SPDX-License-Identifier: Apache-2.0
*/
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
async fn main() -> anyhow::Result<()> {
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/coconut-credential-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.expect("Failed to create SQLx database connection");
.context("Failed to create SQLx database connection")?;
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
.context("Failed to perform SQLx migrations")?;
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -27,4 +34,6 @@ async fn main() {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
@@ -110,14 +110,14 @@ FROM ecash_ticketbook;
-- 6. finally swap out the old tables
-- drop old tables
DROP TABLE expiration_date_signatures;
DROP TABLE pending_issuance;
DROP TABLE ecash_ticketbook;
DROP TABLE expiration_date_signatures;
-- rename new tables
ALTER TABLE expiration_date_signatures_new
RENAME TO expiration_date_signatures;
ALTER TABLE pending_issuance_new
RENAME TO pending_issuance;
ALTER TABLE ecash_ticketbook_new
RENAME TO ecash_ticketbook;
RENAME TO ecash_ticketbook;
ALTER TABLE expiration_date_signatures_new
RENAME TO expiration_date_signatures;
+16
View File
@@ -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<i64>;
}
#[async_trait]
impl TicketVerifier for CredentialVerifier {
async fn verify(&mut self) -> Result<i64> {
self.verify().await
}
}
@@ -108,7 +108,7 @@ impl IssuanceTicketBook {
signing_request.withdrawal_request.clone(),
self.deposit_id,
request_signature,
signing_request.ecash_pub_key.clone(),
signing_request.ecash_pub_key,
signing_request.expiration_date,
signing_request.ticketbook_type,
)
+2
View File
@@ -18,6 +18,7 @@ digest = { workspace = true, optional = true }
generic-array = { workspace = true, optional = true }
hkdf = { workspace = true, optional = true }
hmac = { workspace = true, optional = true }
jwt-simple = { workspace = true, optional = true }
cipher = { workspace = true, optional = true }
x25519-dalek = { workspace = true, features = ["static_secrets"], optional = true }
ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
@@ -39,6 +40,7 @@ rand_chacha = { workspace = true }
[features]
default = []
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
naive_jwt = ["asymmetric", "jwt-simple"]
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
+82 -5
View File
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
pub use ed25519_dalek::SignatureError;
use ed25519_dalek::{SecretKey, Signer, SigningKey};
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use ed25519_dalek::Signer;
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
@@ -13,6 +14,9 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg(feature = "serde")]
pub mod serde_helpers;
#[cfg(feature = "serde")]
pub use serde_helpers::*;
#[cfg(feature = "sphinx")]
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
@@ -81,8 +85,8 @@ impl KeyPair {
}
}
pub fn from_secret(secret: SecretKey, index: u32) -> Self {
let ed25519_signing_key = SigningKey::from(secret);
pub fn from_secret(secret: ed25519_dalek::SecretKey, index: u32) -> Self {
let ed25519_signing_key = ed25519_dalek::SigningKey::from(secret);
KeyPair {
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
@@ -276,7 +280,7 @@ impl Display for PrivateKey {
impl<'a> From<&'a PrivateKey> for PublicKey {
fn from(pk: &'a PrivateKey) -> Self {
PublicKey(SigningKey::from_bytes(&pk.0).verifying_key())
PublicKey(ed25519_dalek::SigningKey::from_bytes(&pk.0).verifying_key())
}
}
@@ -320,7 +324,7 @@ impl PrivateKey {
}
pub fn sign<M: AsRef<[u8]>>(&self, message: M) -> Signature {
let signing_key: SigningKey = self.0.into();
let signing_key: ed25519_dalek::SigningKey = self.0.into();
let sig = signing_key.sign(message.as_ref());
Signature(sig)
}
@@ -425,9 +429,57 @@ impl<'d> Deserialize<'d> for Signature {
}
}
#[cfg(feature = "naive_jwt")]
impl PublicKey {
pub fn to_jwt_compatible_key(&self) -> jwt_simple::algorithms::Ed25519PublicKey {
(*self).into()
}
}
#[cfg(feature = "naive_jwt")]
impl From<PublicKey> for jwt_simple::algorithms::Ed25519PublicKey {
fn from(value: PublicKey) -> Self {
// SAFETY: we have a valid ed25519 pubkey, we're just changing to a different library wrapper
#[allow(clippy::unwrap_used)]
jwt_simple::algorithms::Ed25519PublicKey::from_bytes(&value.to_bytes()).unwrap()
}
}
#[cfg(feature = "naive_jwt")]
impl PrivateKey {
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
let pub_key = self.public_key();
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
bytes[..SECRET_KEY_LENGTH]
.copy_from_slice(zeroize::Zeroizing::new(self.to_bytes()).as_ref());
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&pub_key.to_bytes());
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
#[allow(clippy::unwrap_used)]
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
}
}
#[cfg(feature = "naive_jwt")]
impl KeyPair {
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
bytes[..SECRET_KEY_LENGTH]
.copy_from_slice(zeroize::Zeroizing::new(self.private_key.to_bytes()).as_ref());
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&self.public_key.to_bytes());
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
#[allow(clippy::unwrap_used)]
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::thread_rng;
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
@@ -438,4 +490,29 @@ mod tests {
assert_zeroize::<PrivateKey>();
assert_zeroize_on_drop::<PrivateKey>();
}
#[test]
#[cfg(all(feature = "naive_jwt", feature = "rand"))]
fn check_jwt_key_compat_conversion() {
let mut rng = thread_rng();
let keys = KeyPair::new(&mut rng);
let jwt_keys = keys.to_jwt_compatible_keys();
// internally they're represented by hidden `Edwards25519KeyPair` (plus key_id)
// which has way nicer API for assertions
let jwt_keys_inner =
jwt_simple::algorithms::Edwards25519KeyPair::from_bytes(&jwt_keys.to_bytes()).unwrap();
let compact_ed25519 = jwt_keys_inner.as_ref();
assert!(compact_ed25519
.sk
.validate_public_key(&compact_ed25519.pk)
.is_ok());
let dummy_message = "hello world";
let sig1 = keys.private_key.sign(dummy_message).to_bytes();
let sig2 = compact_ed25519.sk.sign(dummy_message, None).to_vec();
assert_eq!(sig1.to_vec(), sig2);
}
}
@@ -157,6 +157,14 @@ impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC> {
pub fn malformed_details(&self) -> bool {
self.information.parse().is_err()
}
pub fn try_get_test_result(&self) -> Option<&SignerTestResult<LS, TS, LC, TC>> {
if let SignerStatus::Tested { result } = &self.status {
Some(result)
} else {
None
}
}
}
impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC>
+37 -4
View File
@@ -15,6 +15,9 @@ use nym_validator_client::ecash::models::EcashSignerStatusResponse;
use nym_validator_client::models::{
ChainBlocksStatusResponse, ChainStatusResponse, SignerInformationResponse,
};
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
ContractVKShare, DealerDetails, Epoch,
};
mod client_check;
pub mod error;
@@ -48,7 +51,22 @@ pub async fn check_signers(
check_signers_with_client(&client).await
}
pub struct DkgDetails {
pub dkg_epoch: Epoch,
pub threshold: Option<u64>,
pub network_dealers: Vec<DealerDetails>,
pub submitted_shared: HashMap<u64, ContractVKShare>,
}
pub async fn check_signers_with_client<C>(client: &C) -> Result<SignersTestResult, SignerCheckError>
where
C: DkgQueryClient + Sync,
{
let dkg_details = dkg_details_with_client(client).await?;
check_known_dealers(dkg_details).await
}
pub async fn dkg_details_with_client<C>(client: &C) -> Result<DkgDetails, SignerCheckError>
where
C: DkgQueryClient + Sync,
{
@@ -79,16 +97,31 @@ where
.map(|share| (share.node_index, share))
.collect();
Ok(DkgDetails {
dkg_epoch,
threshold,
network_dealers: dealers,
submitted_shared: shares,
})
}
pub async fn check_known_dealers(
dkg_details: DkgDetails,
) -> Result<SignersTestResult, SignerCheckError> {
// 6. for each dealer attempt to perform the checks
let results = dealers
let results = dkg_details
.network_dealers
.into_iter()
.map(|d| {
let share = shares.get(&d.assigned_index);
check_client(d, dkg_epoch.epoch_id, share)
let share = dkg_details.submitted_shared.get(&d.assigned_index);
check_client(d, dkg_details.dkg_epoch.epoch_id, share)
})
.collect::<FuturesUnordered<_>>()
.collect::<Vec<_>>()
.await;
Ok(SignersTestResult { threshold, results })
Ok(SignersTestResult {
threshold: dkg_details.threshold,
results,
})
}
-12
View File
@@ -1,12 +0,0 @@
[package]
name = "nym-execute"
version = "0.1.0"
edition = "2021"
license.workspace = true
[lib]
proc-macro = true
[dependencies]
syn = { workspace = true, features = ["full"] }
quote = { workspace = true }
-110
View File
@@ -1,110 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, Block, ExprMethodCall, FnArg, Ident, ItemFn, LitStr, ReturnType, Token,
VisPublic, Visibility,
};
#[proc_macro_attribute]
pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
let f = parse_macro_input!(item as ItemFn);
let target = parse_macro_input!(attr as LitStr).value();
let cl = if target == "mixnet" {
quote! {self.mixnet_contract_address()}
} else if target == "vesting" {
quote! {self.vesting_contract_address()}
} else {
panic!("Only `mixnet` and `vesting` targets are supported!")
};
let cl = proc_macro::TokenStream::from(cl);
let cl = parse_macro_input!(cl as ExprMethodCall);
let orig_f = f.clone();
let mut execute_f = f.clone();
let mut simulate_f = f.clone();
let name = f.sig.ident;
let name_str = name.to_string();
let call_args = f.sig.inputs.into_iter().filter_map(|arg| match arg {
FnArg::Receiver(_) => None,
FnArg::Typed(arg) => Some(arg.pat),
});
let execute_args = call_args.clone();
let simulate_args = call_args;
execute_f.sig.asyncness = Some(Token![async](execute_f.sig.ident.span()));
simulate_f.sig.asyncness = Some(Token![async](simulate_f.sig.ident.span()));
execute_f.vis = Visibility::Public(VisPublic {
pub_token: Token![pub](execute_f.sig.ident.span()),
});
simulate_f.vis = Visibility::Public(VisPublic {
pub_token: Token![pub](simulate_f.sig.ident.span()),
});
execute_f.sig.ident = Ident::new(
&format!("execute{}", execute_f.sig.ident),
execute_f.sig.ident.span(),
);
simulate_f.sig.ident = Ident::new(
&format!("simulate{}", simulate_f.sig.ident),
simulate_f.sig.ident.span(),
);
let execute_output = quote! {
-> Result<ExecuteResult, NyxdError>
};
let o_ts = proc_macro::TokenStream::from(execute_output);
execute_f.sig.output = parse_macro_input!(o_ts as ReturnType);
let simulate_output = quote! {
-> Result<SimulateResponse, NyxdError>
};
let o_ts = proc_macro::TokenStream::from(simulate_output);
simulate_f.sig.output = parse_macro_input!(o_ts as ReturnType);
let simulate_block = quote! {
{
let (msg, _fee) = self.#name(#(#simulate_args),*);
let msg = self.wrap_contract_execute_message(
#cl,
&msg,
vec![],
)?;
self.simulate(vec![msg]).await
}
};
let ts = proc_macro::TokenStream::from(simulate_block);
simulate_f.block = Box::new(parse_macro_input!(ts as Block));
let execute_block = quote! {
{
let (req, fee) = self.#name(#(#execute_args),*);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.execute(
self.address(),
#cl,
&req,
fee,
#name_str,
vec![],
)
.await
}
};
let ts = proc_macro::TokenStream::from(execute_block);
execute_f.block = Box::new(parse_macro_input!(ts as Block));
let out = quote! {
#orig_f
#execute_f
#simulate_f
};
out.into()
}
+1 -1
View File
@@ -89,7 +89,7 @@ mod tests {
.unwrap();
let blind_sig = issue(
keypair.secret_key(),
sig_req.ecash_pub_key.clone(),
sig_req.ecash_pub_key,
&sig_req.withdrawal_request,
expiration_date.ecash_unix_timestamp(),
issuance.ticketbook_type().encode(),
+2
View File
@@ -7,6 +7,7 @@ homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
sqlx = { workspace = true, features = [
@@ -27,6 +28,7 @@ nym-statistics-common = { path = "../statistics" }
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+13 -4
View File
@@ -1,22 +1,29 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
async fn main() -> anyhow::Result<()> {
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/gateway-stats-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.expect("Failed to create SQLx database connection");
.context("Failed to create SQLx database connection")?;
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
.context("Failed to perform SQLx migrations")?;
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -25,4 +32,6 @@ async fn main() {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
+2
View File
@@ -7,6 +7,7 @@ homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
async-trait = { workspace = true }
@@ -31,6 +32,7 @@ nym-gateway-requests = { path = "../gateway-requests" }
nym-sphinx = { path = "../nymsphinx" }
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+13 -4
View File
@@ -1,22 +1,29 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
async fn main() -> anyhow::Result<()> {
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/gateway-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.expect("Failed to create SQLx database connection");
.context("Failed to create SQLx database connection")?;
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
.context("Failed to perform SQLx migrations")?;
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -25,4 +32,6 @@ async fn main() {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
+6
View File
@@ -136,6 +136,7 @@
//! ```
#![warn(missing_docs)]
pub use reqwest::ClientBuilder as ReqwestClientBuilder;
pub use reqwest::StatusCode;
use crate::path::RequestPath;
@@ -150,6 +151,7 @@ use reqwest::{RequestBuilder, Response};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::net::IpAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use thiserror::Error;
@@ -157,6 +159,7 @@ use tracing::{debug, instrument, warn};
#[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;
#[cfg(feature = "tunneling")]
@@ -411,11 +414,14 @@ impl ClientBuilder {
//
// I am going to leave these here anyways so that removing a decompression method
// from the features list will throw an error if it is not also removed here.
warn!("FORCING IPv4 CONNECTIONS");
reqwest::ClientBuilder::new()
.gzip(true)
.deflate(true)
.brotli(true)
.zstd(true)
.local_address(IpAddr::from_str("0.0.0.0:0").unwrap())
};
ClientBuilder {
+2 -1
View File
@@ -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";
@@ -319,9 +319,9 @@ mod tests {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key();
public_keys.push(pk_user.clone());
public_keys.push(pk_user);
}
public_keys.push(user_keypair.public_key().clone());
public_keys.push(user_keypair.public_key());
let (req, req_info) =
withdrawal_request(user_keypair.secret_key(), expiration_date, t_type).unwrap();
@@ -462,9 +462,9 @@ mod tests {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key();
public_keys.push(pk_user.clone());
public_keys.push(pk_user);
}
public_keys.push(user_keypair.public_key().clone());
public_keys.push(user_keypair.public_key());
let (req, req_info) =
withdrawal_request(user_keypair.secret_key(), expiration_date, t_type).unwrap();
@@ -401,7 +401,7 @@ impl Bytable for SecretKeyUser {
impl Base58 for SecretKeyUser {}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
@@ -554,7 +554,7 @@ impl KeyPairUser {
}
pub fn public_key(&self) -> PublicKeyUser {
self.public_key.clone()
self.public_key
}
pub fn to_bytes(&self) -> Vec<u8> {
+3
View File
@@ -16,3 +16,6 @@ thiserror = { workspace = true }
[dev-dependencies]
rand = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["rand"] }
bincode = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+86 -2
View File
@@ -7,7 +7,7 @@
use crate::nodes::{NodeIdentity, NODE_IDENTITY_SIZE};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_sphinx_types::Destination;
use serde::de::{Error as SerdeError, Unexpected, Visitor};
use serde::de::{Error as SerdeError, SeqAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{self, Formatter};
use std::str::FromStr;
@@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient {
{
struct RecipientVisitor;
impl Visitor<'_> for RecipientVisitor {
impl<'de> Visitor<'de> for RecipientVisitor {
type Value = Recipient;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
@@ -90,6 +90,42 @@ impl<'de> Deserialize<'de> for Recipient {
)
})
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
// if we know the size hint, check if it matches expectation,
// otherwise return an error
if let Some(size_hint) = seq.size_hint() {
if size_hint != Recipient::LEN {
return Err(SerdeError::invalid_length(size_hint, &self));
}
}
let mut recipient_bytes = [0u8; Recipient::LEN];
// clippy's suggestion is completely wrong and it iterates wrong sequence
#[allow(clippy::needless_range_loop)]
for i in 0..Recipient::LEN {
let Some(elem) = seq.next_element::<u8>()? else {
return Err(SerdeError::invalid_length(i + 1, &self));
};
recipient_bytes[i] = elem;
}
// make sure there are no trailing bytes
if seq.next_element::<u8>()?.is_some() {
return Err(SerdeError::invalid_length(Recipient::LEN + 1, &self));
}
Recipient::try_from_bytes(recipient_bytes).map_err(|_| {
SerdeError::invalid_value(
Unexpected::Other("At least one of the curve points was malformed"),
&self,
)
})
}
}
deserializer.deserialize_bytes(RecipientVisitor)
@@ -245,6 +281,18 @@ impl FromStr for Recipient {
mod tests {
use super::*;
fn mock_recipient() -> Recipient {
Recipient::try_from_bytes([
67, 5, 132, 146, 3, 236, 116, 89, 254, 57, 131, 159, 69, 181, 55, 208, 12, 108, 136,
83, 58, 76, 171, 195, 31, 98, 92, 64, 68, 53, 156, 184, 100, 189, 73, 3, 238, 103, 156,
108, 124, 199, 42, 79, 172, 98, 81, 177, 182, 100, 167, 164, 74, 183, 199, 213, 162,
173, 102, 112, 30, 159, 148, 66, 44, 75, 230, 182, 138, 114, 170, 163, 209, 82, 204,
100, 118, 91, 57, 150, 212, 147, 151, 135, 148, 16, 213, 223, 182, 164, 242, 37, 40,
73, 137, 228,
])
.unwrap()
}
#[test]
fn string_conversion_works() {
let mut rng = rand::thread_rng();
@@ -308,4 +356,40 @@ mod tests {
recovered_recipient.gateway.to_bytes()
);
}
// calls `visit_bytes`
#[test]
fn bincode_serialisation_works() {
let recipient = mock_recipient();
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
struct MyStruct {
recipient: Recipient,
}
let a = MyStruct { recipient };
let s = bincode::serialize(&a).unwrap();
let b = bincode::deserialize(&s).unwrap();
assert_eq!(a, b);
}
// calls `visit_seq`
#[test]
fn json_serialisation_works() {
use serde::{Deserialize, Serialize};
let recipient = mock_recipient();
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
struct MyStruct {
recipient: Recipient,
}
let a = MyStruct { recipient };
let s = serde_json::to_string(&a).unwrap();
let b = serde_json::from_str(&s).unwrap();
assert_eq!(a, b);
}
}
+2
View File
@@ -180,6 +180,7 @@ impl NymPacket {
}
#[cfg(feature = "sphinx")]
#[allow(unreachable_patterns)]
pub fn sphinx_packet_ref(&self) -> Option<&SphinxPacket> {
match self {
NymPacket::Sphinx(packet) => Some(packet),
@@ -188,6 +189,7 @@ impl NymPacket {
}
#[cfg(feature = "sphinx")]
#[allow(unreachable_patterns)]
pub fn to_sphinx_packet(self) -> Option<SphinxPacket> {
match self {
NymPacket::Sphinx(packet) => Some(packet),
+2
View File
@@ -7,6 +7,7 @@ homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -36,5 +37,6 @@ url.workspace = true
[build-dependencies]
anyhow = { workspace = true }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
+14 -4
View File
@@ -1,22 +1,30 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use anyhow::Context;
#[tokio::main]
async fn main() {
async fn main() -> anyhow::Result<()> {
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/scraper-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.expect("Failed to create SQLx database connection");
.context("Failed to create SQLx database connection")?;
sqlx::migrate!("./sql_migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
.context("Failed to perform SQLx migrations")?;
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -25,4 +33,6 @@ async fn main() {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "nym-upgrade-mode-check"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[dependencies]
jwt-simple = { workspace = true }
reqwest = { workspace = true, features = ["rustls-tls"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
time = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }
tracing = { workspace = true }
nym-http-api-client = { path = "../http-api-client", default-features = false }
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] }
[dev-dependencies]
anyhow = { workspace = true }
time = { workspace = true, features = ["macros"] }
[lints]
workspace = true
@@ -0,0 +1,123 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::UpgradeModeCheckError;
use nym_crypto::asymmetric::ed25519;
use nym_http_api_client::generate_user_agent;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct UpgradeModeAttestation {
#[serde(flatten)]
pub content: UpgradeModeAttestationContent,
#[serde(with = "ed25519::bs58_ed25519_signature")]
pub signature: ed25519::Signature,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
#[serde(tag = "type")]
#[serde(rename = "upgrade_mode")]
pub struct UpgradeModeAttestationContent {
#[serde(with = "time::serde::timestamp")]
pub starting_time: OffsetDateTime,
#[serde(with = "ed25519::bs58_ed25519_pubkey")]
pub attester_public_key: ed25519::PublicKey,
}
impl UpgradeModeAttestation {
pub fn verify(&self) -> bool {
self.content
.attester_public_key
.verify(self.content.as_json(), &self.signature)
.is_ok()
}
}
impl UpgradeModeAttestationContent {
pub fn as_json(&self) -> String {
// SAFETY: Serialize impl is valid and we have no non-string map keys
#[allow(clippy::unwrap_used)]
serde_json::to_string(&self).unwrap()
}
}
pub fn generate_new_attestation(key: &ed25519::PrivateKey) -> UpgradeModeAttestation {
generate_new_attestation_with_starting_time(key, OffsetDateTime::now_utc())
}
pub fn generate_new_attestation_with_starting_time(
key: &ed25519::PrivateKey,
starting_time: OffsetDateTime,
) -> UpgradeModeAttestation {
let content = UpgradeModeAttestationContent {
starting_time,
attester_public_key: key.into(),
};
UpgradeModeAttestation {
signature: key.sign(content.as_json()),
content,
}
}
pub async fn attempt_retrieve(
url: &str,
) -> Result<Option<UpgradeModeAttestation>, UpgradeModeCheckError> {
let retrieval_failure = |source| UpgradeModeCheckError::AttestationRetrievalFailure {
url: url.to_string(),
source,
};
let attestation = reqwest::ClientBuilder::new()
.user_agent(generate_user_agent!())
.timeout(Duration::from_secs(5))
.build()
.map_err(retrieval_failure)?
.get(url)
.send()
.await
.map_err(retrieval_failure)?
.json::<Option<UpgradeModeAttestation>>()
.await
.map_err(retrieval_failure)?;
Ok(attestation)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn upgrade_mode_attestation_serde_json() -> anyhow::Result<()> {
// unix timestamp: 1629720000
let starting_time = time::macros::datetime!(2021-08-23 12:00 UTC);
let key = ed25519::PrivateKey::from_bytes(&[
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
])?;
let attestation = generate_new_attestation_with_starting_time(&key, starting_time);
let attestation_json = serde_json::to_string(&attestation)?;
let attestation_content_json = attestation.content.as_json();
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","signature":"5rWUr2ypaDTtrMKegMP3tQkkZGFAuhNTnEVCVe5Azv6QqvLzoGdQiMkFmeyhDd1XSfoXpL9fFM58rsdA1kf4GYMM"}"#;
let expected_content = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt"}"#;
assert_eq!(attestation_content_json, expected_content);
assert_eq!(attestation_json, expected_attestation);
let recovered_attestation = serde_json::from_str(&attestation_json)?;
assert_eq!(attestation, recovered_attestation);
let recovered_content = serde_json::from_str(&attestation_content_json)?;
assert_eq!(attestation.content, recovered_content);
Ok(())
}
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum UpgradeModeCheckError {
#[error("failed to decode jwt metadata")]
TokenMetadataDecodeFailure { source: jwt_simple::Error },
#[error("the jwt metadata didn't contain explicit public key")]
MissingTokenPublicKey,
#[error("the attached public key was not valid ed25519 public key")]
MalformedEd25519PublicKey { source: Ed25519RecoveryError },
#[error("failed to verify the jwt: {source}")]
JwtVerificationFailure { source: jwt_simple::Error },
#[error("failed to retrieve attestation from {url}:{source}")]
AttestationRetrievalFailure { url: String, source: reqwest::Error },
}
+119
View File
@@ -0,0 +1,119 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{UpgradeModeAttestation, UpgradeModeCheckError};
use jwt_simple::claims::Claims;
use jwt_simple::common::{KeyMetadata, VerificationOptions};
use jwt_simple::prelude::{EdDSAKeyPairLike, EdDSAPublicKeyLike};
use jwt_simple::token::Token;
use nym_crypto::asymmetric::ed25519;
use std::collections::HashSet;
use std::time::Duration;
// for now use static issuer such as "nym-credential-proxy"
pub fn generate_jwt_for_upgrade_mode_attestation(
attestation: UpgradeModeAttestation,
validity: Duration,
keys: &ed25519::KeyPair,
issuer: Option<&'static str>,
) -> String {
let claim = Claims::with_custom_claims(attestation, validity.into());
let mut claim = if let Some(issuer) = issuer {
claim.with_issuer(issuer)
} else {
claim
};
claim.create_nonce();
let md = KeyMetadata::default().with_public_key(keys.public_key().to_base58_string());
let mut jwt_keys = keys.to_jwt_compatible_keys();
// SAFETY: trait impl for EdDSA is infallible
#[allow(clippy::unwrap_used)]
jwt_keys.attach_metadata(md).unwrap();
// SAFETY: our construction of the jwt is valid
#[allow(clippy::unwrap_used)]
jwt_keys.sign(claim).unwrap()
}
pub fn validate_upgrade_mode_jwt(
token: &str,
expected_issuer: Option<&'static str>,
) -> Result<UpgradeModeAttestation, UpgradeModeCheckError> {
// for now, we completely ignore the validity of the pubkey (I know, I know).
// that will be changed later on
// so as a bypass we have to extract the claimed issuer from the jwt to verify against it
let metadata = Token::decode_metadata(token)
.map_err(|source| UpgradeModeCheckError::TokenMetadataDecodeFailure { source })?;
let pub_key = metadata
.public_key()
.ok_or(UpgradeModeCheckError::MissingTokenPublicKey)?;
let ed25519_pub_key = ed25519::PublicKey::from_base58_string(pub_key)
.map_err(|source| UpgradeModeCheckError::MalformedEd25519PublicKey { source })?;
let mut opts = VerificationOptions::default();
if let Some(issuer) = expected_issuer {
opts.allowed_issuers = Some(HashSet::from_iter(vec![issuer.to_string()]));
}
let attestation = ed25519_pub_key
.to_jwt_compatible_key()
.verify_token::<UpgradeModeAttestation>(token, Some(opts))
.map_err(|source| UpgradeModeCheckError::JwtVerificationFailure { source })?
.custom;
Ok(attestation)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generate_new_attestation;
use nym_crypto::asymmetric::ed25519;
#[test]
fn generate_and_validate_jwt() {
let attestation_key = ed25519::PrivateKey::from_bytes(&[
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
])
.unwrap();
let jwt_key = ed25519::PrivateKey::from_bytes(&[
152, 17, 144, 255, 213, 219, 246, 208, 109, 33, 100, 73, 1, 141, 32, 63, 141, 89, 167,
2, 52, 215, 241, 219, 200, 18, 159, 241, 76, 111, 42, 32,
])
.unwrap();
let keys = ed25519::KeyPair::from(jwt_key);
let attestation = generate_new_attestation(&attestation_key);
let jwt_issuer = generate_jwt_for_upgrade_mode_attestation(
attestation,
Duration::from_secs(60 * 60),
&keys,
Some("nym-credential-proxy"),
);
// we expect 'nym-credential-proxy' issuer
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
// we don't care about issuer
assert!(validate_upgrade_mode_jwt(&jwt_issuer, None).is_ok());
// we expect another-issuer
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("another-issuer")).is_err());
let jwt_no_issuer = generate_jwt_for_upgrade_mode_attestation(
attestation,
Duration::from_secs(60 * 60),
&keys,
None,
);
// we expect 'nym-credential-proxy' issuer
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, Some("nym-credential-proxy")).is_err());
// we don't care about issuer
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, None).is_ok());
}
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod attestation;
pub(crate) mod error;
pub(crate) mod jwt;
pub use attestation::{
attempt_retrieve, generate_new_attestation, generate_new_attestation_with_starting_time,
UpgradeModeAttestation,
};
pub use error::UpgradeModeCheckError;
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
+1 -6
View File
@@ -130,12 +130,7 @@ impl VerlocMeasurement {
let variance_micros = data
.iter()
.map(|&value| {
// make sure we don't underflow
let diff = if mean > value {
mean - value
} else {
value - mean
};
let diff = mean.abs_diff(value);
// we don't need nanos precision
let diff_micros = diff.as_micros();
diff_micros * diff_micros
@@ -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
@@ -0,0 +1,58 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<ErrorResponse>;
#[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<Version, WireguardMetadataApiClientError> {
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<Response, WireguardMetadataApiClientError> {
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<Response, WireguardMetadataApiClientError> {
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 {}
@@ -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
@@ -0,0 +1,46 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<WgApiWrapper>,
}
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<WgApiWrapper>,
axum_shutdown_button: CancellationToken,
) -> Self {
Self {
axum_shutdown_button,
axum_join_handle,
wg_api,
}
}
}
type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
@@ -0,0 +1,14 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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;
@@ -0,0 +1,101 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<AppState>,
}
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<AppState> {
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<ApiHttpServer> {
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::<SocketAddr>(),
)
.with_graceful_shutdown(receiver)
.await
}
}
@@ -0,0 +1,35 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<i64, MetadataError> {
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<i64, MetadataError> {
self.transceiver
.topup_bandwidth(ip, Box::new(credential))
.await
}
}
@@ -0,0 +1,13 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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;
@@ -0,0 +1,111 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<AppState> {
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<OutputParams>) -> AxumResult<FormattedResponse<u64>> {
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<SocketAddr>,
Query(output): Query<OutputParams>,
State(state): State<AppState>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
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<SocketAddr>,
Query(output): Query<OutputParams>,
State(state): State<AppState>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
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))
}
@@ -0,0 +1,307 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<PeerControlRequest>,
}
impl PeerControllerTransceiver {
pub fn new(request_tx: mpsc::Sender<PeerControlRequest>) -> Self {
Self { request_tx }
}
async fn get_client_bandwidth(&self, ip: IpAddr) -> Result<ClientBandwidth, MetadataError> {
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<i64, MetadataError> {
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<CredentialSpendingData>,
) -> Result<i64, MetadataError> {
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<i64> {
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());
}
}
@@ -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
@@ -0,0 +1,28 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<crate::models::error::Error> for MetadataError {
fn from(value: crate::models::error::Error) -> Self {
Self::Models {
message: value.to_string(),
}
}
}
@@ -0,0 +1,20 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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()
}
@@ -0,0 +1,30 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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 },
}
@@ -0,0 +1,145 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<CredentialSpendingData>),
}
impl From<super::latest::interface::RequestData> 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<RequestData> 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<super::latest::interface::ResponseData> 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<ResponseData> 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<RequestData> for Request {
fn construct(info: RequestData, version: Version) -> Result<Self, super::error::Error> {
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<RequestData> 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<ResponseData> for Response {
fn construct(info: ResponseData, version: Version) -> Result<Self, super::error::Error> {
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<ResponseData> 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))
}
}
}
}
@@ -0,0 +1,175 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<u64> 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<Version> 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<u8>,
}
#[derive(Clone, Serialize, Deserialize, ToSchema)]
pub struct Response {
pub version: Version,
pub(crate) inner: Vec<u8>,
}
pub trait Extract<T> {
fn extract(&self) -> Result<(T, Version), Error>;
}
pub trait Construct<T>: Sized {
fn construct(info: T, version: Version) -> Result<Self, Error>;
}
pub type AxumResult<T> = Result<T, AxumErrorResponse>;
#[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,
];
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,86 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedRequest> for InnerAvailableBandwidthRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
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<InnerAvailableBandwidthRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerAvailableBandwidthRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,86 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedResponse> for InnerAvailableBandwidthResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
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<InnerAvailableBandwidthResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerAvailableBandwidthResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,73 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<RequestData> for VersionedRequest {
fn construct(info: RequestData, _version: Version) -> Result<Self, Error> {
match info {
RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?),
RequestData::TopUpBandwidth(_) => Ok(InnerTopUpRequest {}.try_into()?),
}
}
}
impl Extract<RequestData> 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<ResponseData> for VersionedResponse {
fn construct(info: ResponseData, _version: Version) -> Result<Self, Error> {
match info {
ResponseData::AvailableBandwidth(()) => {
Ok(InnerAvailableBandwidthResponse {}.try_into()?)
}
ResponseData::TopUpBandwidth(()) => Ok(InnerTopUpResponse {}.try_into()?),
}
}
}
impl Extract<ResponseData> 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))
}
}
}
}
@@ -0,0 +1,175 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<u8>,
}
impl TryFrom<VersionedRequest> for Request {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
Ok(Request {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for VersionedRequest {
type Error = Error;
fn try_from(value: Request) -> Result<Self, Self::Error> {
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<u8>,
}
impl TryFrom<VersionedResponse> for Response {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
Ok(Response {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for VersionedResponse {
type Error = Error;
fn try_from(value: Response) -> Result<Self, Self::Error> {
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);
}
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,84 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedRequest> for InnerTopUpRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
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<InnerTopUpRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerTopUpRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,84 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedResponse> for InnerTopUpResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
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<InnerTopUpResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerTopUpResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,86 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedRequest> for InnerAvailableBandwidthRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
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<InnerAvailableBandwidthRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerAvailableBandwidthRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,90 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedResponse> for InnerAvailableBandwidthResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
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<InnerAvailableBandwidthResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerAvailableBandwidthResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,224 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<CredentialSpendingData>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseData {
AvailableBandwidth(i64),
TopUpBandwidth(i64),
}
impl Construct<RequestData> for VersionedRequest {
fn construct(info: RequestData, _version: Version) -> Result<Self, Error> {
match info {
RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?),
RequestData::TopUpBandwidth(credential) => Ok(InnerTopUpRequest {
credential: *credential,
}
.try_into()?),
}
}
}
impl Extract<RequestData> 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<ResponseData> for VersionedResponse {
fn construct(info: ResponseData, _version: Version) -> Result<Self, Error> {
match info {
ResponseData::AvailableBandwidth(available_bandwidth) => {
Ok(InnerAvailableBandwidthResponse {
available_bandwidth,
}
.try_into()?)
}
ResponseData::TopUpBandwidth(available_bandwidth) => Ok(InnerTopUpResponse {
available_bandwidth,
}
.try_into()?),
}
}
}
impl Extract<ResponseData> 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<previous::interface::RequestData> for RequestData {
type Error = super::Error;
fn try_from(value: previous::interface::RequestData) -> Result<Self, Self::Error> {
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<RequestData> for previous::interface::RequestData {
type Error = super::Error;
fn try_from(value: RequestData) -> Result<Self, Self::Error> {
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<previous::interface::ResponseData> for ResponseData {
type Error = super::Error;
fn try_from(value: previous::interface::ResponseData) -> Result<Self, Self::Error> {
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<ResponseData> for previous::interface::ResponseData {
type Error = super::Error;
fn try_from(value: ResponseData) -> Result<Self, Self::Error> {
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(())
);
}
}
@@ -0,0 +1,224 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<u8>,
}
impl TryFrom<VersionedRequest> for Request {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
Ok(Request {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for VersionedRequest {
type Error = Error;
fn try_from(value: Request) -> Result<Self, Self::Error> {
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<u8>,
}
impl TryFrom<VersionedResponse> for Response {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
Ok(Response {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for VersionedResponse {
type Error = Error;
fn try_from(value: Response) -> Result<Self, Self::Error> {
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);
}
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,92 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedRequest> for InnerTopUpRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
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<InnerTopUpRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerTopUpRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,88 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<VersionedResponse> for InnerTopUpResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
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<InnerTopUpResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerTopUpResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
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());
}
}
@@ -0,0 +1,10 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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";
@@ -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
@@ -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<i64> {
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::<SocketAddr>(),
)
.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);
}
}
@@ -0,0 +1,150 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<latest::interface::RequestData> 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<RequestData> 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<latest::interface::ResponseData> 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<ResponseData> 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<RequestData> for Request {
fn construct(
info: RequestData,
version: Version,
) -> Result<Self, nym_wireguard_private_metadata_shared::ModelError> {
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<RequestData> 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<ResponseData> for Response {
fn construct(
info: ResponseData,
version: Version,
) -> Result<Self, nym_wireguard_private_metadata_shared::ModelError> {
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<ResponseData> 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,
},
),
}
}
}
@@ -0,0 +1,2 @@
pub(crate) mod interface;
pub(crate) mod network;
@@ -0,0 +1,146 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<AppState> {
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<OutputParams>) -> AxumResult<FormattedResponse<u64>> {
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<OutputParams>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
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<OutputParams>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
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::<SocketAddr>(),
)
.await
.unwrap();
});
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
}
}
+6 -2
View File
@@ -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
+15 -6
View File
@@ -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<KeyPair>) -> (Self, Receiver<PeerControlRequest>) {
let (peer_tx, peer_rx) = mpsc::channel(1);
let (peer_tx, peer_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
(
WireguardGatewayData {
config,
@@ -178,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(ecash_manager.storage(), &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()));
}
@@ -190,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,
};
@@ -233,6 +241,7 @@ 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(
ecash_manager,
metrics,
+84 -23
View File
@@ -10,15 +10,18 @@ use futures::channel::oneshot;
use log::info;
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, ecash::traits::EcashManager,
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier,
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};
@@ -41,22 +44,31 @@ pub enum PeerControlRequest {
key: Key,
response_tx: oneshot::Sender<QueryPeerControlResponse>,
},
GetClientBandwidth {
GetClientBandwidthByKey {
key: Key,
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
},
GetVerifier {
GetClientBandwidthByIp {
ip: IpAddr,
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
},
GetVerifierByKey {
key: Key,
credential: Box<CredentialSpendingData>,
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
},
GetVerifierByIp {
ip: IpAddr,
credential: Box<CredentialSpendingData>,
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
},
}
pub type AddPeerControlResponse = Result<()>;
pub type RemovePeerControlResponse = Result<()>;
pub type QueryPeerControlResponse = Result<Option<Peer>>;
pub type GetClientBandwidthControlResponse = Result<ClientBandwidth>;
pub type QueryVerifierControlResponse = Result<CredentialVerifier>;
pub type QueryVerifierControlResponse = Result<Box<dyn TicketVerifier + Send + Sync>>;
pub struct PeerController {
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
@@ -77,7 +89,7 @@ pub struct PeerController {
impl PeerController {
#[allow(clippy::too_many_arguments)]
pub fn new(
pub(crate) fn new(
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
metrics: NymNodeMetrics,
wg_api: Arc<dyn WireguardInterfaceApi + Send + Sync>,
@@ -165,10 +177,13 @@ impl PeerController {
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(self.ecash_verifier.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(),
@@ -192,7 +207,20 @@ impl PeerController {
Ok(())
}
async fn handle_query_peer(&self, key: &Key) -> Result<Option<Peer>> {
async fn ip_to_key(&self, ip: IpAddr) -> Result<Option<Key>> {
Ok(self
.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<Option<Peer>> {
Ok(self
.ecash_verifier
.storage()
@@ -202,20 +230,32 @@ impl PeerController {
.transpose()?)
}
async fn handle_get_client_bandwidth(&self, key: &Key) -> Result<ClientBandwidth> {
async fn handle_get_client_bandwidth_by_key(&self, key: &Key) -> Result<ClientBandwidth> {
let bandwidth_storage_manager = self
.bw_storage_managers
.get(key)
.ok_or(Error::MissingClientBandwidthEntry)?;
Ok(bandwidth_storage_manager.read().await.client_bandwidth())
Ok(bandwidth_storage_manager
.inner()
.read()
.await
.client_bandwidth())
}
async fn handle_query_verifier(
async fn handle_get_client_bandwidth_by_ip(&self, ip: IpAddr) -> Result<ClientBandwidth> {
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<CredentialVerifier> {
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
let storage = self.ecash_verifier.storage();
let client_id = storage
.get_wireguard_peer(&key.to_string())
@@ -225,7 +265,11 @@ impl PeerController {
let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) else {
return Err(Error::MissingClientBandwidthEntry);
};
let client_bandwidth = bandwidth_storage_manager.read().await.client_bandwidth();
let client_bandwidth = bandwidth_storage_manager
.inner()
.read()
.await
.client_bandwidth();
let verifier = CredentialVerifier::new(
CredentialSpendingRequest::new(credential),
self.ecash_verifier.clone(),
@@ -237,7 +281,19 @@ impl PeerController {
true,
),
);
Ok(verifier)
Ok(Box::new(verifier))
}
async fn handle_query_verifier_by_ip(
&self,
ip: IpAddr,
credential: CredentialSpendingData,
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
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) {
@@ -340,18 +396,23 @@ impl PeerController {
response_tx.send(self.remove_peer(&key).await).ok();
}
Some(PeerControlRequest::QueryPeer { key, response_tx }) => {
response_tx.send(self.handle_query_peer(&key).await).ok();
response_tx.send(self.handle_query_peer_by_key(&key).await).ok();
}
Some(PeerControlRequest::GetClientBandwidth { key, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth(&key).await).ok();
Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok();
}
Some(PeerControlRequest::GetVerifier { key, credential, response_tx }) => {
response_tx.send(self.handle_query_verifier(&key, *credential).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;
}
}
}
+26 -4
View File
@@ -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<RwLock<BandwidthStorageManager>>;
#[derive(Clone)]
pub(crate) struct SharedBandwidthStorageManager {
inner: Arc<RwLock<BandwidthStorageManager>>,
allowed_ips: Vec<IpAddrMask>,
}
impl SharedBandwidthStorageManager {
pub(crate) fn new(
inner: Arc<RwLock<BandwidthStorageManager>>,
allowed_ips: Vec<IpAddrMask>,
) -> Self {
Self { inner, allowed_ips }
}
pub(crate) fn inner(&self) -> &RwLock<BandwidthStorageManager> {
&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<RwLock<Host>>,
cached_peer: CachedPeerManager,
@@ -120,6 +141,7 @@ impl PeerHandle {
if spent_bandwidth > 0
&& self
.bandwidth_storage_manager
.inner()
.write()
.await
.try_use_bandwidth(spent_bandwidth)
@@ -182,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");
}

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