Compare commits
85 Commits
simon/lp/nodes
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bf48de7ba | |||
| 1bc5169691 | |||
| a900656ec8 | |||
| 34709e76a1 | |||
| 153645dabf | |||
| c85fb161d4 | |||
| e27cf142f9 | |||
| a9bf1954bc | |||
| b6202b5a6b | |||
| e8410b2302 | |||
| 8de781f750 | |||
| 225024d428 | |||
| 367716612f | |||
| e82b116230 | |||
| 7324bb23b6 | |||
| de9fa97129 | |||
| 298b55280e | |||
| b41ea3628f | |||
| dad2d30773 | |||
| 8392f7da94 | |||
| cc405cc802 | |||
| 3dd6b907fe | |||
| 2fd26581eb | |||
| aa7b1e939a | |||
| 639c7f83a4 | |||
| 4ce136ccf0 | |||
| 14a85901b4 | |||
| 0796e9e0a6 | |||
| a98a65c16d | |||
| 11320e3f6a | |||
| a52a8c3e81 | |||
| 23e6169c02 | |||
| 17d3791b8e | |||
| c9a9940cb9 | |||
| ff0ecc95fb | |||
| d791e08fac | |||
| 1532c0c16e | |||
| d37b4226d0 | |||
| 43a1bd38e8 | |||
| f28b1e2077 | |||
| dd8c0a2521 | |||
| dc64fb622c | |||
| 86021937df | |||
| e7057f3932 | |||
| f6f01d3700 | |||
| 128f727376 | |||
| a85256c8c4 | |||
| 7310d63f1b | |||
| 25eba09b92 | |||
| a8cecb1200 | |||
| 343a48c297 | |||
| 4e52e9bf77 | |||
| cf55e2fe86 | |||
| 9782bae54b | |||
| 526cb9b8be | |||
| dc0835f1f3 | |||
| b5a8b9d283 | |||
| a395167139 | |||
| 6b98c168fc | |||
| 4645de3eb5 | |||
| e6dd670b16 | |||
| dc48750271 | |||
| 626d013547 | |||
| 28b22f6b22 | |||
| 6b0a904d10 | |||
| d2833c76c0 | |||
| f2e379f10a | |||
| 46c67440bb | |||
| e5cd9fd69e | |||
| 8dc53b137c | |||
| 64c68d7b76 | |||
| 71d4b5b3ea | |||
| b3e37ce13c | |||
| bad438b5ad | |||
| 8795cbe407 | |||
| 21c14c0df0 | |||
| 4a266a7348 | |||
| 3c6d88397e | |||
| ad9d66ef3a | |||
| 657aa9f86f | |||
| b45351bb0c | |||
| c7e4255095 | |||
| bafed70f51 | |||
| e0de0d8835 | |||
| 87c236a927 |
@@ -0,0 +1,2 @@
|
|||||||
|
[target.wasm32-unknown-unknown]
|
||||||
|
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]
|
||||||
@@ -23,10 +23,10 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v5.0.0
|
uses: pnpm/action-setup@v5.0.0
|
||||||
with:
|
with:
|
||||||
version: 9
|
version: 11.1.2
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -17,13 +17,16 @@ jobs:
|
|||||||
run: sudo apt-get install rsync
|
run: sudo apt-get install rsync
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- uses: rlespinasse/github-slug-action@v3.x
|
- uses: rlespinasse/github-slug-action@v3.x
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v5.0.0
|
||||||
|
with:
|
||||||
|
version: 11.1.2
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
- name: Setup yarn
|
cache: pnpm
|
||||||
run: npm install -g yarn
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn && yarn build && yarn build:ci:storybook
|
run: pnpm install && pnpm build && pnpm build:ci:storybook
|
||||||
- name: Deploy branch to CI www (storybook)
|
- name: Deploy branch to CI www (storybook)
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: easingthemes/ssh-deploy@main
|
uses: easingthemes/ssh-deploy@main
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ on:
|
|||||||
- 'sdk/ffi/**'
|
- 'sdk/ffi/**'
|
||||||
- 'sdk/rust/**'
|
- 'sdk/rust/**'
|
||||||
- 'service-providers/**'
|
- 'service-providers/**'
|
||||||
- 'nym-browser-extension/storage/**'
|
|
||||||
- 'tools/**'
|
- 'tools/**'
|
||||||
- 'wasm/**'
|
- 'wasm/**'
|
||||||
- 'Cargo.toml'
|
- 'Cargo.toml'
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: 24
|
||||||
|
|
||||||
- name: Validate version format
|
- name: Validate version format
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: 24
|
||||||
|
|
||||||
- name: Validate version format
|
- name: Validate version format
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v5.0.0
|
uses: pnpm/action-setup@v5.0.0
|
||||||
with:
|
with:
|
||||||
version: 9
|
version: 11.1.2
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- uses: rlespinasse/github-slug-action@v3.x
|
- uses: rlespinasse/github-slug-action@v3.x
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v5.0.0
|
||||||
|
with:
|
||||||
|
version: 11.1.2
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
- name: Setup yarn
|
|
||||||
run: npm install -g yarn
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -44,16 +46,16 @@ jobs:
|
|||||||
go-version: "1.24.6"
|
go-version: "1.24.6"
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: yarn
|
run: pnpm i
|
||||||
|
|
||||||
- name: Build packages
|
- name: Build packages
|
||||||
run: yarn build:ci
|
run: pnpm build:ci
|
||||||
|
|
||||||
- name: Install again
|
- name: Install again
|
||||||
run: yarn
|
run: pnpm i
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: yarn lint
|
run: pnpm lint
|
||||||
|
|
||||||
- name: Typecheck with tsc
|
- name: Typecheck with tsc
|
||||||
run: yarn tsc
|
run: pnpm tsc
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
name: ci-nym-wallet-frontend
|
name: ci-nym-wallet-frontend
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'nym-wallet/**'
|
- 'nym-wallet/**'
|
||||||
@@ -12,30 +13,34 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v5.0.0
|
||||||
|
with:
|
||||||
|
version: 11.1.2
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: nym-wallet/.nvmrc
|
node-version-file: nym-wallet/.nvmrc
|
||||||
cache: yarn
|
cache: pnpm
|
||||||
cache-dependency-path: yarn.lock
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --network-timeout 100000
|
run: pnpm install
|
||||||
|
|
||||||
- name: Build TypeScript packages (wallet depends on @nymproject/types, etc.)
|
- name: Build TypeScript packages (wallet depends on @nymproject/types, etc.)
|
||||||
run: yarn build:types
|
run: pnpm build:types
|
||||||
|
|
||||||
- name: Build @nymproject/mui-theme and @nymproject/react (wallet imports subpaths)
|
- name: Build @nymproject/mui-theme and @nymproject/react (wallet imports subpaths)
|
||||||
run: yarn build:packages
|
run: pnpm build:packages
|
||||||
|
|
||||||
- name: Typecheck nym-wallet
|
- name: Typecheck nym-wallet
|
||||||
run: yarn --cwd nym-wallet tsc
|
run: pnpm --filter @nymproject/nym-wallet-app tsc
|
||||||
|
|
||||||
- name: Lint nym-wallet
|
- name: Lint nym-wallet
|
||||||
run: yarn --cwd nym-wallet lint
|
run: pnpm --filter @nymproject/nym-wallet-app lint
|
||||||
|
|
||||||
- name: Yarn audit (workspace lockfile; informational)
|
- name: pnpm audit (workspace lockfile; informational)
|
||||||
run: yarn audit --level critical
|
run: pnpm audit --audit-level critical
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Unit tests (nym-wallet)
|
- name: Unit tests (nym-wallet)
|
||||||
run: yarn --cwd nym-wallet test
|
run: pnpm --filter @nymproject/nym-wallet-app test
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -23,10 +23,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Node
|
- name: Setup pnpm
|
||||||
uses: actions/setup-node@v4
|
uses: pnpm/action-setup@v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 22.13.0
|
version: 11.1.2
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -68,17 +71,17 @@ jobs:
|
|||||||
fileName: '.env'
|
fileName: '.env'
|
||||||
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||||
|
|
||||||
- name: Yarn cache clean
|
- name: pnpm cache clean
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && yarn cache clean
|
run: cd .. && pnpm cache delete
|
||||||
|
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && yarn --network-timeout 100000
|
run: cd .. && pnpm i
|
||||||
|
|
||||||
- name: Yarn build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && yarn build
|
run: cd .. && pnpm build
|
||||||
|
|
||||||
- name: Install dependencies and build it
|
- name: Install dependencies and build it
|
||||||
env:
|
env:
|
||||||
@@ -97,7 +100,7 @@ jobs:
|
|||||||
TAURI_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
TAURI_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
TAURI_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
TAURI_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
run: |
|
run: |
|
||||||
yarn build-macx86
|
pnpm build-macx86
|
||||||
|
|
||||||
- name: Create app tarball
|
- name: Create app tarball
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -26,12 +26,17 @@ jobs:
|
|||||||
libwebkit2gtk-4.1-dev build-essential curl wget libssl-dev jq \
|
libwebkit2gtk-4.1-dev build-essential curl wget libssl-dev jq \
|
||||||
libgtk-3-dev squashfs-tools libayatana-appindicator3-dev make libfuse2 unzip librsvg2-dev file \
|
libgtk-3-dev squashfs-tools libayatana-appindicator3-dev make libfuse2 unzip librsvg2-dev file \
|
||||||
libsoup-3.0-dev libjavascriptcoregtk-4.1-dev
|
libsoup-3.0-dev libjavascriptcoregtk-4.1-dev
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v5.0.0
|
||||||
|
with:
|
||||||
|
version: 11.1.2
|
||||||
|
|
||||||
- name: Node
|
- name: Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22.13.0
|
node-version: 24
|
||||||
cache: 'yarn'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -40,10 +45,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && yarn --network-timeout 100000
|
run: cd .. && pnpm i
|
||||||
|
|
||||||
- name: Install app dependencies
|
- name: Install app dependencies
|
||||||
run: yarn
|
run: pnpm
|
||||||
|
|
||||||
- name: Create env file
|
- name: Create env file
|
||||||
uses: timheuer/base64-to-file@v1.2
|
uses: timheuer/base64-to-file@v1.2
|
||||||
@@ -52,7 +57,7 @@ jobs:
|
|||||||
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: yarn build
|
run: pnpm build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
|||||||
@@ -40,16 +40,13 @@ jobs:
|
|||||||
- name: Setup MSBuild.exe
|
- name: Setup MSBuild.exe
|
||||||
uses: microsoft/setup-msbuild@v3
|
uses: microsoft/setup-msbuild@v3
|
||||||
|
|
||||||
# No cache:yarn here: setup-node needs yarn on PATH to populate the cache, but this runner
|
- name: Setup pnpm
|
||||||
# only gets yarn from the step below.
|
uses: pnpm/action-setup@v5.0.0
|
||||||
- name: Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
with:
|
||||||
node-version: 22.13.0
|
version: 11.1.2
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
- name: Install Yarn (classic)
|
with:
|
||||||
shell: bash
|
node-version: 24
|
||||||
run: npm install -g yarn@1.22.22
|
|
||||||
|
|
||||||
- name: Strip Authenticode thumbprint (avoid signtool on runner)
|
- name: Strip Authenticode thumbprint (avoid signtool on runner)
|
||||||
working-directory: nym-wallet/src-tauri
|
working-directory: nym-wallet/src-tauri
|
||||||
@@ -118,11 +115,11 @@ jobs:
|
|||||||
' tauri.conf.json
|
' tauri.conf.json
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && yarn --network-timeout 100000
|
run: cd .. && pnpm i
|
||||||
|
|
||||||
- name: Install app dependencies
|
- name: Install app dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn --network-timeout 100000
|
run: pnpm i
|
||||||
|
|
||||||
- name: Build and sign it
|
- name: Build and sign it
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -136,7 +133,7 @@ jobs:
|
|||||||
SSL_COM_TOTP_SECRET: ${{ env.SIGN_WINDOWS == 'true' && secrets.SSL_COM_TOTP_SECRET }}
|
SSL_COM_TOTP_SECRET: ${{ env.SIGN_WINDOWS == 'true' && secrets.SSL_COM_TOTP_SECRET }}
|
||||||
run: |
|
run: |
|
||||||
echo "Starting build process..."
|
echo "Starting build process..."
|
||||||
yarn build
|
pnpm build
|
||||||
|
|
||||||
- name: Check bundle directory
|
- name: Check bundle directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v5.0.0
|
||||||
|
with:
|
||||||
|
version: 11.1.2
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- name: Setup yarn
|
|
||||||
run: npm install -g yarn
|
|
||||||
|
|
||||||
- name: Install rust toolchain
|
- name: Install rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@@ -40,10 +42,10 @@ jobs:
|
|||||||
run: ./wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh
|
run: ./wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: pnpm i
|
||||||
|
|
||||||
- name: Build WASM and Typescript SDK
|
- name: Build WASM and Typescript SDK
|
||||||
run: yarn sdk:build
|
run: pnpm sdk:build
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
- uses: nymtech/nym/.github/actions/nym-hash-releases@develop
|
- uses: nymtech/nym/.github/actions/nym-hash-releases@develop
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -79,3 +79,7 @@ CLAUDE.md
|
|||||||
/notes
|
/notes
|
||||||
/target-otel
|
/target-otel
|
||||||
test-tutorials/
|
test-tutorials/
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
shamefully-hoist=false
|
||||||
|
prefer-workspace-packages=true
|
||||||
|
hoist-pattern[]=*eslint*
|
||||||
|
hoist-pattern[]=*prettier*
|
||||||
|
hoist-pattern[]=*typescript*
|
||||||
|
hoist-pattern[]=*@types*
|
||||||
|
|
||||||
|
auto-install-peers=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
+134
@@ -4,6 +4,140 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2026.11-xynomizithra] (2026-06-08)
|
||||||
|
|
||||||
|
- bugfix: allow re-inviting expired members ([#6863])
|
||||||
|
- feat: disable Nagle's algorithm for LP between nym-nodes ([#6857])
|
||||||
|
- Keep peer in wg table when updating psk ([#6856])
|
||||||
|
- chore: minor nym-node improvements ([#6850])
|
||||||
|
- chore: LP registration adjustments ([#6845])
|
||||||
|
- crates release: bump version to 1.21.1 ([#6844])
|
||||||
|
- fix gateways being penalised for no stress testing ([#6843])
|
||||||
|
- fix score inflation for throttled nodes ([#6842])
|
||||||
|
- Bugfix/cherry pick/waterloo stres testing floats ([#6841])
|
||||||
|
- bugfix: NMv3 race condition ([#6837])
|
||||||
|
- feat: implement UpdateFamily for the node families contract ([#6834])
|
||||||
|
- Bugfix/cherry pick/waterloo ns api ([#6833])
|
||||||
|
- experiment: attempt to retroactively generate specs for node families and ecash contracts ([#6813])
|
||||||
|
- moving lp packets in lp-data crate ([#6810])
|
||||||
|
- upgrade axum to 0.8.9 (and side deps) ([#6808])
|
||||||
|
- chore: expose admin method for migrating vesting delegations/mixnodes ([#6795])
|
||||||
|
- [chore] fix clippy 1.95 lints for future version update ([#6794])
|
||||||
|
- Handle Rate Limit Challenge Response ([#6786])
|
||||||
|
- NYM-583: Avoid corrupted database on Windows. ([#6785])
|
||||||
|
- Max/smolmix wasm ([#6784])
|
||||||
|
- Chore/bugfixes ([#6783])
|
||||||
|
- Switch from yarn to pnpm ([#6779])
|
||||||
|
- feat: Node Families: expose stake information inside DVpnGateway ([#6778])
|
||||||
|
- feat: Node Families: expose family information for NS API consumers ([#6777])
|
||||||
|
- feat: Node Families: cache and expose family data within nym API ([#6774])
|
||||||
|
- Re-order default API urls for network details ([#6767])
|
||||||
|
- add ci for NM agent binary ([#6764])
|
||||||
|
- feat/refactor: introduce shared contract caches within Nym API ([#6760])
|
||||||
|
- chore: removed dead code for redundant mixnet-vesting integration tests ([#6759])
|
||||||
|
- feat: Node Families: remove nodes upon unbonding ([#6752])
|
||||||
|
- feat: Node Families: contract transactions ([#6750])
|
||||||
|
- feat: Node Families: contract queries ([#6731])
|
||||||
|
- feat: Node Families: initial contract storage ([#6717])
|
||||||
|
- start node families topic branch ([#6715])
|
||||||
|
- Bump rand from 0.8.5 to 0.8.6 in /contracts ([#6702])
|
||||||
|
- Testing port checks in NS Agents ([#6694])
|
||||||
|
- build(deps): bump microsoft/setup-msbuild from 2 to 3 ([#6602])
|
||||||
|
- build(deps): bump tar from 0.4.44 to 0.4.45 ([#6595])
|
||||||
|
- build(deps): bump quinn-proto from 0.11.12 to 0.11.14 ([#6549])
|
||||||
|
- build(deps): bump docker/login-action from 3 to 4 ([#6518])
|
||||||
|
- build(deps): bump actions/download-artifact from 7 to 8 ([#6497])
|
||||||
|
- build(deps): bump actions/upload-artifact from 6 to 7 ([#6496])
|
||||||
|
|
||||||
|
[#6863]: https://github.com/nymtech/nym/pull/6863
|
||||||
|
[#6857]: https://github.com/nymtech/nym/pull/6857
|
||||||
|
[#6856]: https://github.com/nymtech/nym/pull/6856
|
||||||
|
[#6850]: https://github.com/nymtech/nym/pull/6850
|
||||||
|
[#6845]: https://github.com/nymtech/nym/pull/6845
|
||||||
|
[#6844]: https://github.com/nymtech/nym/pull/6844
|
||||||
|
[#6843]: https://github.com/nymtech/nym/pull/6843
|
||||||
|
[#6842]: https://github.com/nymtech/nym/pull/6842
|
||||||
|
[#6841]: https://github.com/nymtech/nym/pull/6841
|
||||||
|
[#6837]: https://github.com/nymtech/nym/pull/6837
|
||||||
|
[#6834]: https://github.com/nymtech/nym/pull/6834
|
||||||
|
[#6833]: https://github.com/nymtech/nym/pull/6833
|
||||||
|
[#6813]: https://github.com/nymtech/nym/pull/6813
|
||||||
|
[#6810]: https://github.com/nymtech/nym/pull/6810
|
||||||
|
[#6808]: https://github.com/nymtech/nym/pull/6808
|
||||||
|
[#6795]: https://github.com/nymtech/nym/pull/6795
|
||||||
|
[#6794]: https://github.com/nymtech/nym/pull/6794
|
||||||
|
[#6786]: https://github.com/nymtech/nym/pull/6786
|
||||||
|
[#6785]: https://github.com/nymtech/nym/pull/6785
|
||||||
|
[#6784]: https://github.com/nymtech/nym/pull/6784
|
||||||
|
[#6783]: https://github.com/nymtech/nym/pull/6783
|
||||||
|
[#6779]: https://github.com/nymtech/nym/pull/6779
|
||||||
|
[#6778]: https://github.com/nymtech/nym/pull/6778
|
||||||
|
[#6777]: https://github.com/nymtech/nym/pull/6777
|
||||||
|
[#6774]: https://github.com/nymtech/nym/pull/6774
|
||||||
|
[#6767]: https://github.com/nymtech/nym/pull/6767
|
||||||
|
[#6764]: https://github.com/nymtech/nym/pull/6764
|
||||||
|
[#6760]: https://github.com/nymtech/nym/pull/6760
|
||||||
|
[#6759]: https://github.com/nymtech/nym/pull/6759
|
||||||
|
[#6752]: https://github.com/nymtech/nym/pull/6752
|
||||||
|
[#6750]: https://github.com/nymtech/nym/pull/6750
|
||||||
|
[#6731]: https://github.com/nymtech/nym/pull/6731
|
||||||
|
[#6717]: https://github.com/nymtech/nym/pull/6717
|
||||||
|
[#6715]: https://github.com/nymtech/nym/pull/6715
|
||||||
|
[#6702]: https://github.com/nymtech/nym/pull/6702
|
||||||
|
[#6694]: https://github.com/nymtech/nym/pull/6694
|
||||||
|
[#6602]: https://github.com/nymtech/nym/pull/6602
|
||||||
|
[#6595]: https://github.com/nymtech/nym/pull/6595
|
||||||
|
[#6549]: https://github.com/nymtech/nym/pull/6549
|
||||||
|
[#6518]: https://github.com/nymtech/nym/pull/6518
|
||||||
|
[#6497]: https://github.com/nymtech/nym/pull/6497
|
||||||
|
[#6496]: https://github.com/nymtech/nym/pull/6496
|
||||||
|
|
||||||
|
## [2026.10-waterloo] (2026-05-27)
|
||||||
|
|
||||||
|
- Re-order default API urls for network details - Waterloo release ([#6799])
|
||||||
|
- [bugfix] IPR v8<->v9 mismatch on Waterloo ([#6772])
|
||||||
|
- Migrate to hickory 0.26.1 ([#6751])
|
||||||
|
- add workflows for NM3 ([#6729])
|
||||||
|
- credential proxy pool ([#6726])
|
||||||
|
- chore: made sphinx version threshold assertion a compile time check ([#6718])
|
||||||
|
- Feat/nmv3 updated performance calculation ([#6714])
|
||||||
|
- feat: NMv3: submission of stress testing result into nym-api ([#6709])
|
||||||
|
- feat: NMv3: Prometheus metrics for network monitor ([#6693])
|
||||||
|
- feat: NMv3: add read-only results API to orchestrator ([#6689])
|
||||||
|
- feat: NMv3: Eviction of stale testrun data ([#6685])
|
||||||
|
- feat: NMv3: Wire up testrun assignment and result submission flow ([#6680])
|
||||||
|
- feat: NMv3: Support multiple network monitor agents per host ([#6679])
|
||||||
|
- Feat/nmv3 agent announcement ([#6673])
|
||||||
|
- add node refresher for periodic scraping of bonded nym-node details ([#6626])
|
||||||
|
- Feat/nmv3 orchestrator queue ([#6597])
|
||||||
|
- feat: network monitor agent - standalone node stress-testing ([#6582])
|
||||||
|
- [feat] propagate NM agent noise keys to nym-node routing ([#6577])
|
||||||
|
- start mix stress testing topic branch ([#6575])
|
||||||
|
- Feat/nmv3 agents subscription ([#6567])
|
||||||
|
- Feat/nmv3 agents contract ([#6555])
|
||||||
|
|
||||||
|
[#6799]: https://github.com/nymtech/nym/pull/6799
|
||||||
|
[#6772]: https://github.com/nymtech/nym/pull/6772
|
||||||
|
[#6751]: https://github.com/nymtech/nym/pull/6751
|
||||||
|
[#6729]: https://github.com/nymtech/nym/pull/6729
|
||||||
|
[#6726]: https://github.com/nymtech/nym/pull/6726
|
||||||
|
[#6718]: https://github.com/nymtech/nym/pull/6718
|
||||||
|
[#6714]: https://github.com/nymtech/nym/pull/6714
|
||||||
|
[#6709]: https://github.com/nymtech/nym/pull/6709
|
||||||
|
[#6693]: https://github.com/nymtech/nym/pull/6693
|
||||||
|
[#6689]: https://github.com/nymtech/nym/pull/6689
|
||||||
|
[#6685]: https://github.com/nymtech/nym/pull/6685
|
||||||
|
[#6680]: https://github.com/nymtech/nym/pull/6680
|
||||||
|
[#6679]: https://github.com/nymtech/nym/pull/6679
|
||||||
|
[#6673]: https://github.com/nymtech/nym/pull/6673
|
||||||
|
[#6626]: https://github.com/nymtech/nym/pull/6626
|
||||||
|
[#6597]: https://github.com/nymtech/nym/pull/6597
|
||||||
|
[#6582]: https://github.com/nymtech/nym/pull/6582
|
||||||
|
[#6577]: https://github.com/nymtech/nym/pull/6577
|
||||||
|
[#6575]: https://github.com/nymtech/nym/pull/6575
|
||||||
|
[#6567]: https://github.com/nymtech/nym/pull/6567
|
||||||
|
[#6555]: https://github.com/nymtech/nym/pull/6555
|
||||||
|
|
||||||
## [2026.9-venaco] (2026-05-06)
|
## [2026.9-venaco] (2026-05-06)
|
||||||
|
|
||||||
- Fix for v9 IPR ([#6710])
|
- Fix for v9 IPR ([#6710])
|
||||||
|
|||||||
Generated
+2358
-2147
File diff suppressed because it is too large
Load Diff
+140
-131
@@ -44,6 +44,7 @@ members = [
|
|||||||
"common/cosmwasm-smart-contracts/nym-performance-contract",
|
"common/cosmwasm-smart-contracts/nym-performance-contract",
|
||||||
"common/cosmwasm-smart-contracts/nym-pool-contract",
|
"common/cosmwasm-smart-contracts/nym-pool-contract",
|
||||||
"common/cosmwasm-smart-contracts/vesting-contract",
|
"common/cosmwasm-smart-contracts/vesting-contract",
|
||||||
|
"common/cosmwasm-smart-contracts/network-monitors-contract",
|
||||||
"common/credential-proxy",
|
"common/credential-proxy",
|
||||||
"common/credential-storage",
|
"common/credential-storage",
|
||||||
"common/credential-utils",
|
"common/credential-utils",
|
||||||
@@ -78,6 +79,7 @@ members = [
|
|||||||
"common/nym-kkt-ciphersuite",
|
"common/nym-kkt-ciphersuite",
|
||||||
"common/nym-kkt-context",
|
"common/nym-kkt-context",
|
||||||
"common/nym-lp",
|
"common/nym-lp",
|
||||||
|
"common/nym-lp-data",
|
||||||
"common/nym-metrics",
|
"common/nym-metrics",
|
||||||
"common/nym_offline_compact_ecash",
|
"common/nym_offline_compact_ecash",
|
||||||
"common/nymnoise",
|
"common/nymnoise",
|
||||||
@@ -129,7 +131,6 @@ members = [
|
|||||||
"nym-api",
|
"nym-api",
|
||||||
"nym-api/nym-api-requests",
|
"nym-api/nym-api-requests",
|
||||||
"nym-authenticator-client",
|
"nym-authenticator-client",
|
||||||
"nym-browser-extension/storage",
|
|
||||||
"nym-credential-proxy/nym-credential-proxy",
|
"nym-credential-proxy/nym-credential-proxy",
|
||||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||||
"nym-data-observatory",
|
"nym-data-observatory",
|
||||||
@@ -173,10 +174,12 @@ members = [
|
|||||||
"tools/nymvisor",
|
"tools/nymvisor",
|
||||||
"tools/ts-rs-cli",
|
"tools/ts-rs-cli",
|
||||||
"wasm/client",
|
"wasm/client",
|
||||||
# "wasm/full-nym-wasm", # If we uncomment this again, remember to also uncomment the profile settings below
|
|
||||||
"wasm/mix-fetch",
|
"wasm/mix-fetch",
|
||||||
"wasm/node-tester",
|
"wasm/smolmix",
|
||||||
"wasm/zknym-lib",
|
"wasm/zknym-lib",
|
||||||
|
"nym-network-monitor-v3/nym-network-monitor-orchestrator",
|
||||||
|
"nym-network-monitor-v3/nym-network-monitor-agent",
|
||||||
|
"nym-network-monitor-v3/nym-network-monitor-orchestrator-requests",
|
||||||
]
|
]
|
||||||
|
|
||||||
default-members = [
|
default-members = [
|
||||||
@@ -194,6 +197,8 @@ default-members = [
|
|||||||
"service-providers/network-requester",
|
"service-providers/network-requester",
|
||||||
"tools/internal/localnet-orchestrator",
|
"tools/internal/localnet-orchestrator",
|
||||||
"tools/nymvisor",
|
"tools/nymvisor",
|
||||||
|
"nym-network-monitor-v3/nym-network-monitor-orchestrator",
|
||||||
|
"nym-network-monitor-v3/nym-network-monitor-agent",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = ["contracts", "nym-wallet", "cpu-cycles"]
|
exclude = ["contracts", "nym-wallet", "cpu-cycles"]
|
||||||
@@ -207,7 +212,7 @@ edition = "2024"
|
|||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
rust-version = "1.87.0"
|
rust-version = "1.87.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
version = "1.21.0"
|
version = "1.21.1"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
addr = "0.15.6"
|
addr = "0.15.6"
|
||||||
@@ -221,10 +226,11 @@ anyhow = "1.0.98"
|
|||||||
arc-swap = "1.7.1"
|
arc-swap = "1.7.1"
|
||||||
argon2 = "0.5.0"
|
argon2 = "0.5.0"
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
axum = "0.7.5"
|
async-tungstenite = { version = "0.24", default-features = false }
|
||||||
axum-client-ip = "0.6.1"
|
axum = "0.8.9"
|
||||||
axum-extra = "0.9.4"
|
axum-client-ip = "1.3.1"
|
||||||
axum-test = "16.2.0"
|
axum-extra = "0.12.6"
|
||||||
|
axum-test = "20.0.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
base85rs = "0.1.3"
|
base85rs = "0.1.3"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
@@ -249,7 +255,7 @@ clap_complete_fig = "4.5"
|
|||||||
colored = "2.2"
|
colored = "2.2"
|
||||||
comfy-table = "7.1.4"
|
comfy-table = "7.1.4"
|
||||||
console = "0.16.0"
|
console = "0.16.0"
|
||||||
console-subscriber = "0.4.1"
|
console-subscriber = "0.5.0"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
const-str = "0.5.6"
|
const-str = "0.5.6"
|
||||||
const_format = "0.2.34"
|
const_format = "0.2.34"
|
||||||
@@ -274,24 +280,27 @@ eyre = "0.6.9"
|
|||||||
fastrand = "2.1.1"
|
fastrand = "2.1.1"
|
||||||
flate2 = "1.1.1"
|
flate2 = "1.1.1"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
futures-rustls = { version = "0.26", default-features = false }
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
generic-array = "0.14.7"
|
generic-array = "0.14.7"
|
||||||
getrandom = "0.2.10"
|
getrandom = "0.2.10"
|
||||||
getrandom03 = { package = "getrandom", version = "=0.3.3" }
|
getrandom03 = { package = "getrandom", version = "=0.3.3" }
|
||||||
|
getrandom04 = { package = "getrandom", version = "0.4" }
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
handlebars = "3.5.5"
|
handlebars = "3.5.5"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hickory-proto = "0.26.1"
|
hickory-proto = { version = "0.26.1", default-features = false }
|
||||||
hickory-resolver = "0.26.1"
|
hickory-resolver = "0.26.1"
|
||||||
hkdf = "0.12.3"
|
hkdf = "0.12.3"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "1"
|
http = "1"
|
||||||
http-body-util = "0.1"
|
http-body-util = "0.1"
|
||||||
|
httparse = "1.10"
|
||||||
httpcodec = "0.2.3"
|
httpcodec = "0.2.3"
|
||||||
human-repr = "1.1.0"
|
human-repr = "1.1.0"
|
||||||
humantime = "2.2.0"
|
humantime = "2.2.0"
|
||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
hyper = "1.6.0"
|
hyper = { version = "1.6.0", default-features = false }
|
||||||
hyper-util = "0.1"
|
hyper-util = "0.1"
|
||||||
indicatif = "0.18.0"
|
indicatif = "0.18.0"
|
||||||
inquire = "0.6.2"
|
inquire = "0.6.2"
|
||||||
@@ -337,12 +346,14 @@ regex = "1.10.6"
|
|||||||
reqwest = { version = "0.13.1", default-features = false }
|
reqwest = { version = "0.13.1", default-features = false }
|
||||||
rs_merkle = "1.5.0"
|
rs_merkle = "1.5.0"
|
||||||
rustls = { version = "0.23.37", default-features = false }
|
rustls = { version = "0.23.37", default-features = false }
|
||||||
|
rustls-pki-types = "1"
|
||||||
|
rustls-rustcrypto = "0.0.2-alpha"
|
||||||
schemars = "0.8.22"
|
schemars = "0.8.22"
|
||||||
semver = "1.0.26"
|
semver = "1.0.26"
|
||||||
serde = "1.0.219"
|
serde = "1.0.219"
|
||||||
serde_bytes = "0.11.17"
|
serde_bytes = "0.11.17"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.140"
|
serde_json = { version = "1.0.140", features = ["float_roundtrip"] }
|
||||||
serde_json_path = "0.7.2"
|
serde_json_path = "0.7.2"
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
serde_with = "3.9.0"
|
serde_with = "3.9.0"
|
||||||
@@ -350,6 +361,7 @@ serde_yaml = "0.9.25"
|
|||||||
serde_plain = "1.0.2"
|
serde_plain = "1.0.2"
|
||||||
sha2 = "0.10.3"
|
sha2 = "0.10.3"
|
||||||
si-scale = "0.2.3"
|
si-scale = "0.2.3"
|
||||||
|
simple-dns = "0.7"
|
||||||
smoltcp = "0.12"
|
smoltcp = "0.12"
|
||||||
snow = "0.9.6"
|
snow = "0.9.6"
|
||||||
sphinx-packet = "=0.6.0"
|
sphinx-packet = "=0.6.0"
|
||||||
@@ -373,7 +385,7 @@ tokio-test = "0.4.4"
|
|||||||
tokio-tun = "0.11.5"
|
tokio-tun = "0.11.5"
|
||||||
tokio-rustls = "0.26"
|
tokio-rustls = "0.26"
|
||||||
tokio-smoltcp = "0.5"
|
tokio-smoltcp = "0.5"
|
||||||
tokio-tungstenite = { version = "0.20.1" }
|
tokio-tungstenite = "0.20.1"
|
||||||
tokio-util = "0.7.15"
|
tokio-util = "0.7.15"
|
||||||
toml = "0.8.22"
|
toml = "0.8.22"
|
||||||
tower = "0.5.2"
|
tower = "0.5.2"
|
||||||
@@ -391,17 +403,20 @@ uniffi = "0.29.2"
|
|||||||
uniffi_build = "0.29.0"
|
uniffi_build = "0.29.0"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
utoipa = "5.2"
|
utoipa = "5.2"
|
||||||
utoipa-swagger-ui = "8.1"
|
utoipa-swagger-ui = "9.0.2"
|
||||||
utoipauto = "0.2"
|
utoipauto = "0.2"
|
||||||
uuid = "1.19.0"
|
uuid = "1.19.0"
|
||||||
vergen = { version = "=8.3.1", default-features = false }
|
vergen = { version = "=8.3.1", default-features = false }
|
||||||
vergen-gitcl = { version = "1.0.8", default-features = false }
|
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
x25519-dalek = "2.0.0"
|
x25519-dalek = "2.0.0"
|
||||||
zeroize = "1.7.0"
|
zeroize = "1.7.0"
|
||||||
|
|
||||||
prometheus = { version = "0.14.0" }
|
prometheus = { version = "0.14.0" }
|
||||||
|
|
||||||
|
# recreating lioness
|
||||||
|
# we don't care about particular versions - just pull whatever is used by sphinx
|
||||||
|
lioness = "*"
|
||||||
|
arrayref = "*"
|
||||||
|
|
||||||
# libcrux
|
# libcrux
|
||||||
libcrux-kem = "0.0.7"
|
libcrux-kem = "0.0.7"
|
||||||
@@ -414,113 +429,115 @@ libcrux-sha3 = "0.0.8"
|
|||||||
libcrux-traits = "0.0.6"
|
libcrux-traits = "0.0.6"
|
||||||
|
|
||||||
# Workspace dep definitions required by crates.io publication - we need a workspace version since `cargo workspaces` doesn't work with path imports from crate manifests
|
# Workspace dep definitions required by crates.io publication - we need a workspace version since `cargo workspaces` doesn't work with path imports from crate manifests
|
||||||
nym-api-requests = { version = "1.21.0", path = "nym-api/nym-api-requests" }
|
nym-api-requests = { version = "1.21.1", path = "nym-api/nym-api-requests" }
|
||||||
nym-authenticator-requests = { version = "1.21.0", path = "common/authenticator-requests" }
|
nym-authenticator-requests = { version = "1.21.1", path = "common/authenticator-requests" }
|
||||||
nym-async-file-watcher = { version = "1.21.0", path = "common/async-file-watcher" }
|
nym-async-file-watcher = { version = "1.21.1", path = "common/async-file-watcher" }
|
||||||
nym-authenticator-client = { version = "1.21.0", path = "nym-authenticator-client" }
|
nym-authenticator-client = { version = "1.21.1", path = "nym-authenticator-client" }
|
||||||
nym-bandwidth-controller = { version = "1.21.0", path = "common/bandwidth-controller" }
|
nym-bandwidth-controller = { version = "1.21.1", path = "common/bandwidth-controller" }
|
||||||
nym-bin-common = { version = "1.21.0", path = "common/bin-common" }
|
nym-bin-common = { version = "1.21.1", path = "common/bin-common" }
|
||||||
nym-cache = { version = "1.21.0", path = "common/nym-cache" }
|
nym-cache = { version = "1.21.1", path = "common/nym-cache" }
|
||||||
nym-client-core = { version = "1.21.0", path = "common/client-core", default-features = false }
|
nym-client-core = { version = "1.21.1", path = "common/client-core", default-features = false }
|
||||||
nym-client-core-config-types = { version = "1.21.0", path = "common/client-core/config-types" }
|
nym-client-core-config-types = { version = "1.21.1", path = "common/client-core/config-types" }
|
||||||
nym-client-core-gateways-storage = { version = "1.21.0", path = "common/client-core/gateways-storage" }
|
nym-client-core-gateways-storage = { version = "1.21.1", path = "common/client-core/gateways-storage" }
|
||||||
nym-client-core-surb-storage = { version = "1.21.0", path = "common/client-core/surb-storage" }
|
nym-client-core-surb-storage = { version = "1.21.1", path = "common/client-core/surb-storage" }
|
||||||
nym-client-websocket-requests = { version = "1.21.0", path = "clients/native/websocket-requests" }
|
nym-client-websocket-requests = { version = "1.21.1", path = "clients/native/websocket-requests" }
|
||||||
nym-common = { version = "1.21.0", path = "common/nym-common" }
|
nym-common = { version = "1.21.1", path = "common/nym-common" }
|
||||||
nym-compact-ecash = { version = "1.21.0", path = "common/nym_offline_compact_ecash" }
|
nym-compact-ecash = { version = "1.21.1", path = "common/nym_offline_compact_ecash" }
|
||||||
nym-config = { version = "1.21.0", path = "common/config" }
|
nym-config = { version = "1.21.1", path = "common/config" }
|
||||||
nym-contracts-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/contracts-common" }
|
nym-contracts-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/contracts-common" }
|
||||||
nym-coconut-dkg-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/coconut-dkg" }
|
nym-coconut-dkg-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||||
nym-credential-storage = { version = "1.21.0", path = "common/credential-storage" }
|
nym-credential-storage = { version = "1.21.1", path = "common/credential-storage" }
|
||||||
nym-credential-utils = { version = "1.21.0", path = "common/credential-utils" }
|
nym-credential-utils = { version = "1.21.1", path = "common/credential-utils" }
|
||||||
nym-credential-proxy-lib = { version = "1.21.0", path = "common/credential-proxy" }
|
nym-credential-proxy-lib = { version = "1.21.1", path = "common/credential-proxy" }
|
||||||
nym-credentials = { version = "1.21.0", path = "common/credentials", default-features = false }
|
nym-credentials = { version = "1.21.1", path = "common/credentials", default-features = false }
|
||||||
nym-credentials-interface = { version = "1.21.0", path = "common/credentials-interface" }
|
nym-credentials-interface = { version = "1.21.1", path = "common/credentials-interface" }
|
||||||
nym-credential-proxy-requests = { version = "1.21.0", path = "nym-credential-proxy/nym-credential-proxy-requests", default-features = false }
|
nym-credential-proxy-requests = { version = "1.21.1", path = "nym-credential-proxy/nym-credential-proxy-requests", default-features = false }
|
||||||
nym-credential-verification = { version = "1.21.0", path = "common/credential-verification" }
|
nym-credential-verification = { version = "1.21.1", path = "common/credential-verification" }
|
||||||
nym-crypto = { version = "1.21.0", path = "common/crypto", default-features = false }
|
nym-crypto = { version = "1.21.1", path = "common/crypto", default-features = false }
|
||||||
nym-dkg = { version = "1.21.0", path = "common/dkg" }
|
nym-dkg = { version = "1.21.1", path = "common/dkg" }
|
||||||
nym-ecash-contract-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/ecash-contract" }
|
nym-ecash-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/ecash-contract" }
|
||||||
nym-ecash-signer-check = { version = "1.21.0", path = "common/ecash-signer-check" }
|
nym-ecash-signer-check = { version = "1.21.1", path = "common/ecash-signer-check" }
|
||||||
nym-ecash-signer-check-types = { version = "1.21.0", path = "common/ecash-signer-check-types" }
|
nym-ecash-signer-check-types = { version = "1.21.1", path = "common/ecash-signer-check-types" }
|
||||||
nym-ecash-time = { version = "1.21.0", path = "common/ecash-time" }
|
nym-ecash-time = { version = "1.21.1", path = "common/ecash-time" }
|
||||||
nym-exit-policy = { version = "1.21.0", path = "common/exit-policy" }
|
nym-exit-policy = { version = "1.21.1", path = "common/exit-policy" }
|
||||||
nym-ffi-shared = { version = "1.21.0", path = "sdk/ffi/shared" }
|
nym-ffi-shared = { version = "1.21.1", path = "sdk/ffi/shared" }
|
||||||
nym-gateway-client = { version = "1.21.0", path = "common/client-libs/gateway-client", default-features = false }
|
nym-gateway-client = { version = "1.21.1", path = "common/client-libs/gateway-client", default-features = false }
|
||||||
nym-gateway-probe = { version = "1.18.0", path = "nym-gateway-probe" }
|
nym-gateway-probe = { version = "1.21.1", path = "nym-gateway-probe" }
|
||||||
nym-gateway-requests = { version = "1.21.0", path = "common/gateway-requests" }
|
nym-gateway-requests = { version = "1.21.1", path = "common/gateway-requests" }
|
||||||
nym-gateway-storage = { version = "1.21.0", path = "common/gateway-storage" }
|
nym-gateway-storage = { version = "1.21.1", path = "common/gateway-storage" }
|
||||||
nym-gateway-stats-storage = { version = "1.21.0", path = "common/gateway-stats-storage" }
|
nym-gateway-stats-storage = { version = "1.21.1", path = "common/gateway-stats-storage" }
|
||||||
nym-group-contract-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/group-contract" }
|
nym-group-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/group-contract" }
|
||||||
nym-http-api-client = { version = "1.21.0", path = "common/http-api-client" }
|
nym-http-api-client = { version = "1.21.1", path = "common/http-api-client" }
|
||||||
nym-http-api-client-macro = { version = "1.21.0", path = "common/http-api-client-macro" }
|
nym-http-api-client-macro = { version = "1.21.1", path = "common/http-api-client-macro" }
|
||||||
nym-http-api-common = { version = "1.21.0", path = "common/http-api-common", default-features = false }
|
nym-http-api-common = { version = "1.21.1", path = "common/http-api-common", default-features = false }
|
||||||
nym-id = { version = "1.21.0", path = "common/nym-id" }
|
nym-id = { version = "1.21.1", path = "common/nym-id" }
|
||||||
nym-ip-packet-client = { version = "1.21.0", path = "nym-ip-packet-client" }
|
nym-ip-packet-client = { version = "1.21.1", path = "nym-ip-packet-client" }
|
||||||
nym-ip-packet-requests = { version = "1.21.0", path = "common/ip-packet-requests" }
|
nym-ip-packet-requests = { version = "1.21.1", path = "common/ip-packet-requests" }
|
||||||
nym-lp = { version = "1.21.0", path = "common/nym-lp" }
|
nym-lp = { version = "1.21.1", path = "common/nym-lp" }
|
||||||
nym-kkt = { version = "1.21.0", path = "common/nym-kkt" }
|
nym-lp-data = { version = "1.21.1", path = "common/nym-lp-data" }
|
||||||
nym-kkt-ciphersuite = { version = "1.21.0", path = "common/nym-kkt-ciphersuite" }
|
nym-kkt = { version = "1.21.1", path = "common/nym-kkt" }
|
||||||
nym-kkt-context = { version = "1.21.0", path = "common/nym-kkt-context" }
|
nym-kkt-ciphersuite = { version = "1.21.1", path = "common/nym-kkt-ciphersuite" }
|
||||||
nym-metrics = { version = "1.21.0", path = "common/nym-metrics" }
|
nym-kkt-context = { version = "1.21.1", path = "common/nym-kkt-context" }
|
||||||
nym-mixnet-client = { version = "1.21.0", path = "common/client-libs/mixnet-client" }
|
nym-metrics = { version = "1.21.1", path = "common/nym-metrics" }
|
||||||
nym-mixnet-contract-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/mixnet-contract" }
|
nym-mixnet-client = { version = "1.21.1", path = "common/client-libs/mixnet-client" }
|
||||||
nym-multisig-contract-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/multisig-contract" }
|
nym-mixnet-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||||
nym-network-defaults = { version = "1.21.0", path = "common/network-defaults" }
|
nym-multisig-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/multisig-contract" }
|
||||||
nym-node-tester-utils = { version = "1.21.0", path = "common/node-tester-utils" }
|
nym-network-defaults = { version = "1.21.1", path = "common/network-defaults" }
|
||||||
nym-noise = { version = "1.21.0", path = "common/nymnoise" }
|
nym-node-tester-utils = { version = "1.21.1", path = "common/node-tester-utils" }
|
||||||
nym-noise-keys = { version = "1.21.0", path = "common/nymnoise/keys" }
|
nym-noise = { version = "1.21.1", path = "common/nymnoise" }
|
||||||
nym-nonexhaustive-delayqueue = { version = "1.21.0", path = "common/nonexhaustive-delayqueue" }
|
nym-noise-keys = { version = "1.21.1", path = "common/nymnoise/keys" }
|
||||||
nym-node-requests = { version = "1.21.0", path = "nym-node/nym-node-requests", default-features = false }
|
nym-nonexhaustive-delayqueue = { version = "1.21.1", path = "common/nonexhaustive-delayqueue" }
|
||||||
nym-node-metrics = { version = "1.21.0", path = "nym-node/nym-node-metrics" }
|
nym-node-requests = { version = "1.21.1", path = "nym-node/nym-node-requests", default-features = false }
|
||||||
nym-node-families-contract-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/node-families-contract" }
|
nym-node-metrics = { version = "1.21.1", path = "nym-node/nym-node-metrics" }
|
||||||
nym-ordered-buffer = { version = "1.21.0", path = "common/socks5/ordered-buffer" }
|
nym-node-families-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/node-families-contract" }
|
||||||
nym-outfox = { version = "1.21.0", path = "nym-outfox" }
|
nym-ordered-buffer = { version = "1.21.1", path = "common/socks5/ordered-buffer" }
|
||||||
nym-registration-common = { version = "1.21.0", path = "common/registration" }
|
nym-outfox = { version = "1.21.1", path = "nym-outfox" }
|
||||||
nym-pemstore = { version = "1.21.0", path = "common/pemstore" }
|
nym-registration-common = { version = "1.21.1", path = "common/registration" }
|
||||||
nym-performance-contract-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/nym-performance-contract" }
|
nym-pemstore = { version = "1.21.1", path = "common/pemstore" }
|
||||||
nym-sdk = { version = "1.21.0", path = "sdk/rust/nym-sdk" }
|
nym-performance-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/nym-performance-contract" }
|
||||||
nym-serde-helpers = { version = "1.21.0", path = "common/serde-helpers" }
|
nym-sdk = { version = "1.21.1", path = "sdk/rust/nym-sdk" }
|
||||||
nym-service-providers-common = { version = "1.21.0", path = "service-providers/common" }
|
nym-serde-helpers = { version = "1.21.1", path = "common/serde-helpers" }
|
||||||
nym-service-provider-requests-common = { version = "1.21.0", path = "common/service-provider-requests-common" }
|
nym-service-providers-common = { version = "1.21.1", path = "service-providers/common" }
|
||||||
nym-socks5-client-core = { version = "1.21.0", path = "common/socks5-client-core" }
|
nym-service-provider-requests-common = { version = "1.21.1", path = "common/service-provider-requests-common" }
|
||||||
nym-socks5-proxy-helpers = { version = "1.21.0", path = "common/socks5/proxy-helpers" }
|
nym-socks5-client-core = { version = "1.21.1", path = "common/socks5-client-core" }
|
||||||
nym-socks5-requests = { version = "1.21.0", path = "common/socks5/requests" }
|
nym-socks5-proxy-helpers = { version = "1.21.1", path = "common/socks5/proxy-helpers" }
|
||||||
nym-sphinx = { version = "1.21.0", path = "common/nymsphinx" }
|
nym-socks5-requests = { version = "1.21.1", path = "common/socks5/requests" }
|
||||||
nym-sphinx-acknowledgements = { version = "1.21.0", path = "common/nymsphinx/acknowledgements" }
|
nym-sphinx = { version = "1.21.1", path = "common/nymsphinx" }
|
||||||
nym-sphinx-addressing = { version = "1.21.0", path = "common/nymsphinx/addressing" }
|
nym-sphinx-acknowledgements = { version = "1.21.1", path = "common/nymsphinx/acknowledgements" }
|
||||||
nym-sphinx-anonymous-replies = { version = "1.21.0", path = "common/nymsphinx/anonymous-replies" }
|
nym-sphinx-addressing = { version = "1.21.1", path = "common/nymsphinx/addressing" }
|
||||||
nym-sphinx-chunking = { version = "1.21.0", path = "common/nymsphinx/chunking" }
|
nym-sphinx-anonymous-replies = { version = "1.21.1", path = "common/nymsphinx/anonymous-replies" }
|
||||||
nym-sphinx-cover = { version = "1.21.0", path = "common/nymsphinx/cover" }
|
nym-sphinx-chunking = { version = "1.21.1", path = "common/nymsphinx/chunking" }
|
||||||
nym-sphinx-forwarding = { version = "1.21.0", path = "common/nymsphinx/forwarding" }
|
nym-sphinx-cover = { version = "1.21.1", path = "common/nymsphinx/cover" }
|
||||||
nym-sphinx-framing = { version = "1.21.0", path = "common/nymsphinx/framing" }
|
nym-sphinx-forwarding = { version = "1.21.1", path = "common/nymsphinx/forwarding" }
|
||||||
nym-sphinx-params = { version = "1.21.0", path = "common/nymsphinx/params" }
|
nym-sphinx-framing = { version = "1.21.1", path = "common/nymsphinx/framing" }
|
||||||
nym-sphinx-routing = { version = "1.21.0", path = "common/nymsphinx/routing" }
|
nym-sphinx-params = { version = "1.21.1", path = "common/nymsphinx/params" }
|
||||||
nym-sphinx-types = { version = "1.21.0", path = "common/nymsphinx/types" }
|
nym-sphinx-routing = { version = "1.21.1", path = "common/nymsphinx/routing" }
|
||||||
nym-statistics-common = { version = "1.21.0", path = "common/statistics" }
|
nym-sphinx-types = { version = "1.21.1", path = "common/nymsphinx/types" }
|
||||||
nym-store-cipher = { version = "1.21.0", path = "common/store-cipher" }
|
nym-statistics-common = { version = "1.21.1", path = "common/statistics" }
|
||||||
nym-task = { version = "1.21.0", path = "common/task" }
|
nym-store-cipher = { version = "1.21.1", path = "common/store-cipher" }
|
||||||
nym-tun = { version = "1.21.0", path = "common/tun" }
|
nym-task = { version = "1.21.1", path = "common/task" }
|
||||||
nym-test-utils = { version = "1.21.0", path = "common/test-utils" }
|
nym-tun = { version = "1.21.1", path = "common/tun" }
|
||||||
nym-ticketbooks-merkle = { version = "1.21.0", path = "common/ticketbooks-merkle" }
|
nym-test-utils = { version = "1.21.1", path = "common/test-utils" }
|
||||||
nym-topology = { version = "1.21.0", path = "common/topology" }
|
nym-ticketbooks-merkle = { version = "1.21.1", path = "common/ticketbooks-merkle" }
|
||||||
nym-types = { version = "1.21.0", path = "common/types" }
|
nym-topology = { version = "1.21.1", path = "common/topology" }
|
||||||
nym-upgrade-mode-check = { version = "1.21.0", path = "common/upgrade-mode-check" }
|
nym-types = { version = "1.21.1", path = "common/types" }
|
||||||
nym-validator-client = { version = "1.21.0", path = "common/client-libs/validator-client", default-features = false }
|
nym-upgrade-mode-check = { version = "1.21.1", path = "common/upgrade-mode-check" }
|
||||||
nym-vesting-contract-common = { version = "1.21.0", path = "common/cosmwasm-smart-contracts/vesting-contract" }
|
nym-validator-client = { version = "1.21.1", path = "common/client-libs/validator-client", default-features = false }
|
||||||
nym-verloc = { version = "1.21.0", path = "common/verloc" }
|
nym-vesting-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/vesting-contract" }
|
||||||
nym-wireguard = { version = "1.21.0", path = "common/wireguard" }
|
nym-network-monitors-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/network-monitors-contract" }
|
||||||
nym-wireguard-types = { version = "1.21.0", path = "common/wireguard-types" }
|
nym-verloc = { version = "1.21.1", path = "common/verloc" }
|
||||||
nym-wireguard-private-metadata-shared = { version = "1.21.0", path = "common/wireguard-private-metadata/shared" }
|
nym-wireguard = { version = "1.21.1", path = "common/wireguard" }
|
||||||
nym-wireguard-private-metadata-client = { version = "1.21.0", path = "common/wireguard-private-metadata/client" }
|
nym-wireguard-types = { version = "1.21.1", path = "common/wireguard-types" }
|
||||||
nym-wireguard-private-metadata-server = { version = "1.21.0", path = "common/wireguard-private-metadata/server" }
|
nym-wireguard-private-metadata-shared = { version = "1.21.1", path = "common/wireguard-private-metadata/shared" }
|
||||||
nym-sqlx-pool-guard = { version = "1.2.0", path = "nym-sqlx-pool-guard" }
|
nym-wireguard-private-metadata-client = { version = "1.21.1", path = "common/wireguard-private-metadata/client" }
|
||||||
nym-wasm-client-core = { version = "1.21.0", path = "common/wasm/client-core" }
|
nym-wireguard-private-metadata-server = { version = "1.21.1", path = "common/wireguard-private-metadata/server" }
|
||||||
nym-wasm-storage = { version = "1.21.0", path = "common/wasm/storage" }
|
nym-sqlx-pool-guard = { version = "1.21.1", path = "nym-sqlx-pool-guard" }
|
||||||
nym-wasm-utils = { version = "1.21.0", path = "common/wasm/utils", default-features = false }
|
nym-wasm-client-core = { version = "1.21.1", path = "common/wasm/client-core" }
|
||||||
nyxd-scraper-shared = { version = "1.21.0", path = "common/nyxd-scraper-shared" }
|
nym-wasm-storage = { version = "1.21.1", path = "common/wasm/storage" }
|
||||||
|
nym-wasm-utils = { version = "1.21.1", path = "common/wasm/utils", default-features = false }
|
||||||
|
nyxd-scraper-shared = { version = "1.21.1", path = "common/nyxd-scraper-shared" }
|
||||||
|
|
||||||
smolmix = { version = "1.21.0", path = "smolmix/core" }
|
smolmix = { version = "1.21.1", path = "smolmix/core" }
|
||||||
|
|
||||||
# coconut/DKG related
|
# coconut/DKG related
|
||||||
# unfortunately until https://github.com/zkcrypto/nym-bls12_381-fork/issues/10 is resolved, we have to rely on the fork
|
# unfortunately until https://github.com/zkcrypto/nym-bls12_381-fork/issues/10 is resolved, we have to rely on the fork
|
||||||
@@ -587,18 +604,11 @@ opt-level = 3
|
|||||||
# lto = true
|
# lto = true
|
||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
|
|
||||||
[profile.release.package.nym-node-tester-wasm]
|
[profile.release.package.mix-fetch-wasm]
|
||||||
# lto = true
|
# lto = true
|
||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
|
|
||||||
# Commented out since the crate is also commented out from the inclusion in the
|
[profile.release.package.smolmix-wasm]
|
||||||
# workspace above. We should uncomment this if we re-include it in the
|
|
||||||
# workspace
|
|
||||||
#[profile.release.package.nym-wasm-sdk]
|
|
||||||
## lto = true
|
|
||||||
#opt-level = 'z'
|
|
||||||
|
|
||||||
[profile.release.package.mix-fetch-wasm]
|
|
||||||
# lto = true
|
# lto = true
|
||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
|
|
||||||
@@ -619,4 +629,3 @@ exit = "deny"
|
|||||||
panic = "deny"
|
panic = "deny"
|
||||||
unimplemented = "deny"
|
unimplemented = "deny"
|
||||||
unreachable = "deny"
|
unreachable = "deny"
|
||||||
|
|
||||||
|
|||||||
@@ -104,23 +104,20 @@ $(eval $(call add_cargo_workspace,wallet,nym-wallet))
|
|||||||
sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint
|
sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint
|
||||||
|
|
||||||
sdk-wasm-build:
|
sdk-wasm-build:
|
||||||
# $(MAKE) -C nym-browser-extension/storage wasm-pack
|
|
||||||
$(MAKE) -C wasm/client
|
$(MAKE) -C wasm/client
|
||||||
$(MAKE) -C wasm/node-tester
|
|
||||||
$(MAKE) -C wasm/mix-fetch
|
$(MAKE) -C wasm/mix-fetch
|
||||||
|
$(MAKE) -C wasm/smolmix
|
||||||
# $(MAKE) -C wasm/zknym-lib
|
# $(MAKE) -C wasm/zknym-lib
|
||||||
# $(MAKE) -C wasm/full-nym-wasm
|
|
||||||
|
|
||||||
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
||||||
sdk-typescript-build:
|
sdk-typescript-build:
|
||||||
npx lerna run --scope @nymproject/sdk build --stream
|
npx lerna run --scope @nymproject/sdk build --stream
|
||||||
npx lerna run --scope @nymproject/mix-fetch build --stream
|
npx lerna run --scope @nymproject/mix-fetch build --stream
|
||||||
npx lerna run --scope @nymproject/node-tester build --stream
|
pnpm --pwd sdk/typescript/codegen/contract-clients build
|
||||||
yarn --cwd sdk/typescript/codegen/contract-clients build
|
|
||||||
|
|
||||||
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
|
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
|
||||||
# WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
|
|
||||||
WASM_CRATES = nym-client-wasm nym-node-tester-wasm
|
WASM_CRATES = nym-client-wasm
|
||||||
|
|
||||||
sdk-wasm-test:
|
sdk-wasm-test:
|
||||||
#cargo test $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings
|
#cargo test $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings
|
||||||
@@ -128,6 +125,7 @@ sdk-wasm-test:
|
|||||||
sdk-wasm-lint:
|
sdk-wasm-lint:
|
||||||
RUSTFLAGS='--cfg getrandom_backend="wasm_js"' cargo clippy $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings
|
RUSTFLAGS='--cfg getrandom_backend="wasm_js"' cargo clippy $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings
|
||||||
$(MAKE) -C wasm/mix-fetch check-fmt
|
$(MAKE) -C wasm/mix-fetch check-fmt
|
||||||
|
$(MAKE) -C wasm/smolmix check-fmt
|
||||||
|
|
||||||
# Add to top-level targets
|
# Add to top-level targets
|
||||||
build: sdk-wasm-build
|
build: sdk-wasm-build
|
||||||
@@ -223,7 +221,7 @@ build-nym-cli:
|
|||||||
|
|
||||||
generate-typescript:
|
generate-typescript:
|
||||||
cd tools/ts-rs-cli && cargo run && cd ../..
|
cd tools/ts-rs-cli && cargo run && cd ../..
|
||||||
yarn types:lint:fix
|
pnpm types:lint:fix
|
||||||
|
|
||||||
# Run the integration tests for public nym-api endpoints
|
# Run the integration tests for public nym-api endpoints
|
||||||
run-api-tests:
|
run-api-tests:
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ Nym Node Operators and Validators Terms and Conditions can be found [here](https
|
|||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-client"
|
name = "nym-client"
|
||||||
description = "Implementation of the Nym Client"
|
description = "Implementation of the Nym Client"
|
||||||
version = "1.1.76"
|
version = "1.1.78"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-socks5-client"
|
name = "nym-socks5-client"
|
||||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||||
version = "1.1.76"
|
version = "1.1.78"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ pub trait BandwidthTicketProvider: Send + Sync {
|
|||||||
) -> Result<PreparedCredential, BandwidthControllerError>;
|
) -> Result<PreparedCredential, BandwidthControllerError>;
|
||||||
|
|
||||||
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError>;
|
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError>;
|
||||||
|
|
||||||
|
async fn close(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
@@ -56,6 +58,10 @@ where
|
|||||||
.map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
|
.map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
|
||||||
Ok(Some(token))
|
Ok(Some(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn close(&self) {
|
||||||
|
self.storage.close().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
@@ -75,4 +81,8 @@ impl<T: BandwidthTicketProvider + ?Sized + Send> BandwidthTicketProvider for Box
|
|||||||
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
|
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
|
||||||
(**self).get_upgrade_mode_token().await
|
(**self).get_upgrade_mode_token().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn close(&self) {
|
||||||
|
(**self).close().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1023,6 +1023,16 @@ where
|
|||||||
let encryption_keys = init_res.client_keys.encryption_keypair();
|
let encryption_keys = init_res.client_keys.encryption_keypair();
|
||||||
let identity_keys = init_res.client_keys.identity_keypair();
|
let identity_keys = init_res.client_keys.identity_keypair();
|
||||||
|
|
||||||
|
let credential_store_for_close = credential_store.clone();
|
||||||
|
let close_credential_token = shutdown_tracker.clone_shutdown_token();
|
||||||
|
shutdown_tracker.try_spawn_named(
|
||||||
|
async move {
|
||||||
|
close_credential_token.cancelled().await;
|
||||||
|
credential_store_for_close.close().await;
|
||||||
|
},
|
||||||
|
"CredentialStorage::close_on_shutdown",
|
||||||
|
);
|
||||||
|
|
||||||
// the components are started in very specific order. Unless you know what you are doing,
|
// the components are started in very specific order. Unless you know what you are doing,
|
||||||
// do not change that.
|
// do not change that.
|
||||||
let bandwidth_controller = self
|
let bandwidth_controller = self
|
||||||
|
|||||||
@@ -11,11 +11,17 @@ use nym_bandwidth_controller::BandwidthController;
|
|||||||
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
|
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
|
||||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||||
use nym_validator_client::{QueryHttpRpcNyxdClient, nyxd};
|
use nym_validator_client::{QueryHttpRpcNyxdClient, nyxd};
|
||||||
use std::{io, path::Path};
|
use std::{io, path::Path, time::Duration};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tracing::{error, info, trace};
|
use tracing::{error, info, trace};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// Maximum rename retry attempts when the database file is temporarily locked.
|
||||||
|
const ARCHIVE_MAX_RETRY_ATTEMPTS: u8 = 15;
|
||||||
|
|
||||||
|
/// Delay between archive rename retry attempts.
|
||||||
|
const ARCHIVE_RETRY_DELAY: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
async fn setup_fresh_backend<P: AsRef<Path>>(
|
async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||||
db_path: P,
|
db_path: P,
|
||||||
surb_config: &config::ReplySurbs,
|
surb_config: &config::ReplySurbs,
|
||||||
@@ -74,13 +80,58 @@ async fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()
|
|||||||
};
|
};
|
||||||
let renamed = db_path.with_extension(new_extension);
|
let renamed = db_path.with_extension(new_extension);
|
||||||
|
|
||||||
tokio::fs::rename(db_path, &renamed).await.inspect_err(|_| {
|
// On Windows, sqlx may release its OS file handles asynchronously after
|
||||||
error!(
|
// pool.close() returns, briefly keeping the file locked
|
||||||
"Failed to rename corrupt database file: {} to {}",
|
// (ERROR_SHARING_VIOLATION, os error 32). Retry with a short delay to
|
||||||
db_path.display(),
|
// give the OS time to flush the remaining handles.
|
||||||
renamed.display()
|
for attempt in 0..ARCHIVE_MAX_RETRY_ATTEMPTS {
|
||||||
);
|
match tokio::fs::rename(db_path, &renamed).await {
|
||||||
})
|
Ok(()) => return Ok(()),
|
||||||
|
Err(e) if is_file_locked_error(&e) && (attempt + 1) < ARCHIVE_MAX_RETRY_ATTEMPTS => {
|
||||||
|
trace!(
|
||||||
|
"Database file is temporarily locked, retrying archive \
|
||||||
|
(attempt {}/{}): {e}",
|
||||||
|
attempt + 1,
|
||||||
|
ARCHIVE_MAX_RETRY_ATTEMPTS
|
||||||
|
);
|
||||||
|
tokio::time::sleep(ARCHIVE_RETRY_DELAY).await;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Failed to rename corrupt database file: {} to {}",
|
||||||
|
db_path.display(),
|
||||||
|
renamed.display()
|
||||||
|
);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reached only when every attempt was blocked by a file lock.
|
||||||
|
error!(
|
||||||
|
"Failed to rename corrupt database file after {} attempts: {} to {}",
|
||||||
|
ARCHIVE_MAX_RETRY_ATTEMPTS,
|
||||||
|
db_path.display(),
|
||||||
|
renamed.display()
|
||||||
|
);
|
||||||
|
Err(io::Error::other(
|
||||||
|
"corrupt database archive blocked by persistent file lock",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` when the IO error indicates a temporary file lock held by another handle
|
||||||
|
/// within the same process. Only meaningful on Windows; always `false` elsewhere.
|
||||||
|
fn is_file_locked_error(e: &io::Error) -> bool {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
// ERROR_SHARING_VIOLATION = 32, ERROR_LOCK_VIOLATION = 33
|
||||||
|
matches!(e.raw_os_error(), Some(32) | Some(33))
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
let _ = e;
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ mod nonwasm_sealed {
|
|||||||
impl GatewaySender for LocalGateway {
|
impl GatewaySender for LocalGateway {
|
||||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
||||||
self.packet_forwarder
|
self.packet_forwarder
|
||||||
.forward_packet(packet)
|
.forward_client_packet_without_delay(packet)
|
||||||
.map_err(erase_err)
|
.map_err(erase_err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,6 +337,8 @@ impl ReplyStorageBackend for Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||||
self.stop_client_use().await
|
let result = self.stop_client_use().await;
|
||||||
|
self.shutdown().await;
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ where
|
|||||||
debug!("Started PersistentReplyStorage");
|
debug!("Started PersistentReplyStorage");
|
||||||
if let Err(err) = self.backend.start_storage_session().await {
|
if let Err(err) = self.backend.start_storage_session().await {
|
||||||
error!("failed to start the storage session - {err}");
|
error!("failed to start the storage session - {err}");
|
||||||
|
self.backend.stop_storage_session().await.ok();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,10 +56,11 @@ where
|
|||||||
|
|
||||||
info!("PersistentReplyStorage is flushing all reply-related data to underlying storage");
|
info!("PersistentReplyStorage is flushing all reply-related data to underlying storage");
|
||||||
if let Err(err) = self.backend.flush_surb_storage(&mem_state).await {
|
if let Err(err) = self.backend.flush_surb_storage(&mem_state).await {
|
||||||
error!("failed to flush our reply-related data to the persistent storage: {err}")
|
error!("failed to flush our reply-related data to the persistent storage: {err}");
|
||||||
} else {
|
self.backend.stop_storage_session().await.ok();
|
||||||
info!("Data flush is complete")
|
return;
|
||||||
}
|
}
|
||||||
|
info!("Data flush is complete");
|
||||||
|
|
||||||
if let Err(err) = self.backend.stop_storage_session().await {
|
if let Err(err) = self.backend.stop_storage_session().await {
|
||||||
error!("failed to properly stop the storage session - {err}. We might not be able to smoothly restore it")
|
error!("failed to properly stop the storage session - {err}. We might not be able to smoothly restore it")
|
||||||
|
|||||||
@@ -34,3 +34,4 @@ client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nym-crypto = { workspace = true }
|
nym-crypto = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
tokio = { workspace = true, features = ["macros", "io-util", "rt", "rt-multi-thread"] }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use futures::StreamExt;
|
use futures::{SinkExt, StreamExt};
|
||||||
use nym_noise::config::NoiseConfig;
|
use nym_noise::config::NoiseConfig;
|
||||||
use nym_noise::upgrade_noise_initiator;
|
use nym_noise::upgrade_noise_initiator;
|
||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
@@ -14,6 +14,7 @@ use std::ops::Deref;
|
|||||||
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::error::TrySendError;
|
use tokio::sync::mpsc::error::TrySendError;
|
||||||
@@ -90,13 +91,17 @@ impl Deref for ActiveConnections {
|
|||||||
pub struct ConnectionSender {
|
pub struct ConnectionSender {
|
||||||
channel: mpsc::Sender<FramedNymPacket>,
|
channel: mpsc::Sender<FramedNymPacket>,
|
||||||
current_reconnection_attempt: Arc<AtomicU32>,
|
current_reconnection_attempt: Arc<AtomicU32>,
|
||||||
|
// Identifies the `ManagedConnection` task currently owning this entry; used
|
||||||
|
// to ensure drop-time eviction only fires on the still-owning task.
|
||||||
|
handle_token: Arc<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectionSender {
|
impl ConnectionSender {
|
||||||
fn new(channel: mpsc::Sender<FramedNymPacket>) -> Self {
|
fn new(channel: mpsc::Sender<FramedNymPacket>, handle_token: Arc<()>) -> Self {
|
||||||
ConnectionSender {
|
ConnectionSender {
|
||||||
channel,
|
channel,
|
||||||
current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
|
current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
|
||||||
|
handle_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,6 +112,31 @@ struct ManagedConnection {
|
|||||||
message_receiver: ReceiverStream<FramedNymPacket>,
|
message_receiver: ReceiverStream<FramedNymPacket>,
|
||||||
connection_timeout: Duration,
|
connection_timeout: Duration,
|
||||||
current_reconnection: Arc<AtomicU32>,
|
current_reconnection: Arc<AtomicU32>,
|
||||||
|
active_connections: ActiveConnections,
|
||||||
|
handle_token: Arc<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evicts the cache entry on task exit (only if still owned by this task).
|
||||||
|
// Without this, a stale `ConnectionSender` survives after the peer disconnects
|
||||||
|
// and the next outbound packet is silently swallowed by the dead TCP.
|
||||||
|
struct EvictOnDrop {
|
||||||
|
active_connections: ActiveConnections,
|
||||||
|
address: SocketAddr,
|
||||||
|
handle_token: Arc<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for EvictOnDrop {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let address = self.address;
|
||||||
|
let handle_token = &self.handle_token;
|
||||||
|
self.active_connections.remove_if(&address, |_, sender| {
|
||||||
|
Arc::ptr_eq(&sender.handle_token, handle_token)
|
||||||
|
});
|
||||||
|
trace!(
|
||||||
|
peer = %address,
|
||||||
|
"managed connection task exited; evicted owning cache entry"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManagedConnection {
|
impl ManagedConnection {
|
||||||
@@ -116,6 +146,8 @@ impl ManagedConnection {
|
|||||||
message_receiver: mpsc::Receiver<FramedNymPacket>,
|
message_receiver: mpsc::Receiver<FramedNymPacket>,
|
||||||
connection_timeout: Duration,
|
connection_timeout: Duration,
|
||||||
current_reconnection: Arc<AtomicU32>,
|
current_reconnection: Arc<AtomicU32>,
|
||||||
|
active_connections: ActiveConnections,
|
||||||
|
handle_token: Arc<()>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ManagedConnection {
|
ManagedConnection {
|
||||||
address,
|
address,
|
||||||
@@ -123,72 +155,30 @@ impl ManagedConnection {
|
|||||||
message_receiver: ReceiverStream::new(message_receiver),
|
message_receiver: ReceiverStream::new(message_receiver),
|
||||||
connection_timeout,
|
connection_timeout,
|
||||||
current_reconnection,
|
current_reconnection,
|
||||||
|
active_connections,
|
||||||
|
handle_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(self) {
|
async fn run(self) {
|
||||||
let address = self.address;
|
let address = self.address;
|
||||||
|
let _evict_guard = EvictOnDrop {
|
||||||
|
active_connections: self.active_connections,
|
||||||
|
address,
|
||||||
|
handle_token: self.handle_token,
|
||||||
|
};
|
||||||
|
|
||||||
let reconnection_attempt = self.current_reconnection.load(Ordering::Acquire);
|
let reconnection_attempt = self.current_reconnection.load(Ordering::Acquire);
|
||||||
let connect_start = tokio::time::Instant::now();
|
let connect_start = tokio::time::Instant::now();
|
||||||
let connection_fut = TcpStream::connect(address);
|
let connection_fut = TcpStream::connect(address);
|
||||||
|
|
||||||
let conn = match tokio::time::timeout(self.connection_timeout, connection_fut).await {
|
// 1. attempt to establish the connection with timeout
|
||||||
Ok(stream_res) => match stream_res {
|
let maybe_stream = match tokio::time::timeout(self.connection_timeout, connection_fut).await
|
||||||
Ok(stream) => {
|
{
|
||||||
let connect_ms = connect_start.elapsed().as_millis() as u64;
|
Ok(stream) => stream,
|
||||||
debug!(
|
|
||||||
peer = %address,
|
|
||||||
connect_ms,
|
|
||||||
"Managed to establish connection to {}", self.address
|
|
||||||
);
|
|
||||||
|
|
||||||
let noise_start = tokio::time::Instant::now();
|
|
||||||
let noise_stream =
|
|
||||||
match upgrade_noise_initiator(stream, &self.noise_config).await {
|
|
||||||
Ok(noise_stream) => noise_stream,
|
|
||||||
Err(err) => {
|
|
||||||
let noise_handshake_ms = noise_start.elapsed().as_millis() as u64;
|
|
||||||
warn!(
|
|
||||||
event = "connection.failed.noise",
|
|
||||||
peer = %address,
|
|
||||||
error = %err,
|
|
||||||
connect_ms,
|
|
||||||
noise_handshake_ms,
|
|
||||||
reconnection_attempt,
|
|
||||||
exit_reason = "noise_error",
|
|
||||||
"Failed to perform Noise initiator handshake with {address}"
|
|
||||||
);
|
|
||||||
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let noise_handshake_ms = noise_start.elapsed().as_millis() as u64;
|
|
||||||
self.current_reconnection.store(0, Ordering::Release);
|
|
||||||
debug!(
|
|
||||||
peer = %address,
|
|
||||||
connect_ms,
|
|
||||||
noise_handshake_ms,
|
|
||||||
"Noise initiator handshake completed for {:?}", address
|
|
||||||
);
|
|
||||||
Framed::new(noise_stream, NymCodec)
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
let connect_ms = connect_start.elapsed().as_millis() as u64;
|
|
||||||
warn!(
|
|
||||||
event = "connection.failed.connect",
|
|
||||||
peer = %address,
|
|
||||||
error = %err,
|
|
||||||
connect_ms,
|
|
||||||
reconnection_attempt,
|
|
||||||
exit_reason = "connect_error",
|
|
||||||
"failed to establish connection to {address}"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let connect_ms = connect_start.elapsed().as_millis() as u64;
|
let connect_ms = connect_start.elapsed().as_millis() as u64;
|
||||||
warn!(
|
debug!(
|
||||||
event = "connection.failed.timeout",
|
event = "connection.failed.timeout",
|
||||||
peer = %address,
|
peer = %address,
|
||||||
timeout_ms = self.connection_timeout.as_millis() as u64,
|
timeout_ms = self.connection_timeout.as_millis() as u64,
|
||||||
@@ -203,21 +193,163 @@ impl ManagedConnection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = self.message_receiver.map(Ok).forward(conn).await {
|
// 2. check if it actually succeeded
|
||||||
warn!(
|
let stream = match maybe_stream {
|
||||||
event = "connection.forward_error",
|
Ok(stream) => stream,
|
||||||
peer = %address,
|
Err(err) => {
|
||||||
error = %err,
|
let connect_ms = connect_start.elapsed().as_millis() as u64;
|
||||||
exit_reason = "forward_error",
|
debug!(
|
||||||
"Failed to forward packets to {address}: {err}"
|
event = "connection.failed.connect",
|
||||||
);
|
peer = %address,
|
||||||
}
|
error = %err,
|
||||||
|
connect_ms,
|
||||||
|
reconnection_attempt,
|
||||||
|
exit_reason = "connect_error",
|
||||||
|
"failed to establish connection to {address}"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let connect_ms = connect_start.elapsed().as_millis() as u64;
|
||||||
debug!(
|
debug!(
|
||||||
peer = %address,
|
peer = %address,
|
||||||
exit_reason = "sender_dropped",
|
connect_ms,
|
||||||
"connection manager to {address} finished"
|
"Managed to establish connection to {}", self.address
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// disable Nagle: mix packets are latency-sensitive and flushed one at a time.
|
||||||
|
if let Err(err) = stream.set_nodelay(true) {
|
||||||
|
warn!(peer = %address, error = %err, "failed to set TCP_NODELAY on outbound mixnet connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. perform noise handshake (if applicable)
|
||||||
|
let noise_start = tokio::time::Instant::now();
|
||||||
|
let noise_stream = match upgrade_noise_initiator(stream, &self.noise_config).await {
|
||||||
|
Ok(noise_stream) => noise_stream,
|
||||||
|
Err(err) => {
|
||||||
|
let noise_handshake_ms = noise_start.elapsed().as_millis() as u64;
|
||||||
|
debug!(
|
||||||
|
event = "connection.failed.noise",
|
||||||
|
peer = %address,
|
||||||
|
error = %err,
|
||||||
|
connect_ms,
|
||||||
|
noise_handshake_ms,
|
||||||
|
reconnection_attempt,
|
||||||
|
exit_reason = "noise_error",
|
||||||
|
"Failed to perform Noise initiator handshake with {address}"
|
||||||
|
);
|
||||||
|
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let noise_handshake_ms = noise_start.elapsed().as_millis() as u64;
|
||||||
|
self.current_reconnection.store(0, Ordering::Release);
|
||||||
|
debug!(
|
||||||
|
peer = %address,
|
||||||
|
connect_ms,
|
||||||
|
noise_handshake_ms,
|
||||||
|
"Noise initiator handshake completed for {:?}", address
|
||||||
|
);
|
||||||
|
let mut conn = Framed::new(noise_stream, NymCodec);
|
||||||
|
// let the write buffer accumulate several packets before flushing (see run_io_loop)
|
||||||
|
conn.set_backpressure_boundary(OUTBOUND_WRITE_BUFFER);
|
||||||
|
|
||||||
|
// 4. start handling the framed stream
|
||||||
|
run_io_loop(conn, self.message_receiver, address).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upper bound on how many already-queued packets we drain into a single flush.
|
||||||
|
/// Bounds the per-batch allocation and how often we re-check the read side; the actual
|
||||||
|
/// write coalescing is governed by the Framed backpressure boundary below.
|
||||||
|
const OUTBOUND_FLUSH_BATCH: usize = 1024;
|
||||||
|
|
||||||
|
/// Write-buffer high-water mark for the egress `Framed`: packets are coalesced up to
|
||||||
|
/// roughly this many bytes before a flush, trading a larger write burst for far fewer
|
||||||
|
/// syscalls (and noise frames) under load. Kept under the ~64KiB noise frame ceiling so
|
||||||
|
/// a flush is usually a single frame.
|
||||||
|
const OUTBOUND_WRITE_BUFFER: usize = 32 * 1024;
|
||||||
|
|
||||||
|
// The connection is unidirectional (send-only); we read from it solely to
|
||||||
|
// notice peer FIN/RST while idle so we can evict the cache entry before the
|
||||||
|
// next outbound send finds it stale.
|
||||||
|
async fn run_io_loop<T>(
|
||||||
|
conn: Framed<T, NymCodec>,
|
||||||
|
receiver: ReceiverStream<FramedNymPacket>,
|
||||||
|
address: SocketAddr,
|
||||||
|
) where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
let (mut sink, mut stream) = conn.split();
|
||||||
|
|
||||||
|
// drain all currently-queued packets into one flush rather than flushing per packet,
|
||||||
|
// which otherwise caps egress throughput and backs up the per-connection queue under load
|
||||||
|
let mut receiver = receiver.ready_chunks(OUTBOUND_FLUSH_BATCH);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
msg = stream.next() => {
|
||||||
|
match msg {
|
||||||
|
None => {
|
||||||
|
debug!(
|
||||||
|
peer = %address,
|
||||||
|
exit_reason = "peer_closed",
|
||||||
|
"peer closed mixnet connection to {address}"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(Err(err)) => {
|
||||||
|
debug!(
|
||||||
|
event = "connection.read_error",
|
||||||
|
peer = %address,
|
||||||
|
error = %err,
|
||||||
|
exit_reason = "read_error",
|
||||||
|
"read error on mixnet connection to {address}: {err}"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(Ok(_)) => {
|
||||||
|
trace!(
|
||||||
|
peer = %address,
|
||||||
|
"unexpected inbound packet on mixnet connection to {address}; discarding"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outgoing = receiver.next() => {
|
||||||
|
match outgoing {
|
||||||
|
None => {
|
||||||
|
debug!(
|
||||||
|
peer = %address,
|
||||||
|
exit_reason = "sender_dropped",
|
||||||
|
"connection manager to {address} finished"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(batch) => {
|
||||||
|
// feed the whole ready batch, then flush once
|
||||||
|
let res = async {
|
||||||
|
for packet in batch {
|
||||||
|
sink.feed(packet).await?;
|
||||||
|
}
|
||||||
|
sink.flush().await
|
||||||
|
}
|
||||||
|
.await;
|
||||||
|
if let Err(err) = res {
|
||||||
|
debug!(
|
||||||
|
event = "connection.forward_error",
|
||||||
|
peer = %address,
|
||||||
|
error = %err,
|
||||||
|
exit_reason = "forward_error",
|
||||||
|
"failed to forward packet batch to {address}: {err}"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,13 +396,18 @@ impl Client {
|
|||||||
sender.try_send(pending_packet).unwrap();
|
sender.try_send(pending_packet).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ownership token for the task we're about to spawn; lets it tell
|
||||||
|
// on exit whether the cache entry still names it.
|
||||||
|
let handle_token = Arc::new(());
|
||||||
|
|
||||||
// if we already tried to connect to `address` before, grab the current attempt count
|
// if we already tried to connect to `address` before, grab the current attempt count
|
||||||
let current_reconnection_attempt =
|
let current_reconnection_attempt =
|
||||||
if let Some(mut existing) = self.active_connections.get_mut(&address) {
|
if let Some(mut existing) = self.active_connections.get_mut(&address) {
|
||||||
existing.channel = sender;
|
existing.channel = sender;
|
||||||
|
existing.handle_token = Arc::clone(&handle_token);
|
||||||
Arc::clone(&existing.current_reconnection_attempt)
|
Arc::clone(&existing.current_reconnection_attempt)
|
||||||
} else {
|
} else {
|
||||||
let new_entry = ConnectionSender::new(sender);
|
let new_entry = ConnectionSender::new(sender, Arc::clone(&handle_token));
|
||||||
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
|
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
|
||||||
self.active_connections.insert(address, new_entry);
|
self.active_connections.insert(address, new_entry);
|
||||||
current_attempt
|
current_attempt
|
||||||
@@ -285,6 +422,7 @@ impl Client {
|
|||||||
|
|
||||||
let connections_count = self.connections_count.clone();
|
let connections_count = self.connections_count.clone();
|
||||||
let noise_config = self.noise_config.clone();
|
let noise_config = self.noise_config.clone();
|
||||||
|
let active_connections = self.active_connections.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// before executing the manager, wait for what was specified, if anything
|
// before executing the manager, wait for what was specified, if anything
|
||||||
if let Some(backoff) = backoff {
|
if let Some(backoff) = backoff {
|
||||||
@@ -299,6 +437,8 @@ impl Client {
|
|||||||
receiver,
|
receiver,
|
||||||
initial_connection_timeout,
|
initial_connection_timeout,
|
||||||
current_reconnection_attempt,
|
current_reconnection_attempt,
|
||||||
|
active_connections,
|
||||||
|
handle_token,
|
||||||
)
|
)
|
||||||
.run()
|
.run()
|
||||||
.await;
|
.await;
|
||||||
@@ -428,4 +568,102 @@ mod tests {
|
|||||||
client.config.maximum_reconnection_backoff
|
client.config.maximum_reconnection_backoff
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_addr() -> SocketAddr {
|
||||||
|
"127.0.0.1:1".parse().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_with_token(
|
||||||
|
active: &ActiveConnections,
|
||||||
|
addr: SocketAddr,
|
||||||
|
token: Arc<()>,
|
||||||
|
) -> mpsc::Receiver<FramedNymPacket> {
|
||||||
|
let (tx, rx) = mpsc::channel(1);
|
||||||
|
active.insert(addr, ConnectionSender::new(tx, token));
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn evict_on_drop_removes_entry_when_token_still_matches() {
|
||||||
|
let active = ActiveConnections::default();
|
||||||
|
let addr = test_addr();
|
||||||
|
let token = Arc::new(());
|
||||||
|
let _rx = insert_with_token(&active, addr, Arc::clone(&token));
|
||||||
|
|
||||||
|
assert!(active.get(&addr).is_some());
|
||||||
|
|
||||||
|
{
|
||||||
|
let _guard = EvictOnDrop {
|
||||||
|
active_connections: active.clone(),
|
||||||
|
address: addr,
|
||||||
|
handle_token: token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
active.get(&addr).is_none(),
|
||||||
|
"owning task's drop should evict the entry"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn evict_on_drop_preserves_entry_replaced_by_newer_make_connection() {
|
||||||
|
// Simulates the race: old task's run() has returned, but before its
|
||||||
|
// drop guard fires, a concurrent `make_connection` replaced the
|
||||||
|
// entry's channel + handle_token with a fresh task's token.
|
||||||
|
let active = ActiveConnections::default();
|
||||||
|
let addr = test_addr();
|
||||||
|
let old_token = Arc::new(());
|
||||||
|
let new_token = Arc::new(());
|
||||||
|
let _rx_new = insert_with_token(&active, addr, Arc::clone(&new_token));
|
||||||
|
|
||||||
|
{
|
||||||
|
let _guard = EvictOnDrop {
|
||||||
|
active_connections: active.clone(),
|
||||||
|
address: addr,
|
||||||
|
handle_token: old_token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
active.get(&addr).is_some(),
|
||||||
|
"old task's drop must not clobber the newer entry"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn io_loop_exits_when_peer_closes_idle_connection() {
|
||||||
|
// The fix's second half: while no packets are flowing, peer FIN/RST
|
||||||
|
// must still be observed so the cache entry can be evicted before the
|
||||||
|
// next send finds it stale.
|
||||||
|
let (a, b) = tokio::io::duplex(64);
|
||||||
|
let conn = Framed::new(a, NymCodec);
|
||||||
|
let (_tx, rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
let task = tokio::spawn(run_io_loop(conn, ReceiverStream::new(rx), test_addr()));
|
||||||
|
|
||||||
|
// Simulate peer closing both directions of the connection.
|
||||||
|
drop(b);
|
||||||
|
|
||||||
|
tokio::time::timeout(Duration::from_secs(1), task)
|
||||||
|
.await
|
||||||
|
.expect("io_loop must notice peer close while idle")
|
||||||
|
.expect("io_loop task must not panic");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn io_loop_exits_when_sender_dropped() {
|
||||||
|
let (a, _b) = tokio::io::duplex(64);
|
||||||
|
let conn = Framed::new(a, NymCodec);
|
||||||
|
let (tx, rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
let task = tokio::spawn(run_io_loop(conn, ReceiverStream::new(rx), test_addr()));
|
||||||
|
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
tokio::time::timeout(Duration::from_secs(1), task)
|
||||||
|
.await
|
||||||
|
.expect("io_loop must exit when the upstream sender is dropped")
|
||||||
|
.expect("io_loop task must not panic");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,16 @@ impl From<mpsc::UnboundedSender<PacketToForward>> for MixForwardingSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MixForwardingSender {
|
impl MixForwardingSender {
|
||||||
pub fn forward_packet(&self, packet: impl Into<PacketToForward>) -> Result<(), SendError> {
|
pub fn forward_packet(&self, packet: PacketToForward) -> Result<(), SendError> {
|
||||||
self.0
|
self.0
|
||||||
.unbounded_send(packet.into())
|
.unbounded_send(packet)
|
||||||
.map_err(|err| err.into_send_error())
|
.map_err(|err| err.into_send_error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn forward_client_packet_without_delay(&self, packet: MixPacket) -> Result<(), SendError> {
|
||||||
|
self.forward_packet(PacketToForward::client_packet_without_delay(packet))
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::len_without_is_empty)]
|
#[allow(clippy::len_without_is_empty)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
@@ -38,35 +42,23 @@ pub type MixForwardingReceiver = mpsc::UnboundedReceiver<PacketToForward>;
|
|||||||
pub struct PacketToForward {
|
pub struct PacketToForward {
|
||||||
pub packet: MixPacket,
|
pub packet: MixPacket,
|
||||||
pub forward_delay_target: Option<Instant>,
|
pub forward_delay_target: Option<Instant>,
|
||||||
}
|
pub network_monitor_packet: bool,
|
||||||
|
|
||||||
impl From<MixPacket> for PacketToForward {
|
|
||||||
fn from(packet: MixPacket) -> Self {
|
|
||||||
PacketToForward::new_no_delay(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(MixPacket, Option<Instant>)> for PacketToForward {
|
|
||||||
fn from((packet, delay_until): (MixPacket, Option<Instant>)) -> Self {
|
|
||||||
PacketToForward::new(packet, delay_until)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(MixPacket, Instant)> for PacketToForward {
|
|
||||||
fn from((packet, delay_until): (MixPacket, Instant)) -> Self {
|
|
||||||
PacketToForward::new(packet, Some(delay_until))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketToForward {
|
impl PacketToForward {
|
||||||
pub fn new(packet: MixPacket, forward_delay_target: Option<Instant>) -> Self {
|
pub fn new(
|
||||||
|
packet: MixPacket,
|
||||||
|
forward_delay_target: Option<Instant>,
|
||||||
|
network_monitor_packet: bool,
|
||||||
|
) -> Self {
|
||||||
PacketToForward {
|
PacketToForward {
|
||||||
packet,
|
packet,
|
||||||
forward_delay_target,
|
forward_delay_target,
|
||||||
|
network_monitor_packet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_no_delay(packet: MixPacket) -> Self {
|
pub fn client_packet_without_delay(packet: MixPacket) -> Self {
|
||||||
Self::new(packet, None)
|
Self::new(packet, None, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ nym-ecash-contract-common = { workspace = true }
|
|||||||
nym-multisig-contract-common = { workspace = true }
|
nym-multisig-contract-common = { workspace = true }
|
||||||
nym-group-contract-common = { workspace = true }
|
nym-group-contract-common = { workspace = true }
|
||||||
nym-performance-contract-common = { workspace = true }
|
nym-performance-contract-common = { workspace = true }
|
||||||
|
nym-network-monitors-contract-common = { workspace = true }
|
||||||
nym-node-families-contract-common = { workspace = true }
|
nym-node-families-contract-common = { workspace = true }
|
||||||
nym-serde-helpers = { workspace = true, features = ["hex", "base64"] }
|
nym-serde-helpers = { workspace = true, features = ["hex", "base64"] }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|||||||
@@ -104,6 +104,14 @@ impl TryFrom<NymNetworkDetails> for Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
pub fn new(nyxd_url: Url, api_url: Url, nyxd_config: nyxd::Config) -> Self {
|
||||||
|
Config {
|
||||||
|
api_url,
|
||||||
|
nyxd_url,
|
||||||
|
nyxd_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_from_nym_network_details(
|
pub fn try_from_nym_network_details(
|
||||||
details: &NymNetworkDetails,
|
details: &NymNetworkDetails,
|
||||||
) -> Result<Self, ValidatorClientError> {
|
) -> Result<Self, ValidatorClientError> {
|
||||||
@@ -114,6 +122,15 @@ impl Config {
|
|||||||
.map(|url| Url::parse(url))
|
.map(|url| Url::parse(url))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
if let Some(nym_api_urls) = details.nym_api_urls.as_ref() {
|
||||||
|
api_url.extend(
|
||||||
|
nym_api_urls
|
||||||
|
.iter()
|
||||||
|
.map(|url| url.url.parse())
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if api_url.is_empty() {
|
if api_url.is_empty() {
|
||||||
return Err(ValidatorClientError::NoAPIUrlAvailable);
|
return Err(ValidatorClientError::NoAPIUrlAvailable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,16 @@ use nym_api_requests::ecash::models::{
|
|||||||
VerifyEcashTicketBody,
|
VerifyEcashTicketBody,
|
||||||
};
|
};
|
||||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||||
|
use nym_api_requests::models::network_monitor::{
|
||||||
|
KnownNetworkMonitorResponse, StressTestBatchSubmission,
|
||||||
|
};
|
||||||
use nym_api_requests::models::node_families::NodeFamily;
|
use nym_api_requests::models::node_families::NodeFamily;
|
||||||
use nym_api_requests::models::{
|
use nym_api_requests::models::{
|
||||||
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainBlocksStatusResponse,
|
AnnotationResponseV1, ApiHealthResponse, BinaryBuildInformationOwned,
|
||||||
ChainStatusResponse, KeyRotationInfoResponse, NodePerformanceResponse, NodeRefreshBody,
|
ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse,
|
||||||
NymNodeDescriptionV1, NymNodeDescriptionV2, PerformanceHistoryResponse, RewardedSetResponse,
|
NodePerformanceResponse, NodeRefreshBody, NymNodeDescriptionV1, NymNodeDescriptionV2,
|
||||||
SignerInformationResponse,
|
PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse,
|
||||||
|
StressTestBatchSubmissionResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::pagination::PaginatedResponse;
|
use nym_api_requests::pagination::PaginatedResponse;
|
||||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||||
@@ -1016,7 +1020,7 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
async fn get_node_annotation(
|
async fn get_node_annotation(
|
||||||
&self,
|
&self,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
) -> Result<AnnotationResponse, NymAPIError> {
|
) -> Result<AnnotationResponseV1, NymAPIError> {
|
||||||
self.get_json(
|
self.get_json(
|
||||||
&[
|
&[
|
||||||
routes::V1_API_VERSION,
|
routes::V1_API_VERSION,
|
||||||
@@ -1399,6 +1403,53 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
|
|
||||||
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
|
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Queries the nym-api for whether a particular ed25519 identity key is currently recognised
|
||||||
|
/// as an authorised network monitor permitted to submit stress testing results.
|
||||||
|
///
|
||||||
|
/// `identity_key` is expected to be the base58-encoded form of the ed25519 public key.
|
||||||
|
#[instrument(level = "debug", skip(self))]
|
||||||
|
async fn get_known_network_monitor(
|
||||||
|
&self,
|
||||||
|
identity_key: IdentityKeyRef<'_>,
|
||||||
|
) -> Result<KnownNetworkMonitorResponse, NymAPIError> {
|
||||||
|
self.get_json(
|
||||||
|
&[
|
||||||
|
routes::V3_API_VERSION,
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::STRESS_TESTING,
|
||||||
|
routes::STRESS_TESTING_KNOWN_MONITORS,
|
||||||
|
identity_key,
|
||||||
|
],
|
||||||
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Submit a signed batch of stress-testing results to nym-api on behalf of a network monitor
|
||||||
|
/// orchestrator.
|
||||||
|
///
|
||||||
|
/// The caller is expected to have produced `request` via
|
||||||
|
/// `StressTestBatchSubmissionContent::new(...)` and signed it with the orchestrator's ed25519
|
||||||
|
/// key; nym-api will reject submissions that are stale, replayed, unauthorised, or whose
|
||||||
|
/// signature fails to verify.
|
||||||
|
#[instrument(level = "debug", skip(self, request))]
|
||||||
|
async fn submit_stress_testing_results(
|
||||||
|
&self,
|
||||||
|
request: &StressTestBatchSubmission,
|
||||||
|
) -> Result<StressTestBatchSubmissionResponse, NymAPIError> {
|
||||||
|
self.post_json(
|
||||||
|
&[
|
||||||
|
routes::V3_API_VERSION,
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::STRESS_TESTING,
|
||||||
|
routes::STRESS_TESTING_BATCH_SUBMIT,
|
||||||
|
],
|
||||||
|
NO_PARAMS,
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is already nym_http_api_client::Client (re-exported above), so just one impl needed
|
// Client is already nym_http_api_client::Client (re-exported above), so just one impl needed
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ pub mod nym_nodes {
|
|||||||
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
||||||
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
||||||
pub const BY_ADDRESSES: &str = "by-addresses";
|
pub const BY_ADDRESSES: &str = "by-addresses";
|
||||||
|
pub const STRESS_TESTING: &str = "stress-testing";
|
||||||
|
pub const STRESS_TESTING_KNOWN_MONITORS: &str = "known-monitors";
|
||||||
|
pub const STRESS_TESTING_BATCH_SUBMIT: &str = "batch-submit";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const STATUS_ROUTES: &str = "status";
|
pub const STATUS_ROUTES: &str = "status";
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod ecash_query_client;
|
|||||||
pub mod group_query_client;
|
pub mod group_query_client;
|
||||||
pub mod mixnet_query_client;
|
pub mod mixnet_query_client;
|
||||||
pub mod multisig_query_client;
|
pub mod multisig_query_client;
|
||||||
|
pub mod network_monitors_query_client;
|
||||||
pub mod node_families_query_client;
|
pub mod node_families_query_client;
|
||||||
pub mod performance_query_client;
|
pub mod performance_query_client;
|
||||||
pub mod vesting_query_client;
|
pub mod vesting_query_client;
|
||||||
@@ -23,6 +24,7 @@ pub mod ecash_signing_client;
|
|||||||
pub mod group_signing_client;
|
pub mod group_signing_client;
|
||||||
pub mod mixnet_signing_client;
|
pub mod mixnet_signing_client;
|
||||||
pub mod multisig_signing_client;
|
pub mod multisig_signing_client;
|
||||||
|
pub mod network_monitors_signing_client;
|
||||||
pub mod node_families_signing_client;
|
pub mod node_families_signing_client;
|
||||||
pub mod performance_signing_client;
|
pub mod performance_signing_client;
|
||||||
pub mod vesting_signing_client;
|
pub mod vesting_signing_client;
|
||||||
@@ -33,6 +35,9 @@ pub use ecash_query_client::{EcashQueryClient, PagedEcashQueryClient};
|
|||||||
pub use group_query_client::{GroupQueryClient, PagedGroupQueryClient};
|
pub use group_query_client::{GroupQueryClient, PagedGroupQueryClient};
|
||||||
pub use mixnet_query_client::{MixnetQueryClient, PagedMixnetQueryClient};
|
pub use mixnet_query_client::{MixnetQueryClient, PagedMixnetQueryClient};
|
||||||
pub use multisig_query_client::{MultisigQueryClient, PagedMultisigQueryClient};
|
pub use multisig_query_client::{MultisigQueryClient, PagedMultisigQueryClient};
|
||||||
|
pub use network_monitors_query_client::{
|
||||||
|
NetworkMonitorsQueryClient, PagedNetworkMonitorsQueryClient,
|
||||||
|
};
|
||||||
pub use node_families_query_client::{NodeFamiliesQueryClient, PagedNodeFamiliesQueryClient};
|
pub use node_families_query_client::{NodeFamiliesQueryClient, PagedNodeFamiliesQueryClient};
|
||||||
pub use performance_query_client::{PagedPerformanceQueryClient, PerformanceQueryClient};
|
pub use performance_query_client::{PagedPerformanceQueryClient, PerformanceQueryClient};
|
||||||
pub use vesting_query_client::{PagedVestingQueryClient, VestingQueryClient};
|
pub use vesting_query_client::{PagedVestingQueryClient, VestingQueryClient};
|
||||||
@@ -43,6 +48,7 @@ pub use ecash_signing_client::EcashSigningClient;
|
|||||||
pub use group_signing_client::GroupSigningClient;
|
pub use group_signing_client::GroupSigningClient;
|
||||||
pub use mixnet_signing_client::MixnetSigningClient;
|
pub use mixnet_signing_client::MixnetSigningClient;
|
||||||
pub use multisig_signing_client::MultisigSigningClient;
|
pub use multisig_signing_client::MultisigSigningClient;
|
||||||
|
pub use network_monitors_signing_client::NetworkMonitorsSigningClient;
|
||||||
pub use node_families_signing_client::NodeFamiliesSigningClient;
|
pub use node_families_signing_client::NodeFamiliesSigningClient;
|
||||||
pub use performance_signing_client::PerformanceSigningClient;
|
pub use performance_signing_client::PerformanceSigningClient;
|
||||||
pub use vesting_signing_client::VestingSigningClient;
|
pub use vesting_signing_client::VestingSigningClient;
|
||||||
@@ -53,6 +59,7 @@ pub trait NymContractsProvider {
|
|||||||
fn mixnet_contract_address(&self) -> Option<&AccountId>;
|
fn mixnet_contract_address(&self) -> Option<&AccountId>;
|
||||||
fn vesting_contract_address(&self) -> Option<&AccountId>;
|
fn vesting_contract_address(&self) -> Option<&AccountId>;
|
||||||
fn performance_contract_address(&self) -> Option<&AccountId>;
|
fn performance_contract_address(&self) -> Option<&AccountId>;
|
||||||
|
fn network_monitors_contract_address(&self) -> Option<&AccountId>;
|
||||||
fn node_families_contract_address(&self) -> Option<&AccountId>;
|
fn node_families_contract_address(&self) -> Option<&AccountId>;
|
||||||
|
|
||||||
// coconut-related
|
// coconut-related
|
||||||
@@ -67,6 +74,7 @@ pub struct TypedNymContracts {
|
|||||||
pub mixnet_contract_address: Option<AccountId>,
|
pub mixnet_contract_address: Option<AccountId>,
|
||||||
pub vesting_contract_address: Option<AccountId>,
|
pub vesting_contract_address: Option<AccountId>,
|
||||||
pub performance_contract_address: Option<AccountId>,
|
pub performance_contract_address: Option<AccountId>,
|
||||||
|
pub network_monitors_contract_address: Option<AccountId>,
|
||||||
pub node_families_contract_address: Option<AccountId>,
|
pub node_families_contract_address: Option<AccountId>,
|
||||||
|
|
||||||
pub ecash_contract_address: Option<AccountId>,
|
pub ecash_contract_address: Option<AccountId>,
|
||||||
@@ -92,6 +100,10 @@ impl TryFrom<NymContracts> for TypedNymContracts {
|
|||||||
.performance_contract_address
|
.performance_contract_address
|
||||||
.map(|addr| addr.parse())
|
.map(|addr| addr.parse())
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
|
network_monitors_contract_address: value
|
||||||
|
.network_monitors_contract_address
|
||||||
|
.map(|addr| addr.parse())
|
||||||
|
.transpose()?,
|
||||||
node_families_contract_address: value
|
node_families_contract_address: value
|
||||||
.node_families_contract_address
|
.node_families_contract_address
|
||||||
.map(|addr| addr.parse())
|
.map(|addr| addr.parse())
|
||||||
|
|||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::collect_paged;
|
||||||
|
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||||
|
use crate::nyxd::error::NyxdError;
|
||||||
|
use crate::nyxd::CosmWasmClient;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use nym_network_monitors_contract_common::{
|
||||||
|
AuthorisedNetworkMonitor, AuthorisedNetworkMonitorOrchestratorsResponse,
|
||||||
|
AuthorisedNetworkMonitorsPagedResponse, QueryMsg as NetworkMonitorsQueryMsg,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||||
|
pub trait NetworkMonitorsQueryClient {
|
||||||
|
async fn query_network_monitors_contract<T>(
|
||||||
|
&self,
|
||||||
|
query: NetworkMonitorsQueryMsg,
|
||||||
|
) -> Result<T, NyxdError>
|
||||||
|
where
|
||||||
|
for<'a> T: Deserialize<'a>;
|
||||||
|
|
||||||
|
async fn get_admin(&self) -> Result<cw_controllers::AdminResponse, NyxdError> {
|
||||||
|
self.query_network_monitors_contract(NetworkMonitorsQueryMsg::Admin {})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_network_monitor_orchestrators(
|
||||||
|
&self,
|
||||||
|
) -> Result<AuthorisedNetworkMonitorOrchestratorsResponse, NyxdError> {
|
||||||
|
self.query_network_monitors_contract(
|
||||||
|
NetworkMonitorsQueryMsg::NetworkMonitorOrchestrators {},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_network_monitor_agents_paged(
|
||||||
|
&self,
|
||||||
|
start_next_after: Option<SocketAddr>,
|
||||||
|
limit: Option<u32>,
|
||||||
|
) -> Result<AuthorisedNetworkMonitorsPagedResponse, NyxdError> {
|
||||||
|
self.query_network_monitors_contract(NetworkMonitorsQueryMsg::NetworkMonitorAgents {
|
||||||
|
start_next_after,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||||
|
pub trait PagedNetworkMonitorsQueryClient: NetworkMonitorsQueryClient {
|
||||||
|
async fn get_all_network_monitor_agents(
|
||||||
|
&self,
|
||||||
|
) -> Result<Vec<AuthorisedNetworkMonitor>, NyxdError> {
|
||||||
|
collect_paged!(self, get_network_monitor_agents_paged, authorised)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T> PagedNetworkMonitorsQueryClient for T where T: NetworkMonitorsQueryClient {}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||||
|
impl<C> NetworkMonitorsQueryClient for C
|
||||||
|
where
|
||||||
|
C: CosmWasmClient + NymContractsProvider + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn query_network_monitors_contract<T>(
|
||||||
|
&self,
|
||||||
|
query: NetworkMonitorsQueryMsg,
|
||||||
|
) -> Result<T, NyxdError>
|
||||||
|
where
|
||||||
|
for<'a> T: Deserialize<'a>,
|
||||||
|
{
|
||||||
|
let contract_address = &self
|
||||||
|
.network_monitors_contract_address()
|
||||||
|
.ok_or_else(|| NyxdError::unavailable_contract_address("network monitors contract"))?;
|
||||||
|
self.query_contract_smart(contract_address, &query).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||||
|
|
||||||
|
// it's enough that this compiles and clippy is happy about it
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn all_query_variants_are_covered<C: NetworkMonitorsQueryClient + Send + Sync>(
|
||||||
|
client: C,
|
||||||
|
msg: NetworkMonitorsQueryMsg,
|
||||||
|
) {
|
||||||
|
match msg {
|
||||||
|
NetworkMonitorsQueryMsg::Admin {} => client.get_admin().ignore(),
|
||||||
|
NetworkMonitorsQueryMsg::NetworkMonitorOrchestrators {} => {
|
||||||
|
client.get_network_monitor_orchestrators().ignore()
|
||||||
|
}
|
||||||
|
NetworkMonitorsQueryMsg::NetworkMonitorAgents { .. } => {
|
||||||
|
client.get_network_monitor_agents_paged(None, None).ignore()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||||
|
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||||
|
use crate::nyxd::error::NyxdError;
|
||||||
|
use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
|
||||||
|
use crate::signing::signer::OfflineSigner;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use nym_network_monitors_contract_common::ExecuteMsg as NetworkMonitorsExecuteMsg;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||||
|
pub trait NetworkMonitorsSigningClient {
|
||||||
|
async fn execute_network_monitors_contract(
|
||||||
|
&self,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
msg: NetworkMonitorsExecuteMsg,
|
||||||
|
memo: String,
|
||||||
|
funds: Vec<Coin>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError>;
|
||||||
|
|
||||||
|
async fn update_admin(
|
||||||
|
&self,
|
||||||
|
admin: String,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let msg = NetworkMonitorsExecuteMsg::UpdateAdmin { admin };
|
||||||
|
self.execute_network_monitors_contract(
|
||||||
|
fee,
|
||||||
|
msg,
|
||||||
|
"NetworkMonitorsExecuteMsg::UpdateAdmin".into(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authorise_network_monitor_orchestrator(
|
||||||
|
&self,
|
||||||
|
address: String,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let msg = NetworkMonitorsExecuteMsg::AuthoriseNetworkMonitorOrchestrator { address };
|
||||||
|
self.execute_network_monitors_contract(
|
||||||
|
fee,
|
||||||
|
msg,
|
||||||
|
"NetworkMonitorsExecuteMsg::AuthoriseNetworkMonitorOrchestrator".into(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Announce (or rotate) the ed25519 identity key of the calling network monitor orchestrator.
|
||||||
|
///
|
||||||
|
/// The caller must already be an authorised orchestrator; the contract validates that
|
||||||
|
/// `identity_key` is a well-formed base-58 encoding of a 32-byte ed25519 public key.
|
||||||
|
async fn update_orchestrator_identity_key(
|
||||||
|
&self,
|
||||||
|
identity_key: String,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let msg = NetworkMonitorsExecuteMsg::UpdateOrchestratorIdentityKey { key: identity_key };
|
||||||
|
self.execute_network_monitors_contract(
|
||||||
|
fee,
|
||||||
|
msg,
|
||||||
|
"NetworkMonitorsExecuteMsg::UpdateOrchestratorIdentityKey".into(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn revoke_network_monitor_orchestrator(
|
||||||
|
&self,
|
||||||
|
address: String,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let msg = NetworkMonitorsExecuteMsg::RevokeNetworkMonitorOrchestrator { address };
|
||||||
|
self.execute_network_monitors_contract(
|
||||||
|
fee,
|
||||||
|
msg,
|
||||||
|
"NetworkMonitorsExecuteMsg::RevokeNetworkMonitorOrchestrator".into(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authorise_network_monitor(
|
||||||
|
&self,
|
||||||
|
mixnet_address: SocketAddr,
|
||||||
|
bs58_x25519_noise: String,
|
||||||
|
noise_version: u8,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let msg = NetworkMonitorsExecuteMsg::AuthoriseNetworkMonitor {
|
||||||
|
mixnet_address,
|
||||||
|
bs58_x25519_noise,
|
||||||
|
noise_version,
|
||||||
|
};
|
||||||
|
self.execute_network_monitors_contract(
|
||||||
|
fee,
|
||||||
|
msg,
|
||||||
|
"NetworkMonitorsExecuteMsg::AuthoriseNetworkMonitor".into(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn revoke_network_monitor(
|
||||||
|
&self,
|
||||||
|
address: SocketAddr,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let msg = NetworkMonitorsExecuteMsg::RevokeNetworkMonitor { address };
|
||||||
|
self.execute_network_monitors_contract(
|
||||||
|
fee,
|
||||||
|
msg,
|
||||||
|
"NetworkMonitorsExecuteMsg::RevokeNetworkMonitor".into(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn revoke_all_network_monitors(
|
||||||
|
&self,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let msg = NetworkMonitorsExecuteMsg::RevokeAllNetworkMonitors;
|
||||||
|
self.execute_network_monitors_contract(
|
||||||
|
fee,
|
||||||
|
msg,
|
||||||
|
"NetworkMonitorsExecuteMsg::RevokeAllNetworkMonitors".into(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||||
|
impl<C> NetworkMonitorsSigningClient for C
|
||||||
|
where
|
||||||
|
C: SigningCosmWasmClient + NymContractsProvider + Sync,
|
||||||
|
NyxdError: From<<Self as OfflineSigner>::Error>,
|
||||||
|
{
|
||||||
|
async fn execute_network_monitors_contract(
|
||||||
|
&self,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
msg: NetworkMonitorsExecuteMsg,
|
||||||
|
memo: String,
|
||||||
|
funds: Vec<Coin>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let contract_address = &self
|
||||||
|
.network_monitors_contract_address()
|
||||||
|
.ok_or_else(|| NyxdError::unavailable_contract_address("network monitors contract"))?;
|
||||||
|
|
||||||
|
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||||
|
|
||||||
|
let signer_address = &self.signer_addresses()[0];
|
||||||
|
self.execute(signer_address, contract_address, &msg, fee, memo, funds)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||||
|
use nym_network_monitors_contract_common::ExecuteMsg;
|
||||||
|
|
||||||
|
// it's enough that this compiles and clippy is happy about it
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn all_execute_variants_are_covered<C: NetworkMonitorsSigningClient + Send + Sync>(
|
||||||
|
client: C,
|
||||||
|
msg: NetworkMonitorsExecuteMsg,
|
||||||
|
) {
|
||||||
|
match msg {
|
||||||
|
NetworkMonitorsExecuteMsg::UpdateAdmin { admin } => {
|
||||||
|
client.update_admin(admin, None).ignore()
|
||||||
|
}
|
||||||
|
ExecuteMsg::AuthoriseNetworkMonitorOrchestrator { address } => client
|
||||||
|
.authorise_network_monitor_orchestrator(address, None)
|
||||||
|
.ignore(),
|
||||||
|
ExecuteMsg::UpdateOrchestratorIdentityKey { key } => {
|
||||||
|
client.update_orchestrator_identity_key(key, None).ignore()
|
||||||
|
}
|
||||||
|
ExecuteMsg::RevokeNetworkMonitorOrchestrator { address } => client
|
||||||
|
.revoke_network_monitor_orchestrator(address, None)
|
||||||
|
.ignore(),
|
||||||
|
ExecuteMsg::AuthoriseNetworkMonitor {
|
||||||
|
mixnet_address: address,
|
||||||
|
bs58_x25519_noise,
|
||||||
|
noise_version,
|
||||||
|
} => client
|
||||||
|
.authorise_network_monitor(address, bs58_x25519_noise, noise_version, None)
|
||||||
|
.ignore(),
|
||||||
|
ExecuteMsg::RevokeNetworkMonitor { address } => {
|
||||||
|
client.revoke_network_monitor(address, None).ignore()
|
||||||
|
}
|
||||||
|
ExecuteMsg::RevokeAllNetworkMonitors => {
|
||||||
|
client.revoke_all_network_monitors(None).ignore()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -54,6 +54,27 @@ pub trait NodeFamiliesSigningClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the name and/or description of the caller's family. Each
|
||||||
|
/// argument follows `None = keep` / `Some(_) = replace` semantics; a
|
||||||
|
/// call with both `None` is a server-side no-op.
|
||||||
|
async fn update_family(
|
||||||
|
&self,
|
||||||
|
updated_name: Option<String>,
|
||||||
|
updated_description: Option<String>,
|
||||||
|
fee: Option<Fee>,
|
||||||
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
self.execute_node_families_contract(
|
||||||
|
fee,
|
||||||
|
NodeFamiliesExecuteMsg::UpdateFamily {
|
||||||
|
updated_name,
|
||||||
|
updated_description,
|
||||||
|
},
|
||||||
|
"NodeFamiliesContract::UpdateFamily".to_string(),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn disband_family(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
async fn disband_family(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||||
self.execute_node_families_contract(
|
self.execute_node_families_contract(
|
||||||
fee,
|
fee,
|
||||||
@@ -224,6 +245,12 @@ mod tests {
|
|||||||
NodeFamiliesExecuteMsg::CreateFamily { name, description } => client
|
NodeFamiliesExecuteMsg::CreateFamily { name, description } => client
|
||||||
.create_family(name, description, None, vec![])
|
.create_family(name, description, None, vec![])
|
||||||
.ignore(),
|
.ignore(),
|
||||||
|
NodeFamiliesExecuteMsg::UpdateFamily {
|
||||||
|
updated_name,
|
||||||
|
updated_description,
|
||||||
|
} => client
|
||||||
|
.update_family(updated_name, updated_description, None)
|
||||||
|
.ignore(),
|
||||||
NodeFamiliesExecuteMsg::DisbandFamily {} => client.disband_family(None).ignore(),
|
NodeFamiliesExecuteMsg::DisbandFamily {} => client.disband_family(None).ignore(),
|
||||||
NodeFamiliesExecuteMsg::InviteToFamily {
|
NodeFamiliesExecuteMsg::InviteToFamily {
|
||||||
node_id,
|
node_id,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pub mod logs;
|
|||||||
pub mod module_traits;
|
pub mod module_traits;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct SigningClientOptions {
|
pub(crate) struct SigningClientOptions {
|
||||||
gas_price: GasPrice,
|
gas_price: GasPrice,
|
||||||
simulated_gas_multiplier: f32,
|
simulated_gas_multiplier: f32,
|
||||||
@@ -80,6 +80,17 @@ impl<C, S> MaybeSigningClient<C, S> {
|
|||||||
opts,
|
opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clone_query_client(&self) -> MaybeSigningClient<C, NoSigner>
|
||||||
|
where
|
||||||
|
C: Clone,
|
||||||
|
{
|
||||||
|
MaybeSigningClient {
|
||||||
|
client: self.client.clone(),
|
||||||
|
signer: Default::default(),
|
||||||
|
opts: self.opts.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "http-client")]
|
#[cfg(feature = "http-client")]
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ use async_trait::async_trait;
|
|||||||
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
|
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
|
||||||
use cosmrs::tx::{Raw, SignDoc};
|
use cosmrs::tx::{Raw, SignDoc};
|
||||||
use cosmwasm_std::Addr;
|
use cosmwasm_std::Addr;
|
||||||
|
use nym_contracts_common::build_information::CONTRACT_BUILD_INFO_STORAGE_KEY;
|
||||||
|
use nym_contracts_common::ContractBuildInformation;
|
||||||
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -40,6 +42,7 @@ pub use crate::nyxd::{
|
|||||||
fee::Fee,
|
fee::Fee,
|
||||||
};
|
};
|
||||||
pub use crate::rpc::TendermintRpcClient;
|
pub use crate::rpc::TendermintRpcClient;
|
||||||
|
pub use bip39;
|
||||||
pub use coin::Coin;
|
pub use coin::Coin;
|
||||||
pub use cosmrs::{
|
pub use cosmrs::{
|
||||||
bank::MsgSend,
|
bank::MsgSend,
|
||||||
@@ -70,14 +73,19 @@ pub use tendermint_rpc::{
|
|||||||
Paging, Request, Response, SimpleRequest,
|
Paging, Request, Response, SimpleRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use nym_ecash_contract_common;
|
||||||
|
pub use nym_mixnet_contract_common;
|
||||||
|
pub use nym_multisig_contract_common;
|
||||||
|
pub use nym_network_monitors_contract_common;
|
||||||
|
pub use nym_performance_contract_common;
|
||||||
|
pub use nym_vesting_contract_common;
|
||||||
|
|
||||||
#[cfg(feature = "http-client")]
|
#[cfg(feature = "http-client")]
|
||||||
use crate::http_client;
|
use crate::http_client;
|
||||||
#[cfg(feature = "http-client")]
|
#[cfg(feature = "http-client")]
|
||||||
use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient};
|
use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient};
|
||||||
#[cfg(feature = "http-client")]
|
#[cfg(feature = "http-client")]
|
||||||
use cosmrs::rpc::{HttpClient, HttpClientUrl};
|
use cosmrs::rpc::{HttpClient, HttpClientUrl};
|
||||||
use nym_contracts_common::build_information::CONTRACT_BUILD_INFO_STORAGE_KEY;
|
|
||||||
use nym_contracts_common::ContractBuildInformation;
|
|
||||||
|
|
||||||
pub mod coin;
|
pub mod coin;
|
||||||
pub mod contract_traits;
|
pub mod contract_traits;
|
||||||
@@ -262,6 +270,16 @@ impl<C, S> NyxdClient<C, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clone_query_client(&self) -> NyxdClient<C>
|
||||||
|
where
|
||||||
|
C: Clone,
|
||||||
|
{
|
||||||
|
NyxdClient {
|
||||||
|
client: self.client.clone_query_client(),
|
||||||
|
config: self.config.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn current_config(&self) -> &Config {
|
pub fn current_config(&self) -> &Config {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
@@ -293,6 +311,10 @@ impl<C, S> NyxdClient<C, S> {
|
|||||||
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
|
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
|
||||||
self.config.simulated_gas_multiplier = multiplier;
|
self.config.simulated_gas_multiplier = multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_nym_contracts(&self) -> TypedNymContracts {
|
||||||
|
self.config.contracts.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, S> NymContractsProvider for NyxdClient<C, S> {
|
impl<C, S> NymContractsProvider for NyxdClient<C, S> {
|
||||||
@@ -307,6 +329,12 @@ impl<C, S> NymContractsProvider for NyxdClient<C, S> {
|
|||||||
fn performance_contract_address(&self) -> Option<&AccountId> {
|
fn performance_contract_address(&self) -> Option<&AccountId> {
|
||||||
self.config.contracts.performance_contract_address.as_ref()
|
self.config.contracts.performance_contract_address.as_ref()
|
||||||
}
|
}
|
||||||
|
fn network_monitors_contract_address(&self) -> Option<&AccountId> {
|
||||||
|
self.config
|
||||||
|
.contracts
|
||||||
|
.network_monitors_contract_address
|
||||||
|
.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
fn node_families_contract_address(&self) -> Option<&AccountId> {
|
fn node_families_contract_address(&self) -> Option<&AccountId> {
|
||||||
self.config
|
self.config
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Blacklist types. **The blacklist surface is stubbed today** - the execute
|
||||||
|
//! handlers always return `UnimplementedBlacklisting` and the storage map is
|
||||||
|
//! never populated. These types are kept for the planned redesign.
|
||||||
|
|
||||||
use cosmwasm_schema::cw_serde;
|
use cosmwasm_schema::cw_serde;
|
||||||
|
|
||||||
|
/// Public-key + metadata pair surfaced by `GetBlacklistedAccount` /
|
||||||
|
/// `GetBlacklistPaged`. Always empty on a freshly deployed contract.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct BlacklistedAccount {
|
pub struct BlacklistedAccount {
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
@@ -15,6 +21,8 @@ impl From<(String, Blacklisting)> for BlacklistedAccount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Per-key blacklist record: the multisig proposal that approved it and the
|
||||||
|
/// block height at which finalisation landed (None until finalised).
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct Blacklisting {
|
pub struct Blacklisting {
|
||||||
pub proposal_id: u64,
|
pub proposal_id: u64,
|
||||||
@@ -36,6 +44,8 @@ impl BlacklistedAccount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Page of blacklist entries returned by `GetBlacklistPaged`. Always empty on
|
||||||
|
/// a freshly deployed contract.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct PagedBlacklistedAccountResponse {
|
pub struct PagedBlacklistedAccountResponse {
|
||||||
pub accounts: Vec<BlacklistedAccount>,
|
pub accounts: Vec<BlacklistedAccount>,
|
||||||
@@ -59,6 +69,8 @@ impl PagedBlacklistedAccountResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response shape for `GetBlacklistedAccount`. `account` is `None` for any
|
||||||
|
/// key not present in the (currently always-empty) blacklist.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct BlacklistedAccountResponse {
|
pub struct BlacklistedAccountResponse {
|
||||||
pub account: Option<Blacklisting>,
|
pub account: Option<Blacklisting>,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
use cosmwasm_schema::cw_serde;
|
use cosmwasm_schema::cw_serde;
|
||||||
use cosmwasm_std::Coin;
|
use cosmwasm_std::Coin;
|
||||||
|
|
||||||
|
/// Pool-level deposit accounting. Updated by every successful
|
||||||
|
/// `DepositTicketBookFunds` and (for the unredeemed-tickets counter) by every
|
||||||
|
/// successful legacy `RedeemTickets`.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct PoolCounters {
|
pub struct PoolCounters {
|
||||||
/// Represents the total amount of funds deposited into the contract.
|
/// Represents the total amount of funds deposited into the contract.
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ use crate::error::EcashContractError;
|
|||||||
use cosmwasm_schema::cw_serde;
|
use cosmwasm_schema::cw_serde;
|
||||||
use cosmwasm_std::{StdError, StdResult};
|
use cosmwasm_std::{StdError, StdResult};
|
||||||
|
|
||||||
|
/// Sequential identifier assigned to every accepted deposit. Starts at 0 and
|
||||||
|
/// is never recycled.
|
||||||
pub type DepositId = u32;
|
pub type DepositId = u32;
|
||||||
|
|
||||||
|
/// Opaque on-chain record of a deposit: the depositor-claimed bs58-encoded
|
||||||
|
/// ed25519 identity public key. The contract does not verify control of the
|
||||||
|
/// corresponding private key.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct Deposit {
|
pub struct Deposit {
|
||||||
pub bs58_encoded_ed25519_pubkey: String,
|
pub bs58_encoded_ed25519_pubkey: String,
|
||||||
@@ -19,6 +24,8 @@ impl Deposit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decode a bs58-encoded ed25519 public key to its 32-byte raw form.
|
||||||
|
/// Surfaces `MalformedEd25519Identity` on any bs58 / length failure.
|
||||||
pub fn get_ed25519_pubkey_bytes(raw: &str) -> Result<[u8; 32], EcashContractError> {
|
pub fn get_ed25519_pubkey_bytes(raw: &str) -> Result<[u8; 32], EcashContractError> {
|
||||||
let mut ed25519_pubkey_bytes = [0u8; 32];
|
let mut ed25519_pubkey_bytes = [0u8; 32];
|
||||||
bs58::decode(raw)
|
bs58::decode(raw)
|
||||||
@@ -32,10 +39,13 @@ impl Deposit {
|
|||||||
bs58::encode(raw).into_string()
|
bs58::encode(raw).into_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decode this deposit's identity key to its 32-byte raw form for storage.
|
||||||
pub fn to_bytes(&self) -> Result<[u8; 32], EcashContractError> {
|
pub fn to_bytes(&self) -> Result<[u8; 32], EcashContractError> {
|
||||||
Self::get_ed25519_pubkey_bytes(&self.bs58_encoded_ed25519_pubkey)
|
Self::get_ed25519_pubkey_bytes(&self.bs58_encoded_ed25519_pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reconstruct a `Deposit` from a raw 32-byte ed25519 pubkey as stored
|
||||||
|
/// under the `"deposit"` namespace.
|
||||||
pub fn try_from_bytes(bytes: &[u8]) -> StdResult<Self> {
|
pub fn try_from_bytes(bytes: &[u8]) -> StdResult<Self> {
|
||||||
if bytes.len() != 32 {
|
if bytes.len() != 32 {
|
||||||
return Err(StdError::generic_err("malformed deposit data"));
|
return Err(StdError::generic_err("malformed deposit data"));
|
||||||
@@ -47,12 +57,16 @@ impl Deposit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response shape for `GetLatestDeposit`. `deposit` is `None` on a freshly
|
||||||
|
/// deployed contract.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct LatestDepositResponse {
|
pub struct LatestDepositResponse {
|
||||||
pub deposit: Option<DepositData>,
|
pub deposit: Option<DepositData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response shape for `GetDeposit { deposit_id }`. `deposit` is `None` when
|
||||||
|
/// the id has not yet been assigned (`id >= total_deposits_made`).
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct DepositResponse {
|
pub struct DepositResponse {
|
||||||
pub id: DepositId,
|
pub id: DepositId,
|
||||||
@@ -60,6 +74,8 @@ pub struct DepositResponse {
|
|||||||
pub deposit: Option<Deposit>,
|
pub deposit: Option<Deposit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `(deposit_id, deposit)` pair surfaced by the latest-deposit and paginated
|
||||||
|
/// deposit queries.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct DepositData {
|
pub struct DepositData {
|
||||||
pub id: DepositId,
|
pub id: DepositId,
|
||||||
@@ -73,6 +89,8 @@ impl From<(DepositId, Deposit)> for DepositData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Page of deposits returned by `GetDepositsPaged`. `start_next_after` is the
|
||||||
|
/// id of the last returned entry; pass it as the next call's `start_after`.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct PagedDepositsResponse {
|
pub struct PagedDepositsResponse {
|
||||||
pub deposits: Vec<DepositData>,
|
pub deposits: Vec<DepositData>,
|
||||||
|
|||||||
@@ -6,69 +6,108 @@ use cw_controllers::AdminError;
|
|||||||
use cw_utils::PaymentError;
|
use cw_utils::PaymentError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Errors surfaced by the ecash contract. Each reachable variant is named in at
|
||||||
|
/// least one scenario of `openspec/specs/ecash-contract/spec.md`.
|
||||||
#[derive(Error, Debug, PartialEq)]
|
#[derive(Error, Debug, PartialEq)]
|
||||||
pub enum EcashContractError {
|
pub enum EcashContractError {
|
||||||
|
/// Wrapper for any underlying `cosmwasm_std::StdError` (storage faults,
|
||||||
|
/// address validation, etc.).
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Std(#[from] StdError),
|
Std(#[from] StdError),
|
||||||
|
|
||||||
|
/// Raised by `cw_utils::must_pay` on `DepositTicketBookFunds` when funds
|
||||||
|
/// are missing, multi-denom, or in the wrong denom. Inner variants
|
||||||
|
/// `NoFunds`, `MultipleDenoms`, `MissingDenom` are all reachable.
|
||||||
#[error("Invalid deposit")]
|
#[error("Invalid deposit")]
|
||||||
InvalidDeposit(#[from] PaymentError),
|
InvalidDeposit(#[from] PaymentError),
|
||||||
|
|
||||||
|
/// `DepositTicketBookFunds` with the right denom but a non-matching amount.
|
||||||
|
/// `amount` is the reduced amount (if the sender is whitelisted) or the
|
||||||
|
/// default amount.
|
||||||
#[error("received wrong amount for deposit. got: {received}. required: {amount}")]
|
#[error("received wrong amount for deposit. got: {received}. required: {amount}")]
|
||||||
WrongAmount { received: Coin, amount: Coin },
|
WrongAmount { received: Coin, amount: Coin },
|
||||||
|
|
||||||
|
/// **Unreachable** - preserved for forward compatibility (no current
|
||||||
|
/// execute path triggers this).
|
||||||
#[error("There aren't enough funds in the contract")]
|
#[error("There aren't enough funds in the contract")]
|
||||||
NotEnoughFunds,
|
NotEnoughFunds,
|
||||||
|
|
||||||
|
/// Wrapper for `cw_controllers::AdminError`. Raised by every admin-gated
|
||||||
|
/// and multisig-gated handler when the sender is wrong.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Admin(#[from] AdminError),
|
Admin(#[from] AdminError),
|
||||||
|
|
||||||
|
/// Redemption-proposal reply could not find a `proposal_id` attribute on
|
||||||
|
/// the multisig `wasm` event.
|
||||||
#[error("could not find proposal id inside the multisig reply SubMsg")]
|
#[error("could not find proposal id inside the multisig reply SubMsg")]
|
||||||
MissingProposalId,
|
MissingProposalId,
|
||||||
|
|
||||||
// realistically this should NEVER be thrown
|
/// Redemption-proposal reply found a `proposal_id` attribute that could
|
||||||
|
/// not be parsed as `u64`. Realistically unreachable.
|
||||||
#[error("the proposal id returned by the multisig contract could not be parsed into an u64")]
|
#[error("the proposal id returned by the multisig contract could not be parsed into an u64")]
|
||||||
MalformedProposalId,
|
MalformedProposalId,
|
||||||
|
|
||||||
|
/// Instantiation given a `group_addr` that failed bech32 validation.
|
||||||
#[error("Group contract invalid address '{addr}'")]
|
#[error("Group contract invalid address '{addr}'")]
|
||||||
InvalidGroup { addr: String },
|
InvalidGroup { addr: String },
|
||||||
|
|
||||||
|
/// **Unreachable** - no current execute path triggers this.
|
||||||
#[error("Unauthorized")]
|
#[error("Unauthorized")]
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
|
|
||||||
|
/// **Unreachable** - preserved for future SemVer comparisons during migration.
|
||||||
#[error("Failed to parse {value} into a valid SemVer version: {error_message}")]
|
#[error("Failed to parse {value} into a valid SemVer version: {error_message}")]
|
||||||
SemVerFailure {
|
SemVerFailure {
|
||||||
value: String,
|
value: String,
|
||||||
error_message: String,
|
error_message: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Reply dispatcher saw an `id` that does not match
|
||||||
|
/// `BLACKLIST_PROPOSAL_REPLY_ID` or `REDEMPTION_PROPOSAL_REPLY_ID`.
|
||||||
#[error("received an invalid reply id: {id}. it does not correspond to any sent SubMsg")]
|
#[error("received an invalid reply id: {id}. it does not correspond to any sent SubMsg")]
|
||||||
InvalidReplyId { id: u64 },
|
InvalidReplyId { id: u64 },
|
||||||
|
|
||||||
|
/// **Unreachable** - preserved for the (future) typed-deposit-info feature.
|
||||||
#[error("reached the maximum of 255 different deposit types")]
|
#[error("reached the maximum of 255 different deposit types")]
|
||||||
MaximumDepositTypesReached,
|
MaximumDepositTypesReached,
|
||||||
|
|
||||||
|
/// **Unreachable** - preserved for the (future) typed-deposit-info feature.
|
||||||
#[error("compressed deposit info {typ} does not corresponds to any known type")]
|
#[error("compressed deposit info {typ} does not corresponds to any known type")]
|
||||||
UnknownCompressedDepositInfoType { typ: u8 },
|
UnknownCompressedDepositInfoType { typ: u8 },
|
||||||
|
|
||||||
|
/// **Unreachable** - preserved for the (future) typed-deposit-info feature.
|
||||||
#[error("deposit info {typ} does not corresponds to any previously seen type")]
|
#[error("deposit info {typ} does not corresponds to any previously seen type")]
|
||||||
UnknownDepositInfoType { typ: String },
|
UnknownDepositInfoType { typ: String },
|
||||||
|
|
||||||
|
/// `DepositTicketBookFunds` with an `identity_key` that fails to bs58-decode
|
||||||
|
/// to exactly 32 bytes. Raised inside `Deposit::to_bytes` during
|
||||||
|
/// `save_deposit`.
|
||||||
#[error("the provided ed25519 identity was malformed")]
|
#[error("the provided ed25519 identity was malformed")]
|
||||||
MalformedEd25519Identity,
|
MalformedEd25519Identity,
|
||||||
|
|
||||||
|
/// `nym_network_defaults::TICKETBOOK_SIZE` has diverged from the value
|
||||||
|
/// snapshotted at instantiation in `Item<Invariants>`. Tripwire for
|
||||||
|
/// uncoordinated network-defaults bumps.
|
||||||
#[error("the ticket book size has changed since the contract was created! This was not expected! It used to be {at_init} but it's {current} now! Please let the developers know ASAP!")]
|
#[error("the ticket book size has changed since the contract was created! This was not expected! It used to be {at_init} but it's {current} now! Please let the developers know ASAP!")]
|
||||||
TicketBookSizeChanged { at_init: u64, current: u64 },
|
TicketBookSizeChanged { at_init: u64, current: u64 },
|
||||||
|
|
||||||
|
/// `RequestRedemption` with a `commitment_bs58` that does not decode to a
|
||||||
|
/// 32-byte sha256 digest.
|
||||||
#[error("the provided tickets redemption commitment is malformed")]
|
#[error("the provided tickets redemption commitment is malformed")]
|
||||||
MalformedRedemptionCommitment,
|
MalformedRedemptionCommitment,
|
||||||
|
|
||||||
|
/// Always thrown by `ProposeToBlacklist` and `AddToBlacklist` until the
|
||||||
|
/// blacklist redesign lands.
|
||||||
#[error("the account blacklisting hasn't been fully implemented yet")]
|
#[error("the account blacklisting hasn't been fully implemented yet")]
|
||||||
UnimplementedBlacklisting,
|
UnimplementedBlacklisting,
|
||||||
|
|
||||||
|
/// `SetReducedDepositPrice` (or migration whitelist seeding) given a coin
|
||||||
|
/// whose denom does not match `Config::deposit_amount.denom`.
|
||||||
#[error("reduced deposit must use the same denom as the default deposit (expected '{expected}', got '{got}')")]
|
#[error("reduced deposit must use the same denom as the default deposit (expected '{expected}', got '{got}')")]
|
||||||
InvalidReducedDepositDenom { expected: String, got: String },
|
InvalidReducedDepositDenom { expected: String, got: String },
|
||||||
|
|
||||||
|
/// `SetReducedDepositPrice` (or migration whitelist seeding) given a
|
||||||
|
/// reduced amount not strictly less than the current default.
|
||||||
#[error(
|
#[error(
|
||||||
"reduced deposit amount ({reduced}) must be strictly less than the default ({default})"
|
"reduced deposit amount ({reduced}) must be strictly less than the default ({default})"
|
||||||
)]
|
)]
|
||||||
@@ -77,9 +116,13 @@ pub enum EcashContractError {
|
|||||||
default: cosmwasm_std::Uint128,
|
default: cosmwasm_std::Uint128,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// `RemoveReducedDepositPrice` invoked for an address with no current
|
||||||
|
/// reduced-deposit entry.
|
||||||
#[error("address '{address}' does not have a custom reduced deposit price set")]
|
#[error("address '{address}' does not have a custom reduced deposit price set")]
|
||||||
NoReducedDepositPrice { address: String },
|
NoReducedDepositPrice { address: String },
|
||||||
|
|
||||||
|
/// `UpdateDefaultDepositValue` or `SetReducedDepositPrice` given an amount
|
||||||
|
/// below `nym_network_defaults::TICKETBOOK_SIZE`.
|
||||||
#[error(
|
#[error(
|
||||||
"deposit amount ({amount}) must be at least the ticket book size ({ticket_book_size})"
|
"deposit amount ({amount}) must be at least the ticket book size ({ticket_book_size})"
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/// Duplicate of `events::PROPOSAL_ID_ATTRIBUTE_NAME`. **Dead code**: not
|
||||||
|
/// referenced anywhere in the workspace today; preserved here pending a
|
||||||
|
/// follow-on cleanup. Use `events::PROPOSAL_ID_ATTRIBUTE_NAME` instead.
|
||||||
pub const BANDWIDTH_PROPOSAL_ID: &str = "proposal_id";
|
pub const BANDWIDTH_PROPOSAL_ID: &str = "proposal_id";
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// event types
|
//! Event names and attribute keys emitted by the ecash contract. Renaming any
|
||||||
|
//! of these is a breaking change for indexers and downstream tooling.
|
||||||
|
|
||||||
|
/// Event type emitted by every successful `DepositTicketBookFunds`. Carries a
|
||||||
|
/// single `deposit-id` attribute with the assigned id as a decimal string.
|
||||||
pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds";
|
pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds";
|
||||||
|
|
||||||
|
/// Attribute key on the `deposited-funds` event: the newly assigned deposit id.
|
||||||
pub const DEPOSIT_ID: &str = "deposit-id";
|
pub const DEPOSIT_ID: &str = "deposit-id";
|
||||||
|
|
||||||
|
/// Name of the cosmwasm-std auto-generated event that carries handler
|
||||||
|
/// attributes (`updated_deposit`, `action`, `address`, `deposit`,
|
||||||
|
/// `proposal_id`).
|
||||||
pub const WASM_EVENT_NAME: &str = "wasm";
|
pub const WASM_EVENT_NAME: &str = "wasm";
|
||||||
|
|
||||||
|
/// Attribute key carrying the multisig-issued `proposal_id` on the `wasm`
|
||||||
|
/// event from the redemption-proposal reply handler.
|
||||||
pub const PROPOSAL_ID_ATTRIBUTE_NAME: &str = "proposal_id";
|
pub const PROPOSAL_ID_ATTRIBUTE_NAME: &str = "proposal_id";
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Shared types, messages, events, and errors for the ecash contract.
|
||||||
|
//!
|
||||||
|
//! Consumed by both the contract crate (`contracts/ecash`) and any off-chain
|
||||||
|
//! client (gateways, nym-api signers, indexers, validator-client). See
|
||||||
|
//! `openspec/specs/ecash-contract/spec.md` for the normative interface.
|
||||||
|
|
||||||
pub mod blacklist;
|
pub mod blacklist;
|
||||||
pub mod counters;
|
pub mod counters;
|
||||||
pub mod deposit;
|
pub mod deposit;
|
||||||
|
|||||||
@@ -15,100 +15,134 @@ use crate::reduced_deposit::WhitelistedAccountsResponse;
|
|||||||
#[cfg(feature = "schema")]
|
#[cfg(feature = "schema")]
|
||||||
use cosmwasm_schema::QueryResponses;
|
use cosmwasm_schema::QueryResponses;
|
||||||
|
|
||||||
|
/// Instantiation payload. The sender of the instantiate transaction becomes the
|
||||||
|
/// contract admin; the three addresses are bech32-validated and persisted as
|
||||||
|
/// immutable cross-contract pointers (see spec requirement "Contract instantiation").
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct InstantiateMsg {
|
pub struct InstantiateMsg {
|
||||||
|
/// Cosmos SDK address reserved for the future pool-contract transition.
|
||||||
|
/// Stored in `Config` but never debited by the current contract.
|
||||||
pub holding_account: String,
|
pub holding_account: String,
|
||||||
|
|
||||||
|
/// cw3 multisig contract that gates `RedeemTickets` and (in the redesign)
|
||||||
|
/// blacklist proposals. Not updatable through any execute path.
|
||||||
pub multisig_addr: String,
|
pub multisig_addr: String,
|
||||||
|
|
||||||
|
/// cw4 group contract referenced by the (stubbed) blacklist proposal flow.
|
||||||
pub group_addr: String,
|
pub group_addr: String,
|
||||||
|
|
||||||
|
/// Default per-deposit price. The denom of this coin is the contract's
|
||||||
|
/// canonical denom for the rest of its lifetime.
|
||||||
pub deposit_amount: Coin,
|
pub deposit_amount: Coin,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub enum ExecuteMsg {
|
pub enum ExecuteMsg {
|
||||||
/// Used by clients to request ticket books from the signers
|
/// Submitted by clients to escrow funds and register a claimed ed25519
|
||||||
DepositTicketBookFunds {
|
/// identity key. Mints a sequential `deposit_id`. The contract does not
|
||||||
identity_key: String,
|
/// verify control of the identity key - that proof is checked off-chain by
|
||||||
},
|
/// nym-api signers at blind-sign time.
|
||||||
|
DepositTicketBookFunds { identity_key: String },
|
||||||
|
|
||||||
/// Used by gateways to batch redeem tokens from the spent tickets
|
/// Submitted by gateways to request batch redemption of spent tickets.
|
||||||
|
/// Dispatches a `Propose` SubMsg to the multisig contract; the actual
|
||||||
|
/// transfer effect is gated behind multisig approval.
|
||||||
RequestRedemption {
|
RequestRedemption {
|
||||||
commitment_bs58: String,
|
commitment_bs58: String,
|
||||||
number_of_tickets: u16,
|
number_of_tickets: u16,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The actual message that gets executed, after multisig votes, that transfers the ticket tokens into gateway's (and the holding) account
|
/// **Legacy / dead code.** Only callable by the multisig; bumps the
|
||||||
RedeemTickets {
|
/// unredeemed-tickets counter and emits a `ticket_redemption` event with
|
||||||
n: u16,
|
/// `moved_to_holding_account = "false"`. No known consumer depends on the
|
||||||
gw: String,
|
/// side effects; candidate for removal in a follow-on breaking-schema
|
||||||
},
|
/// change.
|
||||||
|
RedeemTickets { n: u16, gw: String },
|
||||||
|
|
||||||
UpdateAdmin {
|
/// Transfer the contract admin role. Only the current admin may sign.
|
||||||
admin: String,
|
/// Dispatches via the cw_controllers `execute_update_admin` handshake.
|
||||||
},
|
UpdateAdmin { admin: String },
|
||||||
|
|
||||||
|
/// Overwrite `Config::deposit_amount`. Only callable by the contract admin.
|
||||||
|
/// Rejects values below `nym_network_defaults::TICKETBOOK_SIZE` and trips
|
||||||
|
/// `TicketBookSizeChanged` if the snapshotted invariant has diverged from
|
||||||
|
/// the current crate constant.
|
||||||
#[serde(alias = "update_deposit_value")]
|
#[serde(alias = "update_deposit_value")]
|
||||||
UpdateDefaultDepositValue {
|
UpdateDefaultDepositValue { new_deposit: Coin },
|
||||||
new_deposit: Coin,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Set (or overwrite) a reduced deposit price for a specific address.
|
/// Set (or overwrite) a reduced deposit price for a specific address.
|
||||||
/// Only callable by the contract admin.
|
/// Only callable by the contract admin.
|
||||||
SetReducedDepositPrice {
|
SetReducedDepositPrice { address: String, deposit: Coin },
|
||||||
address: String,
|
|
||||||
deposit: Coin,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Remove the reduced deposit price for a specific address, reverting them to
|
/// Remove the reduced deposit price for a specific address, reverting them to
|
||||||
/// the default price. Returns an error if the address has no custom price set.
|
/// the default price. Returns an error if the address has no custom price set.
|
||||||
/// Only callable by the contract admin.
|
/// Only callable by the contract admin.
|
||||||
RemoveReducedDepositPrice {
|
RemoveReducedDepositPrice { address: String },
|
||||||
address: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: properly implement
|
/// **Stubbed**: always returns `EcashContractError::UnimplementedBlacklisting`.
|
||||||
ProposeToBlacklist {
|
/// Storage, reply handler, and helper paths exist but are unreachable from
|
||||||
public_key: String,
|
/// the public ExecuteMsg surface. Preserved for the redesign.
|
||||||
},
|
ProposeToBlacklist { public_key: String },
|
||||||
AddToBlacklist {
|
|
||||||
public_key: String,
|
/// **Stubbed**: always returns `EcashContractError::UnimplementedBlacklisting`.
|
||||||
},
|
AddToBlacklist { public_key: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[cfg_attr(feature = "schema", derive(QueryResponses))]
|
#[cfg_attr(feature = "schema", derive(QueryResponses))]
|
||||||
pub enum QueryMsg {
|
pub enum QueryMsg {
|
||||||
|
/// Look up a blacklist entry by its bs58-encoded ed25519 public key. Always
|
||||||
|
/// returns `None` on a freshly deployed contract because the blacklist
|
||||||
|
/// execute paths are stubbed.
|
||||||
#[cfg_attr(feature = "schema", returns(BlacklistedAccountResponse))]
|
#[cfg_attr(feature = "schema", returns(BlacklistedAccountResponse))]
|
||||||
GetBlacklistedAccount { public_key: String },
|
GetBlacklistedAccount { public_key: String },
|
||||||
|
|
||||||
|
/// Paginated listing of blacklist entries. Always empty today (see stubbed
|
||||||
|
/// blacklist surface). Defaults: limit 50, max 75.
|
||||||
#[cfg_attr(feature = "schema", returns(PagedBlacklistedAccountResponse))]
|
#[cfg_attr(feature = "schema", returns(PagedBlacklistedAccountResponse))]
|
||||||
GetBlacklistPaged {
|
GetBlacklistPaged {
|
||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
start_after: Option<String>,
|
start_after: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Default per-deposit price (`Config::deposit_amount`). The
|
||||||
|
/// `GetRequiredDepositAmount` aliases are kept for backwards compatibility.
|
||||||
#[cfg_attr(feature = "schema", returns(Coin))]
|
#[cfg_attr(feature = "schema", returns(Coin))]
|
||||||
#[serde(alias = "get_required_deposit_amount")]
|
#[serde(alias = "get_required_deposit_amount")]
|
||||||
#[serde(alias = "GetRequiredDepositAmount")]
|
#[serde(alias = "GetRequiredDepositAmount")]
|
||||||
GetDefaultDepositAmount {},
|
GetDefaultDepositAmount {},
|
||||||
|
|
||||||
|
/// Per-address reduced deposit price override, if any. `None` for any
|
||||||
|
/// non-whitelisted address.
|
||||||
#[cfg_attr(feature = "schema", returns(Option<Coin>))]
|
#[cfg_attr(feature = "schema", returns(Option<Coin>))]
|
||||||
GetReducedDepositAmount { address: String },
|
GetReducedDepositAmount { address: String },
|
||||||
|
|
||||||
|
/// Enumerate every reduced-deposit whitelist entry in ascending address
|
||||||
|
/// order. Unpaginated by design (the whitelist is expected to stay small).
|
||||||
#[cfg_attr(feature = "schema", returns(WhitelistedAccountsResponse))]
|
#[cfg_attr(feature = "schema", returns(WhitelistedAccountsResponse))]
|
||||||
GetAllWhitelistedAccounts {},
|
GetAllWhitelistedAccounts {},
|
||||||
|
|
||||||
|
/// Look up a deposit by id. Returns `{ id, deposit: None }` when the id has
|
||||||
|
/// not yet been assigned.
|
||||||
#[cfg_attr(feature = "schema", returns(DepositResponse))]
|
#[cfg_attr(feature = "schema", returns(DepositResponse))]
|
||||||
GetDeposit { deposit_id: u32 },
|
GetDeposit { deposit_id: u32 },
|
||||||
|
|
||||||
|
/// Most recently assigned deposit (or `{ deposit: None }` on a fresh
|
||||||
|
/// contract). See `DepositStorage::latest_deposit`.
|
||||||
#[cfg_attr(feature = "schema", returns(LatestDepositResponse))]
|
#[cfg_attr(feature = "schema", returns(LatestDepositResponse))]
|
||||||
GetLatestDeposit {},
|
GetLatestDeposit {},
|
||||||
|
|
||||||
|
/// Paginated listing of deposits in ascending id order. Defaults: limit 50,
|
||||||
|
/// max 100.
|
||||||
#[cfg_attr(feature = "schema", returns(PagedDepositsResponse))]
|
#[cfg_attr(feature = "schema", returns(PagedDepositsResponse))]
|
||||||
GetDepositsPaged {
|
GetDepositsPaged {
|
||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
start_after: Option<u32>,
|
start_after: Option<u32>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Aggregate statistics: global totals + per-account custom-price
|
||||||
|
/// breakdowns. Reassembled in a single read pass from `PoolCounters` and
|
||||||
|
/// `DepositStatsStorage`.
|
||||||
#[cfg_attr(feature = "schema", returns(DepositsStatistics))]
|
#[cfg_attr(feature = "schema", returns(DepositsStatistics))]
|
||||||
GetDepositsStatistics {},
|
GetDepositsStatistics {},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/// Title used for the cw3 `Propose` message dispatched by `RequestRedemption`.
|
||||||
|
/// nym-api signers cross-check this exact string when validating that an
|
||||||
|
/// in-flight proposal originated from the ecash contract.
|
||||||
// TODO: to be moved to multisig
|
// TODO: to be moved to multisig
|
||||||
pub const BATCH_REDEMPTION_PROPOSAL_TITLE: &str = "ecash-redemption";
|
pub const BATCH_REDEMPTION_PROPOSAL_TITLE: &str = "ecash-redemption";
|
||||||
|
|||||||
@@ -4,12 +4,16 @@
|
|||||||
use cosmwasm_schema::cw_serde;
|
use cosmwasm_schema::cw_serde;
|
||||||
use cosmwasm_std::{Addr, Coin};
|
use cosmwasm_std::{Addr, Coin};
|
||||||
|
|
||||||
|
/// Whitelist entry: an address and the reduced deposit price it may pay.
|
||||||
|
/// Persisted in the `"reduced_deposits"` storage map.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct WhitelistedAccount {
|
pub struct WhitelistedAccount {
|
||||||
pub address: Addr,
|
pub address: Addr,
|
||||||
pub deposit: Coin,
|
pub deposit: Coin,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response shape for `GetAllWhitelistedAccounts`. Unpaginated - the whitelist
|
||||||
|
/// is expected to stay small.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct WhitelistedAccountsResponse {
|
pub struct WhitelistedAccountsResponse {
|
||||||
pub whitelisted_accounts: Vec<WhitelistedAccount>,
|
pub whitelisted_accounts: Vec<WhitelistedAccount>,
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "nym-network-monitors-contract-common"
|
||||||
|
description = "Common library for the Nym Network Monitors contract"
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
documentation.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
schemars = { workspace = true }
|
||||||
|
|
||||||
|
cosmwasm-std = { workspace = true }
|
||||||
|
cosmwasm-schema = { workspace = true }
|
||||||
|
cw-controllers = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
schema = []
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
pub mod storage_keys {
|
||||||
|
pub const CONTRACT_ADMIN: &str = "contract-admin";
|
||||||
|
pub const AUTHORISED_ORCHESTRATORS: &str = "authorised-orchestrators";
|
||||||
|
pub const AUTHORISED_NETWORK_MONITORS: &str = "authorised-network-monitors";
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use cosmwasm_std::Addr;
|
||||||
|
use cw_controllers::AdminError;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq)]
|
||||||
|
pub enum NetworkMonitorsContractError {
|
||||||
|
#[error("could not perform contract migration: {comment}")]
|
||||||
|
FailedMigration { comment: String },
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Admin(#[from] AdminError),
|
||||||
|
|
||||||
|
#[error("unauthorised")]
|
||||||
|
Unauthorized,
|
||||||
|
|
||||||
|
#[error("address {addr} is not an authorised orchestrator")]
|
||||||
|
NotAnOrchestrator { addr: Addr },
|
||||||
|
|
||||||
|
#[error("Failed to recover x25519 public key from its base58 representation: {0}")]
|
||||||
|
MalformedX25519AgentNoiseKey(String),
|
||||||
|
|
||||||
|
#[error("Failed to recover ed25519 public key from its base58 representation: {0}")]
|
||||||
|
MalformedEd25519OrchestratorIdentityKey(String),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
StdErr(#[from] cosmwasm_std::StdError),
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
pub mod constants;
|
||||||
|
pub mod error;
|
||||||
|
pub mod msg;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub use error::*;
|
||||||
|
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||||
|
pub use types::*;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use cosmwasm_schema::cw_serde;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
#[cfg(feature = "schema")]
|
||||||
|
use crate::{
|
||||||
|
AuthorisedNetworkMonitorOrchestratorsResponse, AuthorisedNetworkMonitorsPagedResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct InstantiateMsg {
|
||||||
|
/// Address of the initial network monitor orchestrator.
|
||||||
|
pub orchestrator_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub enum ExecuteMsg {
|
||||||
|
/// Change the admin
|
||||||
|
UpdateAdmin { admin: String },
|
||||||
|
|
||||||
|
/// Authorise new network monitor orchestrator
|
||||||
|
AuthoriseNetworkMonitorOrchestrator { address: String },
|
||||||
|
|
||||||
|
/// Attempt to update the announced identity key of this orchestrator
|
||||||
|
UpdateOrchestratorIdentityKey { key: String },
|
||||||
|
|
||||||
|
/// Revoke network monitor orchestrator authorisation.
|
||||||
|
RevokeNetworkMonitorOrchestrator { address: String },
|
||||||
|
|
||||||
|
/// Authorise new network monitor (or renew authorisation)
|
||||||
|
/// granting additional privileges when sending mixnet packets to Nym nodes.
|
||||||
|
AuthoriseNetworkMonitor {
|
||||||
|
/// Mixnet address of the agent.
|
||||||
|
/// The underlying ip address is going to be used as ingress to the nodes,
|
||||||
|
/// and the full socket address announces the egress and the association with the noise key
|
||||||
|
mixnet_address: SocketAddr,
|
||||||
|
|
||||||
|
/// Base-58 encoded noise key of the agent.
|
||||||
|
bs58_x25519_noise: String,
|
||||||
|
|
||||||
|
/// Version of the noise protocol used by the agent.
|
||||||
|
noise_version: u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Revoke network monitor authorisation.
|
||||||
|
RevokeNetworkMonitor { address: SocketAddr },
|
||||||
|
|
||||||
|
/// Revoke all network monitor authorisations.
|
||||||
|
RevokeAllNetworkMonitors,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
#[cfg_attr(feature = "schema", derive(cosmwasm_schema::QueryResponses))]
|
||||||
|
pub enum QueryMsg {
|
||||||
|
#[cfg_attr(feature = "schema", returns(cw_controllers::AdminResponse))]
|
||||||
|
Admin {},
|
||||||
|
|
||||||
|
// no need for pagination as we don't expect even a double digit of those
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schema",
|
||||||
|
returns(AuthorisedNetworkMonitorOrchestratorsResponse)
|
||||||
|
)]
|
||||||
|
NetworkMonitorOrchestrators {},
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "schema", returns(AuthorisedNetworkMonitorsPagedResponse))]
|
||||||
|
NetworkMonitorAgents {
|
||||||
|
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
|
||||||
|
start_next_after: Option<SocketAddr>,
|
||||||
|
|
||||||
|
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
|
||||||
|
limit: Option<u32>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct MigrateMsg {}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use cosmwasm_schema::cw_serde;
|
||||||
|
use cosmwasm_std::{Addr, Timestamp};
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
pub type OrchestratorAddress = Addr;
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct AuthorisedNetworkMonitorOrchestrator {
|
||||||
|
/// The address associated with the network monitor orchestrator.
|
||||||
|
pub address: Addr,
|
||||||
|
|
||||||
|
/// Base-58 encoded identity key of the orchestrator, announced by the orchestrator itself
|
||||||
|
/// on startup.
|
||||||
|
pub identity_key: Option<String>,
|
||||||
|
|
||||||
|
/// Timestamp of when the network monitor was authorised.
|
||||||
|
pub authorised_at: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct AuthorisedNetworkMonitor {
|
||||||
|
/// Mixnet address of the agent.
|
||||||
|
/// The underlying ip address is going to be used as ingress to the nodes,
|
||||||
|
/// and the full socket address announces the egress and the association with the noise key
|
||||||
|
pub mixnet_address: SocketAddr,
|
||||||
|
|
||||||
|
/// The address of the orchestrator that authorised the network monitor agent.
|
||||||
|
pub authorised_by: OrchestratorAddress,
|
||||||
|
|
||||||
|
/// Timestamp of when the network monitor was authorised.
|
||||||
|
pub authorised_at: Timestamp,
|
||||||
|
|
||||||
|
/// Base-58 encoded noise key of the agent.
|
||||||
|
pub bs58_x25519_noise: String,
|
||||||
|
|
||||||
|
/// Version of the noise protocol used by the agent.
|
||||||
|
pub noise_version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct AuthorisedNetworkMonitorOrchestratorsResponse {
|
||||||
|
pub authorised: Vec<AuthorisedNetworkMonitorOrchestrator>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct AuthorisedNetworkMonitorsPagedResponse {
|
||||||
|
pub authorised: Vec<AuthorisedNetworkMonitor>,
|
||||||
|
|
||||||
|
pub start_next_after: Option<SocketAddr>,
|
||||||
|
}
|
||||||
@@ -101,4 +101,14 @@ pub mod events {
|
|||||||
|
|
||||||
pub const NODE_UNBOND_CLEANUP_EVENT_NAME: &str = "family_node_unbond_cleanup";
|
pub const NODE_UNBOND_CLEANUP_EVENT_NAME: &str = "family_node_unbond_cleanup";
|
||||||
pub const NODE_UNBOND_CLEANUP_EVENT_NODE_ID: &str = "node_id";
|
pub const NODE_UNBOND_CLEANUP_EVENT_NODE_ID: &str = "node_id";
|
||||||
|
|
||||||
|
pub const FAMILY_UPDATE_EVENT_NAME: &str = "family_update";
|
||||||
|
pub const FAMILY_UPDATE_EVENT_FAMILY_ID: &str = "family_id";
|
||||||
|
pub const FAMILY_UPDATE_EVENT_OWNER_ADDRESS: &str = "owner_address";
|
||||||
|
/// Attribute carrying the new family name. Only emitted when the
|
||||||
|
/// `UpdateFamily` message carried `updated_name = Some(_)`.
|
||||||
|
pub const FAMILY_UPDATE_EVENT_UPDATED_NAME: &str = "updated_name";
|
||||||
|
/// Attribute carrying the new family description. Only emitted when the
|
||||||
|
/// `UpdateFamily` message carried `updated_description = Some(_)`.
|
||||||
|
pub const FAMILY_UPDATE_EVENT_UPDATED_DESCRIPTION: &str = "updated_description";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ pub enum ExecuteMsg {
|
|||||||
/// `create_family_fee` must be attached as funds.
|
/// `create_family_fee` must be attached as funds.
|
||||||
CreateFamily { name: String, description: String },
|
CreateFamily { name: String, description: String },
|
||||||
|
|
||||||
|
/// Update the name and/or description of the family owned by the message
|
||||||
|
/// sender. Each field is independently optional: `None` leaves the
|
||||||
|
/// existing value unchanged, `Some(_)` replaces it. Updated values are
|
||||||
|
/// validated against the same length / normalisation / global-uniqueness
|
||||||
|
/// rules as [`Self::CreateFamily`].
|
||||||
|
UpdateFamily {
|
||||||
|
updated_name: Option<String>,
|
||||||
|
updated_description: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Disband the family owned by the message sender. The family must have
|
/// Disband the family owned by the message sender. The family must have
|
||||||
/// no current members; any still-pending invitations are revoked.
|
/// no current members; any still-pending invitations are revoked.
|
||||||
DisbandFamily {},
|
DisbandFamily {},
|
||||||
|
|||||||
@@ -61,9 +61,12 @@ pub struct NodeFamily {
|
|||||||
|
|
||||||
/// A pending invitation for a node to join a particular family.
|
/// A pending invitation for a node to join a particular family.
|
||||||
///
|
///
|
||||||
/// Invitations are stored until they are accepted, rejected, revoked, or until the
|
/// Invitations are stored until they are accepted, rejected, or revoked. Once the
|
||||||
/// chain advances past `expires_at` (in which case they remain in storage but are
|
/// chain advances past `expires_at` an invitation becomes inert but stays in storage
|
||||||
/// treated as inert — there is no background process clearing expired invitations).
|
/// — there is no background process clearing expired invitations. A timed-out
|
||||||
|
/// invitation is cleared either when explicitly revoked/rejected, or when the family
|
||||||
|
/// issues a fresh invitation for the same node, which archives the stale one as
|
||||||
|
/// `Expired` and supersedes it.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct FamilyInvitation {
|
pub struct FamilyInvitation {
|
||||||
/// The family that issued the invitation.
|
/// The family that issued the invitation.
|
||||||
@@ -107,8 +110,10 @@ pub struct PastFamilyMember {
|
|||||||
|
|
||||||
/// Terminal status for an invitation that has been moved out of the pending set.
|
/// Terminal status for an invitation that has been moved out of the pending set.
|
||||||
///
|
///
|
||||||
/// Note: timed-out invitations are not represented here — they are simply left in
|
/// Note: an invitation that merely times out is **not** archived here on its own —
|
||||||
/// the pending set (see `FamilyInvitation::expires_at`).
|
/// it is left inert in the pending set (see `FamilyInvitation::expires_at`). It only
|
||||||
|
/// reaches `Expired` if the family issues a fresh invitation for the same node, which
|
||||||
|
/// supersedes and archives the stale one.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub enum FamilyInvitationStatus {
|
pub enum FamilyInvitationStatus {
|
||||||
/// Still awaiting a response. Recorded with a timestamp for completeness even
|
/// Still awaiting a response. Recorded with a timestamp for completeness even
|
||||||
@@ -121,11 +126,16 @@ pub enum FamilyInvitationStatus {
|
|||||||
/// The family revoked the invitation at the given timestamp before it could
|
/// The family revoked the invitation at the given timestamp before it could
|
||||||
/// be accepted or rejected.
|
/// be accepted or rejected.
|
||||||
Revoked { at: u64 },
|
Revoked { at: u64 },
|
||||||
|
/// The invitation had already expired and was superseded by a fresh invitation
|
||||||
|
/// for the same node from the same family, issued at the given timestamp. This is
|
||||||
|
/// the only path that archives a timed-out invitation.
|
||||||
|
Expired { at: u64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Historical record of an invitation that has reached a terminal state
|
/// Historical record of an invitation that has reached a terminal state
|
||||||
/// (`Accepted`, `Rejected`, or `Revoked`). Timed-out invitations are **not**
|
/// (`Accepted`, `Rejected`, `Revoked`, or `Expired`). A timed-out invitation is
|
||||||
/// archived here — they remain in the pending map until explicitly cleared.
|
/// archived here only when a fresh invitation for the same node supersedes it
|
||||||
|
/// (status `Expired`); otherwise it stays in the pending map until explicitly cleared.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct PastFamilyInvitation {
|
pub struct PastFamilyInvitation {
|
||||||
/// The original invitation as it was issued.
|
/// The original invitation as it was issued.
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ pub struct QuorumStateChecker {
|
|||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
check_interval: Duration,
|
check_interval: Duration,
|
||||||
quorum_state: QuorumState,
|
quorum_state: QuorumState,
|
||||||
|
|
||||||
|
/// indicates whether the last check has been a failure
|
||||||
|
last_failed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QuorumStateChecker {
|
impl QuorumStateChecker {
|
||||||
@@ -42,6 +45,7 @@ impl QuorumStateChecker {
|
|||||||
quorum_state: QuorumState {
|
quorum_state: QuorumState {
|
||||||
available: Arc::new(Default::default()),
|
available: Arc::new(Default::default()),
|
||||||
},
|
},
|
||||||
|
last_failed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// first check MUST succeed, otherwise we shouldn't start
|
// first check MUST succeed, otherwise we shouldn't start
|
||||||
@@ -57,6 +61,7 @@ impl QuorumStateChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn check_quorum_state(&self) -> Result<bool, CredentialProxyError> {
|
async fn check_quorum_state(&self) -> Result<bool, CredentialProxyError> {
|
||||||
|
info!("checking the current quorum state");
|
||||||
let client_guard = self.client.query_chain().await;
|
let client_guard = self.client.query_chain().await;
|
||||||
|
|
||||||
// split the operation as we only need to hold the reference to chain client for the first part
|
// split the operation as we only need to hold the reference to chain client for the first part
|
||||||
@@ -64,7 +69,8 @@ impl QuorumStateChecker {
|
|||||||
let dkg_details = dkg_details_with_client(client_guard.deref()).await?;
|
let dkg_details = dkg_details_with_client(client_guard.deref()).await?;
|
||||||
drop(client_guard);
|
drop(client_guard);
|
||||||
|
|
||||||
let res = check_known_dealers(dkg_details).await?;
|
let res = check_known_dealers(dkg_details, 4).await?;
|
||||||
|
info!("there are {} known DKG dealers", res.results.len());
|
||||||
|
|
||||||
let Some(signing_threshold) = res.threshold else {
|
let Some(signing_threshold) = res.threshold else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -76,15 +82,36 @@ impl QuorumStateChecker {
|
|||||||
let mut working_issuer = 0;
|
let mut working_issuer = 0;
|
||||||
|
|
||||||
for result in res.results {
|
for result in res.results {
|
||||||
|
let dealer = &result.information;
|
||||||
|
let info = format!("[id: {}] @ {}", dealer.node_index, dealer.announce_address);
|
||||||
if result.chain_available() && result.signing_available() {
|
if result.chain_available() && result.signing_available() {
|
||||||
|
info!("✅ {info} is fully available");
|
||||||
working_issuer += 1;
|
working_issuer += 1;
|
||||||
|
} else if !result.chain_available() && !result.signing_available() {
|
||||||
|
warn!("❌ {info} is not available for both chain and signing");
|
||||||
|
} else if !result.chain_available() {
|
||||||
|
warn!("❌ {info} is not available for chain");
|
||||||
|
} else {
|
||||||
|
warn!("❌ {info} is not available for signing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((working_issuer as u64) >= signing_threshold)
|
let available = (working_issuer as u64) >= signing_threshold;
|
||||||
|
|
||||||
|
if available {
|
||||||
|
info!(
|
||||||
|
"✅ Quorum state is available with {working_issuer} out of {signing_threshold} issuers"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
"❌ Quorum state is not available with {working_issuer} out of {signing_threshold} issuers"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(available)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_forever(self) {
|
pub async fn run_forever(mut self) {
|
||||||
info!("starting quorum state checker");
|
info!("starting quorum state checker");
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -94,7 +121,23 @@ impl QuorumStateChecker {
|
|||||||
}
|
}
|
||||||
_ = tokio::time::sleep(self.check_interval) => {
|
_ = tokio::time::sleep(self.check_interval) => {
|
||||||
match self.check_quorum_state().await {
|
match self.check_quorum_state().await {
|
||||||
Ok(available) => self.quorum_state.available.store(available, Ordering::SeqCst),
|
Ok(available) => {
|
||||||
|
let previous = self.quorum_state.available.load(Ordering::SeqCst);
|
||||||
|
// only update the quorum state to a failed state if we've had two consecutive failures
|
||||||
|
if available {
|
||||||
|
if !previous {
|
||||||
|
info!("quorum recovered");
|
||||||
|
}
|
||||||
|
self.quorum_state.available.store(true, Ordering::SeqCst);
|
||||||
|
} else if self.last_failed {
|
||||||
|
if previous {
|
||||||
|
warn!("quorum became unavailable after 2 consecutive failed checks");
|
||||||
|
}
|
||||||
|
self.quorum_state.available.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.last_failed = !available;
|
||||||
|
},
|
||||||
Err(err) => error!("failed to check current quorum state: {err}"),
|
Err(err) => error!("failed to check current quorum state: {err}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,8 +230,8 @@ impl MemoryEcachTicketbookManager {
|
|||||||
expiration_date: t.ticketbook.expiration_date(),
|
expiration_date: t.ticketbook.expiration_date(),
|
||||||
ticketbook_type: t.ticketbook.ticketbook_type().to_string(),
|
ticketbook_type: t.ticketbook.ticketbook_type().to_string(),
|
||||||
epoch_id: t.ticketbook.epoch_id() as u32,
|
epoch_id: t.ticketbook.epoch_id() as u32,
|
||||||
total_tickets: t.ticketbook.spent_tickets() as u32,
|
total_tickets: t.total_tickets,
|
||||||
used_tickets: t.total_tickets,
|
used_tickets: t.ticketbook.spent_tickets() as u32,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -333,3 +333,339 @@ impl MemoryEcachTicketbookManager {
|
|||||||
guard.emergency_credentials.remove(typ);
|
guard.emergency_credentials.remove(typ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use nym_compact_ecash::tests::helpers::generate_expiration_date_signatures;
|
||||||
|
use nym_compact_ecash::{issue, ttp_keygen};
|
||||||
|
use nym_credentials_interface::TicketType;
|
||||||
|
use nym_crypto::asymmetric::ed25519;
|
||||||
|
use nym_ecash_time::EcashTime;
|
||||||
|
use nym_test_utils::helpers::deterministic_rng;
|
||||||
|
|
||||||
|
fn mock_issuance(deposit_id: u32) -> IssuanceTicketBook {
|
||||||
|
let identifier = "foomp";
|
||||||
|
let mut rng = deterministic_rng();
|
||||||
|
let key = ed25519::PrivateKey::new(&mut rng);
|
||||||
|
let typ = TicketType::V1MixnetEntry;
|
||||||
|
IssuanceTicketBook::new(deposit_id, identifier, key, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mock_ticketbook() -> anyhow::Result<IssuedTicketBook> {
|
||||||
|
let signing_keys = ttp_keygen(1, 1)?.remove(0);
|
||||||
|
let issuance = mock_issuance(42);
|
||||||
|
let expiration_date = issuance.expiration_date();
|
||||||
|
|
||||||
|
let sig_req = issuance.prepare_for_signing();
|
||||||
|
let _exp_date_sigs = generate_expiration_date_signatures(
|
||||||
|
sig_req.expiration_date.ecash_unix_timestamp(),
|
||||||
|
&[signing_keys.secret_key()],
|
||||||
|
&[signing_keys.verification_key()],
|
||||||
|
&signing_keys.verification_key(),
|
||||||
|
&[1],
|
||||||
|
)?;
|
||||||
|
let blind_sig = issue(
|
||||||
|
signing_keys.secret_key(),
|
||||||
|
sig_req.ecash_pub_key,
|
||||||
|
&sig_req.withdrawal_request,
|
||||||
|
expiration_date.ecash_unix_timestamp(),
|
||||||
|
issuance.ticketbook_type().encode(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let partial_wallet =
|
||||||
|
issuance.unblind_signature(&signing_keys.verification_key(), &sig_req, blind_sig, 1)?;
|
||||||
|
|
||||||
|
let wallet = issuance.aggregate_signature_shares(
|
||||||
|
&signing_keys.verification_key(),
|
||||||
|
&[partial_wallet],
|
||||||
|
sig_req,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(issuance.into_issued_ticketbook(wallet, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mock_verification_key() -> VerificationKeyAuth {
|
||||||
|
ttp_keygen(1, 1).unwrap().remove(0).verification_key()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_ticketbooks_info_empty() {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let info = manager.get_ticketbooks_info().await;
|
||||||
|
assert!(info.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_ticketbooks_info_maps_inserted_ticketbook() -> anyhow::Result<()> {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let ticketbook = mock_ticketbook()?;
|
||||||
|
let total_tickets = 100;
|
||||||
|
let used_tickets = 25;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.insert_new_ticketbook(&ticketbook, total_tickets, used_tickets)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let info = manager.get_ticketbooks_info().await;
|
||||||
|
assert_eq!(info.len(), 1);
|
||||||
|
let entry = &info[0];
|
||||||
|
assert_eq!(entry.id, 0);
|
||||||
|
assert_eq!(entry.expiration_date, ticketbook.expiration_date());
|
||||||
|
assert_eq!(
|
||||||
|
entry.ticketbook_type,
|
||||||
|
ticketbook.ticketbook_type().to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(entry.epoch_id, ticketbook.epoch_id() as u32);
|
||||||
|
assert_eq!(entry.total_tickets, total_tickets);
|
||||||
|
assert_eq!(entry.used_tickets, used_tickets);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn contains_ticketbook_reflects_insertion() -> anyhow::Result<()> {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let ticketbook = mock_ticketbook()?;
|
||||||
|
|
||||||
|
assert!(!manager.contains_ticketbook(&ticketbook).await);
|
||||||
|
|
||||||
|
manager.insert_new_ticketbook(&ticketbook, 100, 0).await;
|
||||||
|
|
||||||
|
assert!(manager.contains_ticketbook(&ticketbook).await);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn insert_new_ticketbook_assigns_incrementing_ids() -> anyhow::Result<()> {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let ticketbook = mock_ticketbook()?;
|
||||||
|
|
||||||
|
manager.insert_new_ticketbook(&ticketbook, 100, 0).await;
|
||||||
|
manager.insert_new_ticketbook(&ticketbook, 100, 0).await;
|
||||||
|
|
||||||
|
let mut ids: Vec<i64> = manager
|
||||||
|
.get_ticketbooks_info()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| i.id)
|
||||||
|
.collect();
|
||||||
|
ids.sort();
|
||||||
|
assert_eq!(ids, vec![0, 1]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_next_unspent_ticketbook_updates_spent_and_exhausts() -> anyhow::Result<()> {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let ticketbook = mock_ticketbook()?;
|
||||||
|
let typ = ticketbook.ticketbook_type().to_string();
|
||||||
|
|
||||||
|
// total = 3, used = 0 — leaves 3 tickets available
|
||||||
|
manager.insert_new_ticketbook(&ticketbook, 3, 0).await;
|
||||||
|
|
||||||
|
let first = manager
|
||||||
|
.get_next_unspent_ticketbook_and_update(typ.clone(), 2)
|
||||||
|
.await;
|
||||||
|
assert!(first.is_some());
|
||||||
|
let first = first.unwrap();
|
||||||
|
assert_eq!(first.total_tickets, 3);
|
||||||
|
// returned ticketbook reflects state *before* the update
|
||||||
|
assert_eq!(first.ticketbook.spent_tickets(), 0);
|
||||||
|
|
||||||
|
// next withdrawal of 2 should be rejected (only 1 left)
|
||||||
|
let second = manager
|
||||||
|
.get_next_unspent_ticketbook_and_update(typ.clone(), 2)
|
||||||
|
.await;
|
||||||
|
assert!(second.is_none());
|
||||||
|
|
||||||
|
// but a withdrawal of 1 succeeds
|
||||||
|
let third = manager
|
||||||
|
.get_next_unspent_ticketbook_and_update(typ.clone(), 1)
|
||||||
|
.await;
|
||||||
|
assert!(third.is_some());
|
||||||
|
|
||||||
|
// and now nothing left
|
||||||
|
let fourth = manager.get_next_unspent_ticketbook_and_update(typ, 1).await;
|
||||||
|
assert!(fourth.is_none());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_next_unspent_ticketbook_filters_by_type() -> anyhow::Result<()> {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let ticketbook = mock_ticketbook()?;
|
||||||
|
|
||||||
|
manager.insert_new_ticketbook(&ticketbook, 5, 0).await;
|
||||||
|
|
||||||
|
let mismatched = manager
|
||||||
|
.get_next_unspent_ticketbook_and_update("nonexistent_type".to_string(), 1)
|
||||||
|
.await;
|
||||||
|
assert!(mismatched.is_none());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn revert_ticketbook_withdrawal_resets_spent_only_when_expected_matches(
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let ticketbook = mock_ticketbook()?;
|
||||||
|
let typ = ticketbook.ticketbook_type().to_string();
|
||||||
|
|
||||||
|
manager.insert_new_ticketbook(&ticketbook, 10, 0).await;
|
||||||
|
manager
|
||||||
|
.get_next_unspent_ticketbook_and_update(typ.clone(), 4)
|
||||||
|
.await
|
||||||
|
.expect("should withdraw");
|
||||||
|
|
||||||
|
// stale expected_current_total_spent — should be rejected
|
||||||
|
assert!(!manager.revert_ticketbook_withdrawal(0, 4, 99).await);
|
||||||
|
// spent_tickets unchanged
|
||||||
|
let used_after_failed = manager.get_ticketbooks_info().await[0].used_tickets;
|
||||||
|
assert_eq!(used_after_failed, 4);
|
||||||
|
|
||||||
|
// matching expected — should succeed and restore
|
||||||
|
assert!(manager.revert_ticketbook_withdrawal(0, 4, 4).await);
|
||||||
|
let used_after_revert = manager.get_ticketbooks_info().await[0].used_tickets;
|
||||||
|
assert_eq!(used_after_revert, 0);
|
||||||
|
|
||||||
|
// unknown ticketbook_id is rejected
|
||||||
|
assert!(!manager.revert_ticketbook_withdrawal(999, 1, 0).await);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pending_ticketbook_round_trip() {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let issuance = mock_issuance(7);
|
||||||
|
let deposit_id = issuance.deposit_id() as i64;
|
||||||
|
|
||||||
|
assert!(manager.get_pending_ticketbooks().await.is_empty());
|
||||||
|
|
||||||
|
manager.insert_pending_ticketbook(&issuance).await;
|
||||||
|
|
||||||
|
let pending = manager.get_pending_ticketbooks().await;
|
||||||
|
assert_eq!(pending.len(), 1);
|
||||||
|
assert_eq!(pending[0].pending_id, deposit_id);
|
||||||
|
assert_eq!(
|
||||||
|
pending[0].pending_ticketbook.deposit_id(),
|
||||||
|
issuance.deposit_id()
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.remove_pending_ticketbook(deposit_id).await;
|
||||||
|
assert!(manager.get_pending_ticketbooks().await.is_empty());
|
||||||
|
|
||||||
|
// removing a non-existent id is a no-op
|
||||||
|
manager.remove_pending_ticketbook(999).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn emergency_credential_lifecycle() {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
|
||||||
|
let cred_a = EmergencyCredentialContent {
|
||||||
|
typ: "type-a".to_string(),
|
||||||
|
content: vec![1, 2, 3],
|
||||||
|
expiration: None,
|
||||||
|
};
|
||||||
|
let cred_b = EmergencyCredentialContent {
|
||||||
|
typ: "type-a".to_string(),
|
||||||
|
content: vec![4, 5, 6],
|
||||||
|
expiration: None,
|
||||||
|
};
|
||||||
|
let cred_c = EmergencyCredentialContent {
|
||||||
|
typ: "type-b".to_string(),
|
||||||
|
content: vec![7, 8, 9],
|
||||||
|
expiration: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(manager.get_emergency_credential("type-a").await.is_none());
|
||||||
|
|
||||||
|
manager.insert_emergency_credential(&cred_a).await;
|
||||||
|
manager.insert_emergency_credential(&cred_b).await;
|
||||||
|
manager.insert_emergency_credential(&cred_c).await;
|
||||||
|
|
||||||
|
// get returns the first inserted entry for the type
|
||||||
|
let first = manager.get_emergency_credential("type-a").await.unwrap();
|
||||||
|
assert_eq!(first.id, 0);
|
||||||
|
assert_eq!(first.data.content, vec![1, 2, 3]);
|
||||||
|
|
||||||
|
// remove by id drops only that entry; type-a now exposes cred_b
|
||||||
|
manager.remove_emergency_credential(0).await;
|
||||||
|
let after_remove = manager.get_emergency_credential("type-a").await.unwrap();
|
||||||
|
assert_eq!(after_remove.id, 1);
|
||||||
|
assert_eq!(after_remove.data.content, vec![4, 5, 6]);
|
||||||
|
|
||||||
|
// remove by type clears the bucket entirely
|
||||||
|
manager.remove_emergency_credentials_of_type("type-a").await;
|
||||||
|
assert!(manager.get_emergency_credential("type-a").await.is_none());
|
||||||
|
|
||||||
|
// unrelated type is untouched
|
||||||
|
assert!(manager.get_emergency_credential("type-b").await.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn master_verification_key_round_trip() {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let key = mock_verification_key();
|
||||||
|
let epoch = EpochVerificationKey {
|
||||||
|
epoch_id: 7,
|
||||||
|
key: key.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(manager.get_master_verification_key(7).await.is_none());
|
||||||
|
|
||||||
|
manager.insert_master_verification_key(&epoch).await;
|
||||||
|
|
||||||
|
assert_eq!(manager.get_master_verification_key(7).await, Some(key));
|
||||||
|
assert!(manager.get_master_verification_key(8).await.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn coin_index_signatures_round_trip() {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let sigs = AggregatedCoinIndicesSignatures {
|
||||||
|
epoch_id: 3,
|
||||||
|
signatures: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(manager.get_coin_index_signatures(3).await.is_none());
|
||||||
|
|
||||||
|
manager.insert_coin_index_signatures(&sigs).await;
|
||||||
|
|
||||||
|
let retrieved = manager.get_coin_index_signatures(3).await;
|
||||||
|
assert!(retrieved.is_some());
|
||||||
|
assert!(retrieved.unwrap().is_empty());
|
||||||
|
assert!(manager.get_coin_index_signatures(4).await.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn expiration_date_signatures_round_trip() {
|
||||||
|
let manager = MemoryEcachTicketbookManager::new();
|
||||||
|
let date = nym_ecash_time::ecash_today().date();
|
||||||
|
let sigs = AggregatedExpirationDateSignatures {
|
||||||
|
epoch_id: 5,
|
||||||
|
expiration_date: date,
|
||||||
|
signatures: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(manager
|
||||||
|
.get_expiration_date_signatures(date, 5)
|
||||||
|
.await
|
||||||
|
.is_none());
|
||||||
|
|
||||||
|
manager.insert_expiration_date_signatures(&sigs).await;
|
||||||
|
|
||||||
|
let retrieved = manager.get_expiration_date_signatures(date, 5).await;
|
||||||
|
assert!(retrieved.is_some());
|
||||||
|
assert!(retrieved.unwrap().is_empty());
|
||||||
|
|
||||||
|
// wrong epoch / wrong date → miss
|
||||||
|
assert!(manager
|
||||||
|
.get_expiration_date_signatures(date, 6)
|
||||||
|
.await
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,5 +36,13 @@ nym-ecash-contract-common = { workspace = true }
|
|||||||
nym-network-defaults = { workspace = true }
|
nym-network-defaults = { workspace = true }
|
||||||
nym-serde-helpers = { workspace = true, features = ["date"] }
|
nym-serde-helpers = { workspace = true, features = ["date"] }
|
||||||
|
|
||||||
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||||
|
workspace = true
|
||||||
|
features = ["time"]
|
||||||
|
|
||||||
|
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||||
|
workspace = true
|
||||||
|
features = ["tokio"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::ecash::bandwidth::serialiser::VersionedSerialise;
|
|||||||
use crate::ecash::bandwidth::CredentialSigningData;
|
use crate::ecash::bandwidth::CredentialSigningData;
|
||||||
use crate::ecash::utils::cred_exp_date;
|
use crate::ecash::utils::cred_exp_date;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use log::{debug, warn};
|
||||||
use nym_api_requests::ecash::BlindSignRequestBody;
|
use nym_api_requests::ecash::BlindSignRequestBody;
|
||||||
use nym_credentials_interface::{
|
use nym_credentials_interface::{
|
||||||
aggregate_wallets, generate_keypair_user_from_seed, issue_verify, withdrawal_request,
|
aggregate_wallets, generate_keypair_user_from_seed, issue_verify, withdrawal_request,
|
||||||
@@ -17,8 +18,15 @@ use nym_ecash_contract_common::deposit::DepositId;
|
|||||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today, EcashTime};
|
use nym_ecash_time::{ecash_default_expiration_date, ecash_today, EcashTime};
|
||||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use wasmtimer::tokio::sleep;
|
||||||
|
|
||||||
pub use nym_validator_client::nyxd::{Coin, Hash};
|
pub use nym_validator_client::nyxd::{Coin, Hash};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -192,6 +200,49 @@ impl IssuanceTicketBook {
|
|||||||
Ok(unblinded_signature)
|
Ok(unblinded_signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
|
||||||
|
pub async fn obtain_partial_ticketbook_credential_with_retries(
|
||||||
|
&self,
|
||||||
|
client: &nym_http_api_client::Client,
|
||||||
|
signer_index: u64,
|
||||||
|
validator_vk: &VerificationKeyAuth,
|
||||||
|
signing_data: CredentialSigningData,
|
||||||
|
max_attempts: usize,
|
||||||
|
) -> Result<PartialWallet, Error> {
|
||||||
|
let Some(client_url) = client.base_urls().first() else {
|
||||||
|
return Err(Error::CredentialShareObtainFailed);
|
||||||
|
};
|
||||||
|
let mut last_err = None;
|
||||||
|
for attempt in 0..max_attempts {
|
||||||
|
if attempt > 0 {
|
||||||
|
sleep(Duration::from_millis(500 * attempt as u64)).await;
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"attempt {} / {max_attempts} to obtain partial ticketbook credential from {client_url}",
|
||||||
|
attempt + 1,
|
||||||
|
);
|
||||||
|
match self
|
||||||
|
.obtain_partial_ticketbook_credential(
|
||||||
|
client,
|
||||||
|
signer_index,
|
||||||
|
validator_vk,
|
||||||
|
signing_data.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(partial_wallet) => return Ok(partial_wallet),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
"attempt {} / {max_attempts} to obtain partial ticketbook credential from {client_url} failed: {err}",
|
||||||
|
attempt + 1,
|
||||||
|
);
|
||||||
|
last_err = Some(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(last_err.unwrap_or(Error::CredentialShareObtainFailed))
|
||||||
|
}
|
||||||
|
|
||||||
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
|
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
|
||||||
pub async fn obtain_partial_ticketbook_credential(
|
pub async fn obtain_partial_ticketbook_credential(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ pub async fn obtain_aggregate_wallet(
|
|||||||
ecash_api_clients: &[EcashApiClient],
|
ecash_api_clients: &[EcashApiClient],
|
||||||
threshold: u64,
|
threshold: u64,
|
||||||
) -> Result<WalletSignatures, Error> {
|
) -> Result<WalletSignatures, Error> {
|
||||||
|
const MAX_ATTEMPTS: usize = 2;
|
||||||
|
|
||||||
if ecash_api_clients.len() < threshold as usize {
|
if ecash_api_clients.len() < threshold as usize {
|
||||||
return Err(Error::NoValidatorsAvailable);
|
return Err(Error::NoValidatorsAvailable);
|
||||||
}
|
}
|
||||||
@@ -154,11 +156,12 @@ pub async fn obtain_aggregate_wallet(
|
|||||||
);
|
);
|
||||||
|
|
||||||
match voucher
|
match voucher
|
||||||
.obtain_partial_ticketbook_credential(
|
.obtain_partial_ticketbook_credential_with_retries(
|
||||||
&ecash_api_client.api_client,
|
&ecash_api_client.api_client,
|
||||||
ecash_api_client.node_id,
|
ecash_api_client.node_id,
|
||||||
&ecash_api_client.verification_key,
|
&ecash_api_client.verification_key,
|
||||||
request.clone(),
|
request.clone(),
|
||||||
|
MAX_ATTEMPTS,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -167,6 +170,11 @@ pub async fn obtain_aggregate_wallet(
|
|||||||
warn!("failed to obtain partial credential from API {ecash_api_client}: {err}",);
|
warn!("failed to obtain partial credential from API {ecash_api_client}: {err}",);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// we got sufficient number of shares
|
||||||
|
if wallets.len() >= threshold as usize {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if wallets.len() < threshold as usize {
|
if wallets.len() < threshold as usize {
|
||||||
return Err(Error::NotEnoughShares);
|
return Err(Error::NotEnoughShares);
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("failed to create a secp256k1 signature")]
|
#[error("failed to create a secp256k1 signature")]
|
||||||
Secp256k1SignFailure,
|
Secp256k1SignFailure,
|
||||||
|
|
||||||
|
#[error("failed to obtain a valid credential share")]
|
||||||
|
CredentialShareObtainFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NymAPIError> for Error {
|
impl From<NymAPIError> for Error {
|
||||||
|
|||||||
@@ -31,3 +31,5 @@ pub use aes_gcm_siv::{Aes128GcmSiv, Aes256GcmSiv};
|
|||||||
pub use blake3;
|
pub use blake3;
|
||||||
#[cfg(feature = "stream_cipher")]
|
#[cfg(feature = "stream_cipher")]
|
||||||
pub use ctr;
|
pub use ctr;
|
||||||
|
#[cfg(feature = "hashing")]
|
||||||
|
pub use sha2;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use nym_coconut_dkg_common::verification_key::VerificationKeyShare;
|
|||||||
use nym_crypto::asymmetric::ed25519;
|
use nym_crypto::asymmetric::ed25519;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tracing::{debug, warn};
|
use tracing::warn;
|
||||||
|
|
||||||
pub trait Verifiable {
|
pub trait Verifiable {
|
||||||
fn verify_signature(&self, pub_key: &ed25519::PublicKey) -> bool;
|
fn verify_signature(&self, pub_key: &ed25519::PublicKey) -> bool;
|
||||||
@@ -36,6 +36,7 @@ pub trait ChainResponse: Verifiable + TimestampedResponse {
|
|||||||
|
|
||||||
// we rely on information provided from the api itself AS LONG AS it's not too outdated
|
// we rely on information provided from the api itself AS LONG AS it's not too outdated
|
||||||
if self.timestamp() + stale_response_threshold < now {
|
if self.timestamp() + stale_response_threshold < now {
|
||||||
|
warn!("chain status response is stale");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
self.chain_synced()
|
self.chain_synced()
|
||||||
@@ -96,26 +97,27 @@ pub trait SignerResponse: Verifiable + TimestampedResponse {
|
|||||||
|
|
||||||
// we rely on information provided from the api itself AS LONG AS it's not too outdated
|
// we rely on information provided from the api itself AS LONG AS it's not too outdated
|
||||||
if self.timestamp() + stale_response_threshold < now {
|
if self.timestamp() + stale_response_threshold < now {
|
||||||
|
warn!("stale signer response");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.has_signing_keys() {
|
if !self.has_signing_keys() {
|
||||||
debug!("missing signing keys");
|
warn!("missing signing keys");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.signer_disabled() {
|
if self.signer_disabled() {
|
||||||
debug!("signer functionalities explicitly disabled");
|
warn!("signer functionalities are explicitly disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.is_ecash_signer() {
|
if !self.is_ecash_signer() {
|
||||||
debug!("signer doesn't recognise it's a signer for this epoch");
|
warn!("signer doesn't recognise it's a signer for this epoch");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if dkg_epoch_id != self.dkg_ecash_epoch_id() {
|
if dkg_epoch_id != self.dkg_ecash_epoch_id() {
|
||||||
debug!(
|
warn!(
|
||||||
"mismatched dkg epoch id. current: {dkg_epoch_id}, signer's: {}",
|
"mismatched dkg epoch id. current: {dkg_epoch_id}, signer's: {}",
|
||||||
self.dkg_ecash_epoch_id()
|
self.dkg_ecash_epoch_id()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ use nym_crypto::asymmetric::ed25519;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use tracing::warn;
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
pub(crate) const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60);
|
pub(crate) const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(10 * 60);
|
||||||
pub(crate) const STALE_RESPONSE_THRESHOLD: Duration = Duration::from_secs(5 * 60);
|
pub(crate) const STALE_RESPONSE_THRESHOLD: Duration = Duration::from_secs(10 * 60);
|
||||||
|
|
||||||
// the reason for generics is not to remove duplication of code,
|
// the reason for generics is not to remove duplication of code,
|
||||||
// but because without them, we'd be having problems with circular dependencies,
|
// but because without them, we'd be having problems with circular dependencies,
|
||||||
@@ -188,6 +189,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let SignerStatus::Tested { result } = &self.status else {
|
let SignerStatus::Tested { result } = &self.status else {
|
||||||
|
warn!("no valid chain response");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
result
|
result
|
||||||
@@ -239,6 +241,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let SignerStatus::Tested { result } = &self.status else {
|
let SignerStatus::Tested { result } = &self.status else {
|
||||||
|
warn!("no valid signer response");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
result.signing_status.signing_available(
|
result.signing_status.signing_available(
|
||||||
|
|||||||
@@ -195,9 +195,9 @@ impl ClientUnderTest {
|
|||||||
pub(crate) async fn check_client(
|
pub(crate) async fn check_client(
|
||||||
dealer_details: DealerDetails,
|
dealer_details: DealerDetails,
|
||||||
dkg_epoch: u64,
|
dkg_epoch: u64,
|
||||||
contract_share: Option<&ContractVKShare>,
|
contract_share: Option<ContractVKShare>,
|
||||||
) -> TypedSignerResult {
|
) -> TypedSignerResult {
|
||||||
let dealer_information = RawDealerInformation::new(&dealer_details, contract_share);
|
let dealer_information = RawDealerInformation::new(&dealer_details, contract_share.as_ref());
|
||||||
|
|
||||||
// 7. attempt to construct client instances out of them
|
// 7. attempt to construct client instances out of them
|
||||||
let Ok(parsed_information) = dealer_information.parse() else {
|
let Ok(parsed_information) = dealer_information.parse() else {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client_check::check_client;
|
use crate::client_check::check_client;
|
||||||
use futures::stream::{FuturesUnordered, StreamExt};
|
use futures::stream;
|
||||||
|
use futures::stream::StreamExt;
|
||||||
use nym_ecash_signer_check_types::status::{SignerResult, Status};
|
use nym_ecash_signer_check_types::status::{SignerResult, Status};
|
||||||
use nym_network_defaults::NymNetworkDetails;
|
use nym_network_defaults::NymNetworkDetails;
|
||||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||||
@@ -65,7 +66,7 @@ where
|
|||||||
C: DkgQueryClient + Sync,
|
C: DkgQueryClient + Sync,
|
||||||
{
|
{
|
||||||
let dkg_details = dkg_details_with_client(client).await?;
|
let dkg_details = dkg_details_with_client(client).await?;
|
||||||
check_known_dealers(dkg_details).await
|
check_known_dealers(dkg_details, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dkg_details_with_client<C>(client: &C) -> Result<DkgDetails, SignerCheckError>
|
pub async fn dkg_details_with_client<C>(client: &C) -> Result<DkgDetails, SignerCheckError>
|
||||||
@@ -109,18 +110,21 @@ where
|
|||||||
|
|
||||||
pub async fn check_known_dealers(
|
pub async fn check_known_dealers(
|
||||||
dkg_details: DkgDetails,
|
dkg_details: DkgDetails,
|
||||||
|
concurrency: impl Into<Option<usize>>,
|
||||||
) -> Result<SignersTestResult, SignerCheckError> {
|
) -> Result<SignersTestResult, SignerCheckError> {
|
||||||
// 6. for each dealer attempt to perform the checks
|
// 6. for each dealer attempt to perform the checks
|
||||||
let results = dkg_details
|
let epoch_id = dkg_details.dkg_epoch.epoch_id;
|
||||||
.network_dealers
|
let submitted = dkg_details.submitted_shared;
|
||||||
.into_iter()
|
let dealers = dkg_details.network_dealers.len();
|
||||||
.map(|d| {
|
|
||||||
let share = dkg_details.submitted_shared.get(&d.assigned_index);
|
let tasks = dkg_details.network_dealers.into_iter().map(move |d| {
|
||||||
check_client(d, dkg_details.dkg_epoch.epoch_id, share)
|
let share = submitted.get(&d.assigned_index).cloned();
|
||||||
})
|
check_client(d, epoch_id, share)
|
||||||
.collect::<FuturesUnordered<_>>()
|
});
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.await;
|
let limit = concurrency.into().filter(|&n| n > 0).unwrap_or(dealers);
|
||||||
|
|
||||||
|
let results = stream::iter(tasks).buffer_unordered(limit).collect().await;
|
||||||
|
|
||||||
Ok(SignersTestResult {
|
Ok(SignersTestResult {
|
||||||
threshold: dkg_details.threshold,
|
threshold: dkg_details.threshold,
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ fn sanitize_fragment(segment: &str) -> &str {
|
|||||||
segment.trim_matches(|c: char| c.is_whitespace() || c == '/')
|
segment.trim_matches(|c: char| c.is_whitespace() || c == '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines a path that can be used to make a request to an API.
|
||||||
pub trait RequestPath: Debug {
|
pub trait RequestPath: Debug {
|
||||||
|
/// Sanitise the request path by removing empty segments and trimming whitespace and slashes
|
||||||
fn to_sanitized_segments(&self) -> Vec<&str>;
|
fn to_sanitized_segments(&self) -> Vec<&str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use axum::extract::{ConnectInfo, FromRequestParts};
|
||||||
|
use axum::http::request::Parts;
|
||||||
|
use axum_client_ip::RightmostXForwardedFor;
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
/// Best-effort client IP extractor.
|
||||||
|
///
|
||||||
|
/// Prefers the rightmost entry of `X-Forwarded-For` (set by a trusted reverse
|
||||||
|
/// proxy); falls back to the TCP peer address when the header is absent, and to
|
||||||
|
/// the unspecified address when neither is available (tests).
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ClientIpAddr(pub IpAddr);
|
||||||
|
|
||||||
|
impl<S> FromRequestParts<S> for ClientIpAddr
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = Infallible;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
if let Ok(RightmostXForwardedFor(ip)) =
|
||||||
|
RightmostXForwardedFor::from_request_parts(parts, state).await
|
||||||
|
{
|
||||||
|
return Ok(ClientIpAddr(ip));
|
||||||
|
}
|
||||||
|
if let Ok(ConnectInfo(addr)) =
|
||||||
|
ConnectInfo::<SocketAddr>::from_request_parts(parts, state).await
|
||||||
|
{
|
||||||
|
return Ok(ClientIpAddr(addr.ip()));
|
||||||
|
}
|
||||||
|
warn!("ClientIpAddr: no X-Forwarded-For or ConnectInfo found; using 0.0.0.0 fallback");
|
||||||
|
Ok(ClientIpAddr(IpAddr::V4(Ipv4Addr::UNSPECIFIED)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::middleware::client_ip::ClientIpAddr;
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use axum::http::HeaderValue;
|
use axum::http::HeaderValue;
|
||||||
use axum::http::header::{HOST, USER_AGENT};
|
use axum::http::header::{HOST, USER_AGENT};
|
||||||
use axum::middleware::Next;
|
use axum::middleware::Next;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum_client_ip::InsecureClientIp;
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
@@ -17,24 +17,24 @@ enum LogLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn log_request_info(
|
pub async fn log_request_info(
|
||||||
insecure_client_ip: InsecureClientIp,
|
client_ip: ClientIpAddr,
|
||||||
request: Request,
|
request: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
log_request(insecure_client_ip, request, next, LogLevel::Info).await
|
log_request(client_ip, request, next, LogLevel::Info).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn log_request_debug(
|
pub async fn log_request_debug(
|
||||||
insecure_client_ip: InsecureClientIp,
|
client_ip: ClientIpAddr,
|
||||||
request: Request,
|
request: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
log_request(insecure_client_ip, request, next, LogLevel::Debug).await
|
log_request(client_ip, request, next, LogLevel::Debug).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simple logger for requests
|
/// Simple logger for requests
|
||||||
async fn log_request(
|
async fn log_request(
|
||||||
InsecureClientIp(addr): InsecureClientIp,
|
ClientIpAddr(addr): ClientIpAddr,
|
||||||
request: Request,
|
request: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
pub mod bearer_auth;
|
pub mod bearer_auth;
|
||||||
|
pub mod client_ip;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ publish = true
|
|||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["codec"]
|
||||||
|
codec = ["dep:tokio-util"]
|
||||||
test-utils = ["pnet_packet"]
|
test-utils = ["pnet_packet"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -29,6 +31,5 @@ semver = { workspace = true }
|
|||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
time = { workspace = true }
|
time = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["time"] }
|
tokio-util = { workspace = true, features = ["codec"], optional = true }
|
||||||
tokio-util = { workspace = true, features = ["codec"] }
|
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use bytes::{Buf, Bytes, BytesMut};
|
use bytes::{Buf, Bytes, BytesMut};
|
||||||
|
#[cfg(feature = "codec")]
|
||||||
use tokio_util::codec::{Decoder, Encoder};
|
use tokio_util::codec::{Decoder, Encoder};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@@ -38,6 +39,23 @@ impl MultiIpPacketCodec {
|
|||||||
bundled_packets.extend_from_slice(&packet);
|
bundled_packets.extend_from_slice(&packet);
|
||||||
bundled_packets.freeze()
|
bundled_packets.freeze()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decode one length-prefixed packet from `src`, advancing past it.
|
||||||
|
///
|
||||||
|
/// Same logic as the `Decoder` impl but available without the `codec`
|
||||||
|
/// feature (i.e. without depending on `tokio-util`).
|
||||||
|
pub fn decode_one(&mut self, src: &mut BytesMut) -> Result<Option<IprPacket>, Error> {
|
||||||
|
if src.len() < LENGTH_PREFIX_SIZE {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let packet_size = u16::from_be_bytes([src[0], src[1]]) as usize;
|
||||||
|
if src.len() < packet_size + LENGTH_PREFIX_SIZE {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
src.advance(LENGTH_PREFIX_SIZE);
|
||||||
|
let packet = src.split_to(packet_size);
|
||||||
|
Ok(Some(IprPacket::Data(packet.freeze())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MultiIpPacketCodec {
|
impl Default for MultiIpPacketCodec {
|
||||||
@@ -82,6 +100,7 @@ impl From<Vec<u8>> for IprPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "codec")]
|
||||||
impl Encoder<IprPacket> for MultiIpPacketCodec {
|
impl Encoder<IprPacket> for MultiIpPacketCodec {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -125,6 +144,7 @@ impl Encoder<IprPacket> for MultiIpPacketCodec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "codec")]
|
||||||
impl Decoder for MultiIpPacketCodec {
|
impl Decoder for MultiIpPacketCodec {
|
||||||
type Item = IprPacket;
|
type Item = IprPacket;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
@@ -152,7 +172,7 @@ impl Decoder for MultiIpPacketCodec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(all(test, feature = "codec"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use tokio_util::codec::Decoder;
|
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -84,7 +83,7 @@ pub fn handle_ipr_response(data: &[u8]) -> Option<MixnetMessageOutcome> {
|
|||||||
let mut buf = BytesMut::from(data_response.ip_packet.as_ref());
|
let mut buf = BytesMut::from(data_response.ip_packet.as_ref());
|
||||||
let mut packets = Vec::new();
|
let mut packets = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
match codec.decode(&mut buf) {
|
match codec.decode_one(&mut buf) {
|
||||||
Ok(Some(packet)) => packets.push(packet.into_bytes()),
|
Ok(Some(packet)) => packets.push(packet.into_bytes()),
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ exclude = ["build.rs"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dotenvy = { workspace = true, optional = true }
|
dotenvy = { workspace = true, optional = true }
|
||||||
log = { workspace = true, optional = true }
|
|
||||||
schemars = { workspace = true, features = ["preserve_order"], optional = true }
|
schemars = { workspace = true, features = ["preserve_order"], optional = true }
|
||||||
serde = { workspace = true, features = ["derive"], optional = true }
|
serde = { workspace = true, features = ["derive"], optional = true }
|
||||||
serde_json = {workspace = true, optional = true }
|
serde_json = { workspace = true, optional = true }
|
||||||
tracing = {workspace = true, optional = true }
|
tracing = { workspace = true, optional = true }
|
||||||
url = { workspace = true, optional = true }
|
url = { workspace = true, optional = true }
|
||||||
utoipa = { workspace = true, optional = true }
|
utoipa = { workspace = true, optional = true }
|
||||||
|
|
||||||
@@ -32,9 +31,9 @@ utoipa = { workspace = true, optional = true }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["env", "network"]
|
default = ["env", "network"]
|
||||||
env = ["dotenvy", "log", "serde_json", "tracing"]
|
env = ["dotenvy", "serde_json", "tracing"]
|
||||||
network = ["schemars", "serde", "url"]
|
network = ["schemars", "serde", "url"]
|
||||||
utoipa = [ "dep:utoipa" ]
|
utoipa = ["dep:utoipa"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
|
|||||||
@@ -27,16 +27,20 @@ fn print_env_vars_with_keys_in_file<P: AsRef<Path> + Copy>(config_env_file: P) {
|
|||||||
.expect("Invalid path to environment configuration file");
|
.expect("Invalid path to environment configuration file");
|
||||||
for item in items {
|
for item in items {
|
||||||
let (key, val) = item.expect("Invalid item in environment configuration file");
|
let (key, val) = item.expect("Invalid item in environment configuration file");
|
||||||
log::debug!("{key}: {val}");
|
tracing::debug!("{key}: {val}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn env_configured() -> bool {
|
||||||
|
std::env::var(var_names::CONFIGURED).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
|
pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
|
||||||
match std::env::var(var_names::CONFIGURED) {
|
match std::env::var(var_names::CONFIGURED) {
|
||||||
// if the configuration is not already set in the env vars
|
// if the configuration is not already set in the env vars
|
||||||
Err(std::env::VarError::NotPresent) => {
|
Err(std::env::VarError::NotPresent) => {
|
||||||
if let Some(config_env_file) = &config_env_file {
|
if let Some(config_env_file) = &config_env_file {
|
||||||
log::debug!(
|
tracing::debug!(
|
||||||
"Loading environment variables from {:?}",
|
"Loading environment variables from {:?}",
|
||||||
config_env_file.as_ref()
|
config_env_file.as_ref()
|
||||||
);
|
);
|
||||||
@@ -47,12 +51,12 @@ pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
|
|||||||
// if nothing is set, the use mainnet defaults
|
// if nothing is set, the use mainnet defaults
|
||||||
// if the user has not set `CONFIGURED`, then even if they set any of the env variables,
|
// if the user has not set `CONFIGURED`, then even if they set any of the env variables,
|
||||||
// overwrite them
|
// overwrite them
|
||||||
log::debug!("Loading mainnet defaults");
|
tracing::debug!("Loading mainnet defaults");
|
||||||
crate::mainnet::export_to_env();
|
crate::mainnet::export_to_env();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::debug!("Environment variables already set. Using them");
|
tracing::debug!("Environment variables already set. Using them");
|
||||||
crate::mainnet::export_to_env()
|
crate::mainnet::export_to_env()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ pub const VESTING_CONTRACT_ADDRESS: &str =
|
|||||||
pub const PERFORMANCE_CONTRACT_ADDRESS: &str = "";
|
pub const PERFORMANCE_CONTRACT_ADDRESS: &str = "";
|
||||||
// /\ TODO: this has to be updated once the contract is deployed
|
// /\ TODO: this has to be updated once the contract is deployed
|
||||||
|
|
||||||
// \/ TODO: this has to be updated once the contract is deployed
|
pub const NETWORK_MONITORS_CONTRACT_ADDRESS: &str =
|
||||||
pub const NODE_FAMILIES_CONTRACT_ADDRESS: &str = "";
|
"n1m3a2ltkjqud8mkmrpqvgllrtv2p4r6js6qwl7p8cqkzrq8jg6e2qwqgl8z";
|
||||||
// /\ TODO: this has to be updated once the contract is deployed
|
pub const NODE_FAMILIES_CONTRACT_ADDRESS: &str =
|
||||||
|
"n1na0vys0z077hq3zrz6pfea85zgv8ks3t5zysdt6y38c87q045hnsyf2g5x";
|
||||||
pub const ECASH_CONTRACT_ADDRESS: &str =
|
pub const ECASH_CONTRACT_ADDRESS: &str =
|
||||||
"n1r7s6aksyc6pqardx88k3rkgfagwvj4z4zum9mmz2sfk3zm2mha0sd4dnun";
|
"n1r7s6aksyc6pqardx88k3rkgfagwvj4z4zum9mmz2sfk3zm2mha0sd4dnun";
|
||||||
pub const GROUP_CONTRACT_ADDRESS: &str =
|
pub const GROUP_CONTRACT_ADDRESS: &str =
|
||||||
@@ -40,6 +40,10 @@ pub const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772
|
|||||||
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||||
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
|
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
|
||||||
|
|
||||||
|
// cluster of lite rpc nodes (not part of consensus, aggressive pruning, no archival state)
|
||||||
|
pub const NYXD_QUERY_LITE: &str = "https://blockstream.nymtech.net";
|
||||||
|
pub const NYXD_WS_LITE: &str = "wss://blockstream.nymtech.net/websocket";
|
||||||
|
|
||||||
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||||
#[cfg(feature = "network")]
|
#[cfg(feature = "network")]
|
||||||
pub const NYM_APIS: &[ApiUrlConst] = &[
|
pub const NYM_APIS: &[ApiUrlConst] = &[
|
||||||
@@ -137,6 +141,11 @@ pub fn read_parsed_var_if_not_default<T: std::str::FromStr>(
|
|||||||
.map(std::str::FromStr::from_str)
|
.map(std::str::FromStr::from_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "env")]
|
||||||
|
pub fn read_parsed_var<T: std::str::FromStr>(var: &str) -> Result<T, T::Err> {
|
||||||
|
std::env::var(var).unwrap_or_default().parse()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "env", feature = "network"))]
|
#[cfg(all(feature = "env", feature = "network"))]
|
||||||
pub fn export_to_env() {
|
pub fn export_to_env() {
|
||||||
use crate::var_names;
|
use crate::var_names;
|
||||||
@@ -167,6 +176,14 @@ pub fn export_to_env() {
|
|||||||
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
|
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||||
COCONUT_DKG_CONTRACT_ADDRESS,
|
COCONUT_DKG_CONTRACT_ADDRESS,
|
||||||
);
|
);
|
||||||
|
set_var_to_default(
|
||||||
|
var_names::PERFORMANCE_CONTRACT_ADDRESS,
|
||||||
|
PERFORMANCE_CONTRACT_ADDRESS,
|
||||||
|
);
|
||||||
|
set_var_to_default(
|
||||||
|
var_names::NETWORK_MONITORS_CONTRACT_ADDRESS,
|
||||||
|
NETWORK_MONITORS_CONTRACT_ADDRESS,
|
||||||
|
);
|
||||||
set_var_to_default(
|
set_var_to_default(
|
||||||
var_names::REWARDING_VALIDATOR_ADDRESS,
|
var_names::REWARDING_VALIDATOR_ADDRESS,
|
||||||
REWARDING_VALIDATOR_ADDRESS,
|
REWARDING_VALIDATOR_ADDRESS,
|
||||||
@@ -186,6 +203,8 @@ pub fn export_to_env() {
|
|||||||
var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||||
UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||||
);
|
);
|
||||||
|
set_var_to_default(var_names::NYXD_QUERY_LITE, NYXD_QUERY_LITE);
|
||||||
|
set_var_to_default(var_names::NYXD_WS_LITE, NYXD_WS_LITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "env", feature = "network"))]
|
#[cfg(all(feature = "env", feature = "network"))]
|
||||||
@@ -237,4 +256,6 @@ pub fn export_to_env_if_not_set() {
|
|||||||
var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||||
UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||||
);
|
);
|
||||||
|
set_var_conditionally_to_default(var_names::NYXD_QUERY_LITE, NYXD_QUERY_LITE);
|
||||||
|
set_var_conditionally_to_default(var_names::NYXD_WS_LITE, NYXD_WS_LITE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ pub struct NymContracts {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub performance_contract_address: Option<String>,
|
pub performance_contract_address: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub network_monitors_contract_address: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub node_families_contract_address: Option<String>,
|
pub node_families_contract_address: Option<String>,
|
||||||
pub ecash_contract_address: Option<String>,
|
pub ecash_contract_address: Option<String>,
|
||||||
pub group_contract_address: Option<String>,
|
pub group_contract_address: Option<String>,
|
||||||
@@ -74,6 +76,15 @@ pub struct ApiUrl {
|
|||||||
pub front_hosts: Option<Vec<String>>,
|
pub front_hosts: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Url> for ApiUrl {
|
||||||
|
fn from(value: Url) -> Self {
|
||||||
|
ApiUrl {
|
||||||
|
url: value.to_string(),
|
||||||
|
front_hosts: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize)]
|
#[derive(Copy, Clone, Debug, Serialize)]
|
||||||
pub struct ApiUrlConst<'a> {
|
pub struct ApiUrlConst<'a> {
|
||||||
pub url: &'a str,
|
pub url: &'a str,
|
||||||
@@ -183,6 +194,10 @@ impl NymNetworkDetails {
|
|||||||
.with_group_contract(get_optional_env(var_names::GROUP_CONTRACT_ADDRESS))
|
.with_group_contract(get_optional_env(var_names::GROUP_CONTRACT_ADDRESS))
|
||||||
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
||||||
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
||||||
|
.with_performance_contract(get_optional_env(var_names::PERFORMANCE_CONTRACT_ADDRESS))
|
||||||
|
.with_network_monitors_contract(get_optional_env(
|
||||||
|
var_names::NETWORK_MONITORS_CONTRACT_ADDRESS,
|
||||||
|
))
|
||||||
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
||||||
.with_nym_vpn_api_urls(nym_vpn_api_urls)
|
.with_nym_vpn_api_urls(nym_vpn_api_urls)
|
||||||
.with_nym_api_urls(nym_api_urls)
|
.with_nym_api_urls(nym_api_urls)
|
||||||
@@ -204,6 +219,9 @@ impl NymNetworkDetails {
|
|||||||
performance_contract_address: parse_optional_str(
|
performance_contract_address: parse_optional_str(
|
||||||
mainnet::PERFORMANCE_CONTRACT_ADDRESS,
|
mainnet::PERFORMANCE_CONTRACT_ADDRESS,
|
||||||
),
|
),
|
||||||
|
network_monitors_contract_address: parse_optional_str(
|
||||||
|
mainnet::NETWORK_MONITORS_CONTRACT_ADDRESS,
|
||||||
|
),
|
||||||
node_families_contract_address: parse_optional_str(
|
node_families_contract_address: parse_optional_str(
|
||||||
mainnet::NODE_FAMILIES_CONTRACT_ADDRESS,
|
mainnet::NODE_FAMILIES_CONTRACT_ADDRESS,
|
||||||
),
|
),
|
||||||
@@ -234,7 +252,7 @@ impl NymNetworkDetails {
|
|||||||
|
|
||||||
fn set_optional_var(var_name: &str, value: Option<String>) {
|
fn set_optional_var(var_name: &str, value: Option<String>) {
|
||||||
if let Some(value) = value {
|
if let Some(value) = value {
|
||||||
unsafe {set_var(var_name, value)}
|
unsafe { set_var(var_name, value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -260,6 +278,7 @@ impl NymNetworkDetails {
|
|||||||
|
|
||||||
set_optional_var(var_names::MIXNET_CONTRACT_ADDRESS, self.contracts.mixnet_contract_address);
|
set_optional_var(var_names::MIXNET_CONTRACT_ADDRESS, self.contracts.mixnet_contract_address);
|
||||||
set_optional_var(var_names::VESTING_CONTRACT_ADDRESS, self.contracts.vesting_contract_address);
|
set_optional_var(var_names::VESTING_CONTRACT_ADDRESS, self.contracts.vesting_contract_address);
|
||||||
|
set_optional_var(var_names::NETWORK_MONITORS_CONTRACT_ADDRESS, self.contracts.network_monitors_contract_address);
|
||||||
set_optional_var(var_names::NODE_FAMILIES_CONTRACT_ADDRESS, self.contracts.node_families_contract_address);
|
set_optional_var(var_names::NODE_FAMILIES_CONTRACT_ADDRESS, self.contracts.node_families_contract_address);
|
||||||
set_optional_var(var_names::ECASH_CONTRACT_ADDRESS, self.contracts.ecash_contract_address);
|
set_optional_var(var_names::ECASH_CONTRACT_ADDRESS, self.contracts.ecash_contract_address);
|
||||||
set_optional_var(var_names::GROUP_CONTRACT_ADDRESS, self.contracts.group_contract_address);
|
set_optional_var(var_names::GROUP_CONTRACT_ADDRESS, self.contracts.group_contract_address);
|
||||||
@@ -379,15 +398,31 @@ impl NymNetworkDetails {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_performance_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
|
||||||
|
self.contracts.performance_contract_address = contract.map(Into::into);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_network_monitors_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
|
||||||
|
self.contracts.network_monitors_contract_address = contract.map(Into::into);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
|
pub fn with_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
|
||||||
self.nym_vpn_api_url = endpoint.map(Into::into);
|
self.nym_vpn_api_url = endpoint.map(Into::into);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_nym_api_urls<U: Into<ApiUrl>>(&mut self, urls: Vec<U>) {
|
||||||
|
self.nym_api_urls = Some(urls.into_iter().map(Into::into).collect());
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_nym_api_urls(mut self, urls: Vec<ApiUrl>) -> Self {
|
pub fn with_nym_api_urls<U: Into<ApiUrl>>(mut self, urls: Vec<U>) -> Self {
|
||||||
self.nym_api_urls = Some(urls);
|
self.set_nym_api_urls(urls);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,15 @@ pub const GROUP_CONTRACT_ADDRESS: &str = "GROUP_CONTRACT_ADDRESS";
|
|||||||
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
|
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
|
||||||
pub const NODE_FAMILIES_CONTRACT_ADDRESS: &str = "NODE_FAMILIES_CONTRACT_ADDRESS";
|
pub const NODE_FAMILIES_CONTRACT_ADDRESS: &str = "NODE_FAMILIES_CONTRACT_ADDRESS";
|
||||||
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
|
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
|
||||||
|
pub const PERFORMANCE_CONTRACT_ADDRESS: &str = "PERFORMANCE_CONTRACT_ADDRESS";
|
||||||
|
pub const NETWORK_MONITORS_CONTRACT_ADDRESS: &str = "NETWORK_MONITORS_CONTRACT_ADDRESS";
|
||||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
|
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
|
||||||
pub const NYXD: &str = "NYXD";
|
pub const NYXD: &str = "NYXD";
|
||||||
pub const NYM_API: &str = "NYM_API";
|
pub const NYM_API: &str = "NYM_API";
|
||||||
pub const NYM_APIS: &str = "NYM_APIS";
|
pub const NYM_APIS: &str = "NYM_APIS";
|
||||||
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
||||||
|
pub const NYXD_QUERY_LITE: &str = "NYXD_LITE";
|
||||||
|
pub const NYXD_WS_LITE: &str = "NYXD_WS_LITE";
|
||||||
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
||||||
pub const NYM_VPN_API: &str = "NYM_VPN_API";
|
pub const NYM_VPN_API: &str = "NYM_VPN_API";
|
||||||
pub const NYM_VPN_APIS: &str = "NYM_VPN_APIS";
|
pub const NYM_VPN_APIS: &str = "NYM_VPN_APIS";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ bytes.workspace = true
|
|||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
nym-config = { workspace = true }
|
nym-config = { workspace = true }
|
||||||
nym-common = { workspace = true }
|
nym-common = { workspace = true }
|
||||||
nym-ip-packet-requests = { workspace = true }
|
nym-ip-packet-requests = { workspace = true, default-features = true }
|
||||||
nym-sdk = { workspace = true }
|
nym-sdk = { workspace = true }
|
||||||
pnet_packet.workspace = true
|
pnet_packet.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("failed to create ipv4 packet")]
|
#[error("failed to create ipv4 packet")]
|
||||||
Ipv4PacketCreationFailure,
|
Ipv4PacketCreationFailure,
|
||||||
|
|
||||||
|
#[error("packet length {length} exceeds the u16 IP header field")]
|
||||||
|
PacketLengthOverflow { length: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result type based on our error type
|
// Result type based on our error type
|
||||||
|
|||||||
@@ -79,9 +79,14 @@ pub fn wrap_icmp_in_ipv4(
|
|||||||
let mut ipv4_packet =
|
let mut ipv4_packet =
|
||||||
MutableIpv4Packet::owned(ipv4_buffer).ok_or(Error::Ipv4PacketCreationFailure)?;
|
MutableIpv4Packet::owned(ipv4_buffer).ok_or(Error::Ipv4PacketCreationFailure)?;
|
||||||
|
|
||||||
|
let total_length_u16 =
|
||||||
|
u16::try_from(total_length).map_err(|_| Error::PacketLengthOverflow {
|
||||||
|
length: total_length,
|
||||||
|
})?;
|
||||||
|
|
||||||
ipv4_packet.set_version(4);
|
ipv4_packet.set_version(4);
|
||||||
ipv4_packet.set_header_length(5);
|
ipv4_packet.set_header_length(5);
|
||||||
ipv4_packet.set_total_length(total_length as u16);
|
ipv4_packet.set_total_length(total_length_u16);
|
||||||
ipv4_packet.set_ttl(64);
|
ipv4_packet.set_ttl(64);
|
||||||
ipv4_packet.set_next_level_protocol(pnet_packet::ip::IpNextHeaderProtocols::Icmp);
|
ipv4_packet.set_next_level_protocol(pnet_packet::ip::IpNextHeaderProtocols::Icmp);
|
||||||
ipv4_packet.set_source(source);
|
ipv4_packet.set_source(source);
|
||||||
@@ -101,12 +106,18 @@ pub fn wrap_icmp_in_ipv6(
|
|||||||
source: Ipv6Addr,
|
source: Ipv6Addr,
|
||||||
destination: Ipv6Addr,
|
destination: Ipv6Addr,
|
||||||
) -> Result<Ipv6Packet> {
|
) -> Result<Ipv6Packet> {
|
||||||
let ipv6_buffer = vec![0u8; 40 + icmp_echo_request.packet().len()];
|
let payload_length = icmp_echo_request.packet().len();
|
||||||
|
let payload_length_u16 =
|
||||||
|
u16::try_from(payload_length).map_err(|_| Error::PacketLengthOverflow {
|
||||||
|
length: payload_length,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let ipv6_buffer = vec![0u8; 40 + payload_length];
|
||||||
let mut ipv6_packet =
|
let mut ipv6_packet =
|
||||||
MutableIpv6Packet::owned(ipv6_buffer).ok_or(Error::Ipv4PacketCreationFailure)?;
|
MutableIpv6Packet::owned(ipv6_buffer).ok_or(Error::Ipv4PacketCreationFailure)?;
|
||||||
|
|
||||||
ipv6_packet.set_version(6);
|
ipv6_packet.set_version(6);
|
||||||
ipv6_packet.set_payload_length(icmp_echo_request.packet().len() as u16);
|
ipv6_packet.set_payload_length(payload_length_u16);
|
||||||
ipv6_packet.set_next_header(pnet_packet::ip::IpNextHeaderProtocols::Icmpv6);
|
ipv6_packet.set_next_header(pnet_packet::ip::IpNextHeaderProtocols::Icmpv6);
|
||||||
ipv6_packet.set_hop_limit(64);
|
ipv6_packet.set_hop_limit(64);
|
||||||
ipv6_packet.set_source(source);
|
ipv6_packet.set_source(source);
|
||||||
@@ -164,3 +175,122 @@ pub(crate) fn is_icmp_v6_echo_reply(packet: &Bytes) -> Option<(u16, Ipv6Addr, Ip
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pnet_packet::icmp::IcmpTypes;
|
||||||
|
use pnet_packet::icmpv6::Icmpv6Types;
|
||||||
|
use pnet_packet::ip::IpNextHeaderProtocols;
|
||||||
|
|
||||||
|
const V4_SRC: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
|
||||||
|
const V4_DST: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
|
||||||
|
const V6_SRC: Ipv6Addr = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
|
||||||
|
const V6_DST: Ipv6Addr = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn icmpv4_echo_request_sets_fields_and_valid_checksum() {
|
||||||
|
let echo = create_icmpv4_echo_request(42, 7).unwrap();
|
||||||
|
assert_eq!(echo.get_sequence_number(), 42);
|
||||||
|
assert_eq!(echo.get_identifier(), 7);
|
||||||
|
assert_eq!(echo.get_icmp_type(), IcmpTypes::EchoRequest);
|
||||||
|
|
||||||
|
// pnet's `checksum` skips the checksum word, so recomputing on the produced
|
||||||
|
// packet must equal the stored value.
|
||||||
|
let icmp = IcmpPacket::new(echo.packet()).unwrap();
|
||||||
|
assert_eq!(echo.get_checksum(), pnet_packet::icmp::checksum(&icmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn icmpv6_echo_request_sets_fields_and_valid_checksum() {
|
||||||
|
let echo = create_icmpv6_echo_request(99, 12, &V6_SRC, &V6_DST).unwrap();
|
||||||
|
assert_eq!(echo.get_sequence_number(), 99);
|
||||||
|
assert_eq!(echo.get_identifier(), 12);
|
||||||
|
assert_eq!(echo.get_icmpv6_type(), Icmpv6Types::EchoRequest);
|
||||||
|
|
||||||
|
let icmpv6 = icmpv6::Icmpv6Packet::new(echo.packet()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
echo.get_checksum(),
|
||||||
|
pnet_packet::icmpv6::checksum(&icmpv6, &V6_SRC, &V6_DST)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrap_icmp_in_ipv4_sets_headers_and_payload() {
|
||||||
|
let echo = create_icmpv4_echo_request(1, 2).unwrap();
|
||||||
|
let echo_bytes = echo.packet().to_vec();
|
||||||
|
let packet = wrap_icmp_in_ipv4(echo, V4_SRC, V4_DST).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(packet.get_version(), 4);
|
||||||
|
assert_eq!(packet.get_header_length(), 5);
|
||||||
|
assert_eq!(packet.get_total_length() as usize, 20 + echo_bytes.len());
|
||||||
|
assert_eq!(packet.get_ttl(), 64);
|
||||||
|
assert_eq!(
|
||||||
|
packet.get_next_level_protocol(),
|
||||||
|
IpNextHeaderProtocols::Icmp
|
||||||
|
);
|
||||||
|
assert_eq!(packet.get_source(), V4_SRC);
|
||||||
|
assert_eq!(packet.get_destination(), V4_DST);
|
||||||
|
assert_eq!(packet.payload(), echo_bytes.as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrap_icmp_in_ipv6_sets_headers_and_payload() {
|
||||||
|
let echo = create_icmpv6_echo_request(1, 2, &V6_SRC, &V6_DST).unwrap();
|
||||||
|
let echo_bytes = echo.packet().to_vec();
|
||||||
|
let packet = wrap_icmp_in_ipv6(echo, V6_SRC, V6_DST).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(packet.get_version(), 6);
|
||||||
|
assert_eq!(packet.get_payload_length() as usize, echo_bytes.len());
|
||||||
|
assert_eq!(packet.get_next_header(), IpNextHeaderProtocols::Icmpv6);
|
||||||
|
assert_eq!(packet.get_hop_limit(), 64);
|
||||||
|
assert_eq!(packet.get_source(), V6_SRC);
|
||||||
|
assert_eq!(packet.get_destination(), V6_DST);
|
||||||
|
assert_eq!(packet.payload(), echo_bytes.as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_ipv4_checksum_is_zero_on_correctly_checksummed_packet() {
|
||||||
|
let echo = create_icmpv4_echo_request(1, 2).unwrap();
|
||||||
|
let packet = wrap_icmp_in_ipv4(echo, V4_SRC, V4_DST).unwrap();
|
||||||
|
// RFC 1071: summing every 16-bit word of a header that already contains its
|
||||||
|
// own checksum yields all-ones; the one's complement is therefore zero.
|
||||||
|
assert_eq!(compute_ipv4_checksum(&packet), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_icmp_echo_reply_extracts_identifier_and_addresses() {
|
||||||
|
// pnet's EchoReply/EchoRequest share the same byte layout (only the ICMP
|
||||||
|
// type field differs) and `is_icmp_echo_reply` does not check the type,
|
||||||
|
// so a wrapped echo *request* exercises the same parsing path.
|
||||||
|
let identifier = 1234;
|
||||||
|
let echo = create_icmpv4_echo_request(7, identifier).unwrap();
|
||||||
|
let packet = wrap_icmp_in_ipv4(echo, V4_SRC, V4_DST).unwrap();
|
||||||
|
let bytes = Bytes::copy_from_slice(packet.packet());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
is_icmp_echo_reply(&bytes),
|
||||||
|
Some((identifier, V4_SRC, V4_DST))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_icmp_v6_echo_reply_extracts_identifier_and_addresses() {
|
||||||
|
let identifier = 5678;
|
||||||
|
let echo = create_icmpv6_echo_request(7, identifier, &V6_SRC, &V6_DST).unwrap();
|
||||||
|
let packet = wrap_icmp_in_ipv6(echo, V6_SRC, V6_DST).unwrap();
|
||||||
|
let bytes = Bytes::copy_from_slice(packet.packet());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
is_icmp_v6_echo_reply(&bytes),
|
||||||
|
Some((identifier, V6_SRC, V6_DST))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_icmp_echo_reply_returns_none_for_undersized_bytes() {
|
||||||
|
let bytes = Bytes::from_static(&[0u8; 4]);
|
||||||
|
assert!(is_icmp_echo_reply(&bytes).is_none());
|
||||||
|
assert!(is_icmp_v6_echo_reply(&bytes).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::{
|
|||||||
|
|
||||||
use ansi_term::Color::Yellow;
|
use ansi_term::Color::Yellow;
|
||||||
use bytes::{Buf, BytesMut};
|
use bytes::{Buf, BytesMut};
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, trace, warn};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use crate::MAX_RTO;
|
use crate::MAX_RTO;
|
||||||
@@ -499,21 +499,9 @@ impl KcpSession {
|
|||||||
self.snd_buf.len(),
|
self.snd_buf.len(),
|
||||||
post_retain_sns
|
post_retain_sns
|
||||||
);
|
);
|
||||||
// Corrected format string arguments for the removed count log
|
|
||||||
debug!(
|
|
||||||
"[ConvID: {}, Thread: {:?}] parse_una(una={}): Removed {} segment(s) from snd_buf ({} -> {}). Remaining sns: {:?}",
|
|
||||||
self.conv,
|
|
||||||
thread::current().id(),
|
|
||||||
una,
|
|
||||||
removed_count,
|
|
||||||
original_len,
|
|
||||||
self.snd_buf.len(),
|
|
||||||
post_retain_sns
|
|
||||||
);
|
|
||||||
|
|
||||||
if removed_count > 0 {
|
if removed_count == 0 {
|
||||||
// Use trace level if no segments were removed but buffer wasn't empty
|
trace!(
|
||||||
debug!(
|
|
||||||
"[ConvID: {}, Thread: {:?}] parse_una(una={}): No segments removed from snd_buf (len={}). Remaining sns: {:?}",
|
"[ConvID: {}, Thread: {:?}] parse_una(una={}): No segments removed from snd_buf (len={}). Remaining sns: {:?}",
|
||||||
self.conv,
|
self.conv,
|
||||||
thread::current().id(),
|
thread::current().id(),
|
||||||
@@ -521,6 +509,17 @@ impl KcpSession {
|
|||||||
original_len,
|
original_len,
|
||||||
self.snd_buf.iter().map(|s| s.sn).collect::<Vec<_>>()
|
self.snd_buf.iter().map(|s| s.sn).collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"[ConvID: {}, Thread: {:?}] parse_una(una={}): Removed {} segment(s) from snd_buf ({} -> {}). Remaining sns: {:?}",
|
||||||
|
self.conv,
|
||||||
|
thread::current().id(),
|
||||||
|
una,
|
||||||
|
removed_count,
|
||||||
|
original_len,
|
||||||
|
self.snd_buf.len(),
|
||||||
|
post_retain_sns
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the known acknowledged sequence number.
|
// Update the known acknowledged sequence number.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-kkt"
|
name = "nym-kkt"
|
||||||
description = "Key transport protocol for the Nym network"
|
description = "Key transport protocol for the Nym network"
|
||||||
version = "1.21.0"
|
version = "1.21.1"
|
||||||
authors = ["Georgio Nicolas <georgio@nymtech.net>"]
|
authors = ["Georgio Nicolas <georgio@nymtech.net>"]
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "nym-lp-data"
|
||||||
|
description = "Lewes Protocol data structure for the Nym network"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
documentation.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
publish = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes.workspace = true
|
||||||
|
num_enum.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
nym-common.workspace = true
|
||||||
|
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
pub mod packet;
|
||||||
@@ -110,7 +110,9 @@ impl LpFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn len(&self) -> usize {
|
// is_empty in the sense len == 0 doesn't make sense in that case
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
LpFrameHeader::SIZE + self.content.len()
|
LpFrameHeader::SIZE + self.content.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,6 +167,8 @@ impl SphinxStreamFrameAttributes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(attrs: &LpFrameAttributes) -> Result<Self, MalformedLpPacketError> {
|
pub fn parse(attrs: &LpFrameAttributes) -> Result<Self, MalformedLpPacketError> {
|
||||||
|
// SAFETY : 8 bytes slice into 8 bytes array
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
let stream_id = u64::from_be_bytes(attrs[0..8].try_into().unwrap());
|
let stream_id = u64::from_be_bytes(attrs[0..8].try_into().unwrap());
|
||||||
let msg_type = match attrs[8] {
|
let msg_type = match attrs[8] {
|
||||||
0 => SphinxStreamMsgType::Open,
|
0 => SphinxStreamMsgType::Open,
|
||||||
@@ -175,6 +179,8 @@ impl SphinxStreamFrameAttributes {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// SAFETY : 4 bytes slice into 4 bytes array
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
let sequence_num = u32::from_be_bytes(attrs[9..13].try_into().unwrap());
|
let sequence_num = u32::from_be_bytes(attrs[9..13].try_into().unwrap());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
stream_id,
|
stream_id,
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::packet::error::MalformedLpPacketError;
|
||||||
use crate::packet::version;
|
use crate::packet::version;
|
||||||
use crate::{packet::error::MalformedLpPacketError, peer_config::LpReceiverIndex};
|
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
pub type LpReceiverIndex = u32;
|
||||||
|
|
||||||
/// Outer header (12 bytes) - always cleartext, used for routing.
|
/// Outer header (12 bytes) - always cleartext, used for routing.
|
||||||
///
|
///
|
||||||
/// This is the first 12 bytes of every LP packet, containing only the fields
|
/// This is the first 12 bytes of every LP packet, containing only the fields
|
||||||
@@ -13,7 +13,6 @@ pub use header::{InnerHeader, LpHeader, OuterHeader};
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod frame;
|
pub mod frame;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod replay;
|
|
||||||
|
|
||||||
pub mod version {
|
pub mod version {
|
||||||
/// The current version of the Lewes Protocol that is put into each new constructed header.
|
/// The current version of the Lewes Protocol that is put into each new constructed header.
|
||||||
@@ -25,10 +25,10 @@ nym-crypto = { workspace = true, features = ["hashing"] }
|
|||||||
nym-common.workspace = true
|
nym-common.workspace = true
|
||||||
nym-kkt = { workspace = true }
|
nym-kkt = { workspace = true }
|
||||||
nym-kkt-ciphersuite = { workspace = true }
|
nym-kkt-ciphersuite = { workspace = true }
|
||||||
|
nym-lp-data.workspace = true
|
||||||
|
|
||||||
# libcrux dependencies for PSQ (Post-Quantum PSK derivation)
|
# libcrux dependencies for PSQ (Post-Quantum PSK derivation)
|
||||||
libcrux-psq = { workspace = true, features = ["test-utils"] }
|
libcrux-psq = { workspace = true, features = ["test-utils"] }
|
||||||
num_enum = { workspace = true }
|
|
||||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||||
|
|
||||||
|
|
||||||
@@ -48,3 +48,6 @@ mock = ["nym-test-utils"]
|
|||||||
[[bench]]
|
[[bench]]
|
||||||
name = "replay_protection"
|
name = "replay_protection"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main};
|
use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main};
|
||||||
use nym_lp::replay::ReceivingKeyCounterValidator;
|
use nym_lp::replay::ReceivingKeyCounterValidator;
|
||||||
use nym_test_utils::helpers::deterministic_rng_09;
|
use nym_test_utils::helpers::deterministic_rng_09;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::LpError;
|
use crate::LpError;
|
||||||
use crate::packet::{EncryptedLpPacket, InnerHeader, LpFrame, LpHeader, LpPacket};
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use libcrux_psq::Channel;
|
use libcrux_psq::Channel;
|
||||||
|
use nym_lp_data::packet::{EncryptedLpPacket, InnerHeader, LpFrame, LpHeader, LpPacket};
|
||||||
|
|
||||||
// needs to be equal or above to the actual overhead
|
// needs to be equal or above to the actual overhead
|
||||||
pub(crate) const SANE_ENC_OVERHEAD: usize = 32;
|
pub(crate) const SANE_ENC_OVERHEAD: usize = 32;
|
||||||
@@ -82,12 +82,12 @@ pub(crate) fn decrypt_lp_packet(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::LpError;
|
use crate::LpError;
|
||||||
use crate::codec::{decrypt_data, decrypt_lp_packet, encrypt_data, encrypt_lp_packet};
|
use crate::codec::{decrypt_data, decrypt_lp_packet, encrypt_data, encrypt_lp_packet};
|
||||||
use crate::packet::{EncryptedLpPacket, LpFrame, LpHeader, LpPacket};
|
|
||||||
use crate::peer::mock_peers;
|
use crate::peer::mock_peers;
|
||||||
use crate::psq::initiator::{build_psq_ciphersuite, build_psq_principal};
|
use crate::psq::initiator::{build_psq_ciphersuite, build_psq_principal};
|
||||||
use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size, responder};
|
use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size, responder};
|
||||||
use libcrux_psq::{Channel, IntoSession};
|
use libcrux_psq::{Channel, IntoSession};
|
||||||
use nym_kkt_ciphersuite::KEM;
|
use nym_kkt_ciphersuite::KEM;
|
||||||
|
use nym_lp_data::packet::{EncryptedLpPacket, LpFrame, LpHeader, LpPacket};
|
||||||
use nym_test_utils::helpers::u64_seeded_rng_09;
|
use nym_test_utils::helpers::u64_seeded_rng_09;
|
||||||
|
|
||||||
fn mock_transport() -> (
|
fn mock_transport() -> (
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user