Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 761f970912 | |||
| 0cdefc5881 | |||
| 85454dc431 |
@@ -1,2 +0,0 @@
|
|||||||
[target.wasm32-unknown-unknown]
|
|
||||||
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]
|
|
||||||
@@ -25,14 +25,14 @@ jobs:
|
|||||||
echo "file2=$(ls nym-vpn*.deb)" >> $GITHUB_ENV
|
echo "file2=$(ls nym-vpn*.deb)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload nym-repo-setup
|
- name: Upload nym-repo-setup
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ env.file1 }}
|
name: ${{ env.file1 }}
|
||||||
path: ppa/packages/nym-repo-setup*.deb
|
path: ppa/packages/nym-repo-setup*.deb
|
||||||
retention-days: 10
|
retention-days: 10
|
||||||
|
|
||||||
- name: Upload nym-vpn
|
- name: Upload nym-vpn
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ env.file2 }}
|
name: ${{ env.file2 }}
|
||||||
path: ppa/packages/nym-vpn*.deb
|
path: ppa/packages/nym-vpn*.deb
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ jobs:
|
|||||||
run: sudo apt-get install -y rsync
|
run: sudo apt-get install -y rsync
|
||||||
- uses: rlespinasse/github-slug-action@v3.x
|
- uses: rlespinasse/github-slug-action@v3.x
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v5.0.0
|
uses: pnpm/action-setup@v4.2.0
|
||||||
with:
|
with:
|
||||||
version: 11.1.2
|
version: 9
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 20
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@@ -37,9 +37,6 @@ jobs:
|
|||||||
command: build
|
command: build
|
||||||
args: --workspace --release
|
args: --workspace --release
|
||||||
|
|
||||||
- name: Verify doc versions
|
|
||||||
run: ${{ github.workspace }}/documentation/scripts/verify-doc-versions.sh
|
|
||||||
working-directory: ${{ github.workspace }}
|
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
run: pnpm i
|
run: pnpm i
|
||||||
- name: Generate llms-full.txt
|
- name: Generate llms-full.txt
|
||||||
|
|||||||
@@ -17,16 +17,13 @@ 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: 24
|
node-version: 20
|
||||||
cache: pnpm
|
- name: Setup yarn
|
||||||
|
run: npm install -g yarn
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm install && pnpm build && pnpm build:ci:storybook
|
run: yarn && yarn build && yarn 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
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: nym-binaries-artifacts
|
name: nym-binaries-artifacts
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ jobs:
|
|||||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||||
EXCLUDE: "/dist/, /node_modules/"
|
EXCLUDE: "/dist/, /node_modules/"
|
||||||
@@ -23,6 +23,7 @@ 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'
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
name: ci-crates-preflight
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'Cargo.toml'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- 'tools/internal/check_publish_preflight.py'
|
|
||||||
- '.github/workflows/ci-crates-preflight.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
preflight:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Preflight publish checks
|
|
||||||
run: python3 tools/internal/check_publish_preflight.py
|
|
||||||
@@ -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: 24
|
node-version: "20"
|
||||||
|
|
||||||
- name: Validate version format
|
- name: Validate version format
|
||||||
run: |
|
run: |
|
||||||
@@ -57,8 +57,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update workspace dependencies
|
- name: Update workspace dependencies
|
||||||
run: |
|
run: |
|
||||||
# Match any semver version on lines with `path = `, not just the current workspace version.
|
sed -i '/path = /s/version = "${{ steps.current_version.outputs.version }}"/version = "${{ inputs.version }}"/g' Cargo.toml
|
||||||
sed -i '/path = /s/version = "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"/version = "${{ inputs.version }}"/g' Cargo.toml
|
|
||||||
|
|
||||||
- name: Bump versions (local only)
|
- name: Bump versions (local only)
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -33,11 +33,7 @@ jobs:
|
|||||||
- name: Install cargo-workspaces
|
- name: Install cargo-workspaces
|
||||||
run: cargo install cargo-workspaces
|
run: cargo install cargo-workspaces
|
||||||
|
|
||||||
- name: Preflight publish checks
|
# `--publish-as-is` skips version bumping since that's done in a separate CI job.
|
||||||
run: |
|
|
||||||
python3 tools/internal/check_publish_preflight.py
|
|
||||||
|
|
||||||
# --publish-as-is skips version bumping since that's done in a separate CI job.
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
env:
|
env:
|
||||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|||||||
@@ -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: 24
|
node-version: "20"
|
||||||
|
|
||||||
- name: Validate version format
|
- name: Validate version format
|
||||||
run: |
|
run: |
|
||||||
@@ -59,9 +59,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update workspace dependencies
|
- name: Update workspace dependencies
|
||||||
run: |
|
run: |
|
||||||
# Match any semver version on lines with `path = `, not just the current workspace version.
|
sed -i '/path = /s/version = "${{ steps.current_version.outputs.version }}"/version = "${{ inputs.version }}"/g' Cargo.toml
|
||||||
# This catches entries whose version has drifted (e.g. nym-sqlx-pool-guard at 1.2.0).
|
|
||||||
sed -i '/path = /s/version = "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"/version = "${{ inputs.version }}"/g' Cargo.toml
|
|
||||||
|
|
||||||
- name: Bump versions
|
- name: Bump versions
|
||||||
run: |
|
run: |
|
||||||
@@ -69,35 +67,20 @@ jobs:
|
|||||||
--no-git-commit \
|
--no-git-commit \
|
||||||
--yes
|
--yes
|
||||||
|
|
||||||
- name: Commit and push version bump
|
- name: Create pull request
|
||||||
run: |
|
uses: peter-evans/create-pull-request@v7
|
||||||
set -euo pipefail
|
with:
|
||||||
BASE_BRANCH="${GITHUB_REF_NAME}"
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
PR_BRANCH="ci/crates-version-bump-${{ inputs.version }}-${GITHUB_RUN_ID}"
|
branch: "chore/bump-version-${{ inputs.version }}"
|
||||||
|
base: ${{ github.ref_name }}
|
||||||
|
commit-message: "crates release: bump version to ${{ inputs.version }}"
|
||||||
|
title: "chore: bump crate versions to ${{ inputs.version }}"
|
||||||
|
body: |
|
||||||
|
Automated version bump from `${{ steps.current_version.outputs.version }}` → `${{ inputs.version }}`.
|
||||||
|
|
||||||
git checkout -b "$PR_BRANCH"
|
Triggered by @${{ github.actor }} via workflow dispatch.
|
||||||
git add -A
|
labels: "automated, crates-version-bump"
|
||||||
git commit -m "crates release: bump version to ${{ inputs.version }}"
|
delete-branch: true
|
||||||
git push -u origin "$PR_BRANCH"
|
|
||||||
|
|
||||||
cat > /tmp/crates-version-bump-pr-body.md <<'EOF'
|
|
||||||
This PR was created by CI because direct pushes to the release branch are blocked by branch protection rules.
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
- Bump workspace crate versions to the requested release version.
|
|
||||||
- Update workspace dependency versions accordingly.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
- Merge this PR to proceed with crates.io publishing.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
gh pr create \
|
|
||||||
--base "$BASE_BRANCH" \
|
|
||||||
--head "$PR_BRANCH" \
|
|
||||||
--title "crates release: bump version to ${{ inputs.version }}" \
|
|
||||||
--body-file /tmp/crates-version-bump-pr-body.md
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Show package versions
|
- name: Show package versions
|
||||||
run: cargo workspaces list --long
|
run: cargo workspaces list --long
|
||||||
@@ -7,10 +7,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "documentation/docs/**"
|
- "documentation/docs/**"
|
||||||
- "sdk/typescript/packages/sdk/src/**"
|
- "sdk/typescript/packages/sdk/src/**"
|
||||||
- "sdk/typescript/packages/mix-tunnel/src/**"
|
|
||||||
- "sdk/typescript/packages/mix-fetch/src/**"
|
- "sdk/typescript/packages/mix-fetch/src/**"
|
||||||
- "sdk/typescript/packages/mix-dns/src/**"
|
|
||||||
- "sdk/typescript/packages/mix-websocket/src/**"
|
|
||||||
- ".github/workflows/ci-docs.yml"
|
- ".github/workflows/ci-docs.yml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -31,12 +28,12 @@ jobs:
|
|||||||
run: sudo apt-get install -y rsync
|
run: sudo apt-get install -y rsync
|
||||||
- uses: rlespinasse/github-slug-action@v3.x
|
- uses: rlespinasse/github-slug-action@v3.x
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v5.0.0
|
uses: pnpm/action-setup@v4.2.0
|
||||||
with:
|
with:
|
||||||
version: 11.1.2
|
version: 9
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 20
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@@ -50,7 +47,7 @@ jobs:
|
|||||||
- name: Check if TypeScript SDK source changed
|
- name: Check if TypeScript SDK source changed
|
||||||
id: check-ts-sdk
|
id: check-ts-sdk
|
||||||
run: |
|
run: |
|
||||||
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -qE '^sdk/typescript/packages/(sdk|mix-tunnel|mix-fetch|mix-dns|mix-websocket)/src/'; then
|
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -qE '^sdk/typescript/packages/(sdk|mix-fetch)/src/'; then
|
||||||
echo "changed=true" >> $GITHUB_OUTPUT
|
echo "changed=true" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "changed=false" >> $GITHUB_OUTPUT
|
echo "changed=false" >> $GITHUB_OUTPUT
|
||||||
@@ -61,15 +58,9 @@ jobs:
|
|||||||
if: steps.check-ts-sdk.outputs.changed == 'true'
|
if: steps.check-ts-sdk.outputs.changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
npm install -g typedoc@0.25.13 typedoc-plugin-markdown@4.0.3
|
npm install -g typedoc@0.25.13 typedoc-plugin-markdown@4.0.3
|
||||||
cd ${{ github.workspace }}/sdk/typescript/packages/sdk && typedoc --skipErrorChecking
|
cd ${{ github.workspace }}/sdk/typescript/packages/sdk && typedoc --skipErrorChecking
|
||||||
cd ${{ github.workspace }}/sdk/typescript/packages/mix-tunnel && typedoc --skipErrorChecking
|
cd ${{ github.workspace }}/sdk/typescript/packages/mix-fetch && typedoc --skipErrorChecking
|
||||||
cd ${{ github.workspace }}/sdk/typescript/packages/mix-fetch && typedoc --skipErrorChecking
|
|
||||||
cd ${{ github.workspace }}/sdk/typescript/packages/mix-dns && typedoc --skipErrorChecking
|
|
||||||
cd ${{ github.workspace }}/sdk/typescript/packages/mix-websocket && typedoc --skipErrorChecking
|
|
||||||
|
|
||||||
- name: Verify doc versions
|
|
||||||
run: ${{ github.workspace }}/documentation/scripts/verify-doc-versions.sh
|
|
||||||
working-directory: ${{ github.workspace }}
|
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
run: pnpm i
|
run: pnpm i
|
||||||
- name: Generate llms-full.txt
|
- name: Generate llms-full.txt
|
||||||
|
|||||||
@@ -20,14 +20,12 @@ 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: 24
|
node-version: 20
|
||||||
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
|
||||||
@@ -40,24 +38,22 @@ jobs:
|
|||||||
- name: Install wasm-opt
|
- name: Install wasm-opt
|
||||||
run: cargo install wasm-opt
|
run: cargo install wasm-opt
|
||||||
|
|
||||||
# Produce wasm/smolmix/pkg/package.json before any pnpm step. The
|
- name: Set up Go
|
||||||
# `pnpm dev:on` in `prebuild:ci` adds wasm/smolmix/pkg to the dynamic
|
uses: actions/setup-go@v6
|
||||||
# workspace; mix-tunnel's `workspace:*` lookup against @nymproject/
|
with:
|
||||||
# smolmix-wasm needs the package.json to be present.
|
go-version: "1.24.6"
|
||||||
- name: Build smolmix wasm
|
|
||||||
run: make -C wasm/smolmix
|
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: pnpm i
|
run: yarn
|
||||||
|
|
||||||
- name: Build packages
|
- name: Build packages
|
||||||
run: pnpm build:ci
|
run: yarn build:ci
|
||||||
|
|
||||||
- name: Install again
|
- name: Install again
|
||||||
run: pnpm i
|
run: yarn
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm lint
|
run: yarn lint
|
||||||
|
|
||||||
- name: Typecheck with tsc
|
- name: Typecheck with tsc
|
||||||
run: pnpm tsc
|
run: yarn tsc
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
name: ci-nym-wallet-frontend
|
name: ci-nym-wallet-frontend
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'nym-wallet/**'
|
- 'nym-wallet/**'
|
||||||
@@ -13,34 +12,30 @@ 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: pnpm
|
cache: yarn
|
||||||
|
cache-dependency-path: yarn.lock
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: yarn install --network-timeout 100000
|
||||||
|
|
||||||
- name: Build TypeScript packages (wallet depends on @nymproject/types, etc.)
|
- name: Build TypeScript packages (wallet depends on @nymproject/types, etc.)
|
||||||
run: pnpm build:types
|
run: yarn 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: pnpm build:packages
|
run: yarn build:packages
|
||||||
|
|
||||||
- name: Typecheck nym-wallet
|
- name: Typecheck nym-wallet
|
||||||
run: pnpm --filter @nymproject/nym-wallet-app tsc
|
run: yarn --cwd nym-wallet tsc
|
||||||
|
|
||||||
- name: Lint nym-wallet
|
- name: Lint nym-wallet
|
||||||
run: pnpm --filter @nymproject/nym-wallet-app lint
|
run: yarn --cwd nym-wallet lint
|
||||||
|
|
||||||
- name: pnpm audit (workspace lockfile; informational)
|
- name: Yarn audit (workspace lockfile; informational)
|
||||||
run: pnpm audit --audit-level critical
|
run: yarn audit --level critical
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Unit tests (nym-wallet)
|
- name: Unit tests (nym-wallet)
|
||||||
run: pnpm --filter @nymproject/nym-wallet-app test
|
run: yarn --cwd nym-wallet test
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 20
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@@ -30,6 +30,11 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: "1.24.6"
|
||||||
|
|
||||||
- name: Install wasm-pack
|
- name: Install wasm-pack
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
find . -name Cargo.toml -exec cargo deny --manifest-path {} check \
|
find . -name Cargo.toml -exec cargo deny --manifest-path {} check \
|
||||||
advisories -A advisory-not-detected --hide-inclusion-graph \; &> \
|
advisories -A advisory-not-detected --hide-inclusion-graph \; &> \
|
||||||
>(uniq &> .github/workflows/support-files/notifications/deny.message )
|
>(uniq &> .github/workflows/support-files/notifications/deny.message )
|
||||||
- uses: actions/upload-artifact@v7
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: report
|
name: report
|
||||||
path: .github/workflows/support-files/notifications/deny.message
|
path: .github/workflows/support-files/notifications/deny.message
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
args: --workspace --release ${{ env.CARGO_FEATURES }}
|
args: --workspace --release ${{ env.CARGO_FEATURES }}
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: my-artifact
|
name: my-artifact
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ jobs:
|
|||||||
run: make contracts
|
run: make contracts
|
||||||
|
|
||||||
- name: Upload Mixnet Contract Artifact
|
- name: Upload Mixnet Contract Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: mixnet_contract.wasm
|
name: mixnet_contract.wasm
|
||||||
path: contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
|
path: contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
- name: Upload Vesting Contract Artifact
|
- name: Upload Vesting Contract Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: vesting_contract.wasm
|
name: vesting_contract.wasm
|
||||||
path: contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
|
path: contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
|
||||||
|
|||||||
@@ -23,13 +23,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Node
|
||||||
uses: pnpm/action-setup@v5.0.0
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
version: 11.1.2
|
node-version: 22.13.0
|
||||||
- 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
|
||||||
@@ -71,17 +68,17 @@ jobs:
|
|||||||
fileName: '.env'
|
fileName: '.env'
|
||||||
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||||
|
|
||||||
- name: pnpm cache clean
|
- name: Yarn cache clean
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && pnpm cache delete
|
run: cd .. && yarn cache clean
|
||||||
|
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && pnpm i
|
run: cd .. && yarn --network-timeout 100000
|
||||||
|
|
||||||
- name: Build
|
- name: Yarn build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && pnpm build
|
run: cd .. && yarn build
|
||||||
|
|
||||||
- name: Install dependencies and build it
|
- name: Install dependencies and build it
|
||||||
env:
|
env:
|
||||||
@@ -100,7 +97,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: |
|
||||||
pnpm build-macx86
|
yarn build-macx86
|
||||||
|
|
||||||
- name: Create app tarball
|
- name: Create app tarball
|
||||||
run: |
|
run: |
|
||||||
@@ -111,7 +108,7 @@ jobs:
|
|||||||
cd -
|
cd -
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: nym-wallet.app.tar.gz
|
name: nym-wallet.app.tar.gz
|
||||||
path: nym-wallet/target/x86_64-apple-darwin/release/bundle/macos/nym-wallet.app.tar.gz
|
path: nym-wallet/target/x86_64-apple-darwin/release/bundle/macos/nym-wallet.app.tar.gz
|
||||||
|
|||||||
@@ -26,17 +26,12 @@ 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: 24
|
node-version: 22.13.0
|
||||||
cache: 'pnpm'
|
cache: 'yarn'
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -45,10 +40,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && pnpm i
|
run: cd .. && yarn --network-timeout 100000
|
||||||
|
|
||||||
- name: Install app dependencies
|
- name: Install app dependencies
|
||||||
run: pnpm i
|
run: yarn
|
||||||
|
|
||||||
- name: Create env file
|
- name: Create env file
|
||||||
uses: timheuer/base64-to-file@v1.2
|
uses: timheuer/base64-to-file@v1.2
|
||||||
@@ -57,7 +52,7 @@ jobs:
|
|||||||
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: pnpm build
|
run: yarn 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 }}
|
||||||
@@ -137,7 +132,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: nym-wallet-appimage.tar.gz
|
name: nym-wallet-appimage.tar.gz
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -38,15 +38,18 @@ jobs:
|
|||||||
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
|
||||||
|
|
||||||
- name: Setup MSBuild.exe
|
- name: Setup MSBuild.exe
|
||||||
uses: microsoft/setup-msbuild@v3
|
uses: microsoft/setup-msbuild@v2
|
||||||
|
|
||||||
- name: Setup pnpm
|
# No cache:yarn here: setup-node needs yarn on PATH to populate the cache, but this runner
|
||||||
uses: pnpm/action-setup@v5.0.0
|
# only gets yarn from the step below.
|
||||||
|
- name: Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
version: 11.1.2
|
node-version: 22.13.0
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
- name: Install Yarn (classic)
|
||||||
node-version: 24
|
shell: bash
|
||||||
|
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
|
||||||
@@ -115,11 +118,11 @@ jobs:
|
|||||||
' tauri.conf.json
|
' tauri.conf.json
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cd .. && pnpm i
|
run: cd .. && yarn --network-timeout 100000
|
||||||
|
|
||||||
- name: Install app dependencies
|
- name: Install app dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: pnpm i
|
run: yarn --network-timeout 100000
|
||||||
|
|
||||||
- name: Build and sign it
|
- name: Build and sign it
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -133,7 +136,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..."
|
||||||
pnpm build
|
yarn build
|
||||||
|
|
||||||
- name: Check bundle directory
|
- name: Check bundle directory
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -162,7 +165,7 @@ jobs:
|
|||||||
find . -name "*.msi" -type f
|
find . -name "*.msi" -type f
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: nym-wallet.msi
|
name: nym-wallet.msi
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
apk/nyms5-arch64-release.apk
|
apk/nyms5-arch64-release.apk
|
||||||
|
|
||||||
- name: Upload APKs
|
- name: Upload APKs
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: nyms5-apk-arch64
|
name: nyms5-apk-arch64
|
||||||
path: |
|
path: |
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Download binary artifact
|
- name: Download binary artifact
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: nyms5-apk-arch64
|
name: nyms5-apk-arch64
|
||||||
path: apk
|
path: apk
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
name: publish-sdk-npm
|
name: publish-sdk-npm
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
dry_run:
|
|
||||||
description: "Rehearse the publish (pnpm publish --dry-run, no tarballs uploaded). Untick to publish for real."
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
dist_tag:
|
|
||||||
description: "Tag mode. 'auto' picks per package: new packages and same-major releases -> latest; a breaking major (e.g. mix-fetch v2 over v1) -> next, promote later with `npm dist-tag add`. 'next'/'latest' force that tag on all four."
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- auto
|
|
||||||
- next
|
|
||||||
- latest
|
|
||||||
default: auto
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
@@ -21,17 +8,15 @@ 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: 24
|
node-version: 20
|
||||||
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:
|
||||||
@@ -46,15 +31,21 @@ jobs:
|
|||||||
- name: Install wasm-opt
|
- name: Install wasm-opt
|
||||||
run: cargo install wasm-opt
|
run: cargo install wasm-opt
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: "1.24.6"
|
||||||
|
|
||||||
|
- name: Update root CA certificate bundle
|
||||||
|
run: ./wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm i
|
run: yarn
|
||||||
|
|
||||||
- name: Build WASM and Typescript SDK
|
- name: Build WASM and Typescript SDK
|
||||||
run: pnpm sdk:build
|
run: yarn sdk:build
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||||
DRY_RUN: ${{ inputs.dry_run && '1' || '0' }}
|
|
||||||
NPM_DIST_TAG: ${{ inputs.dist_tag }}
|
|
||||||
run: ./sdk/typescript/scripts/publish.sh
|
run: ./sdk/typescript/scripts/publish.sh
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: arc-linux-latest-dind
|
runs-on: arc-linux-latest-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: arc-linux-latest-dind
|
runs-on: arc-linux-latest-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-22.04-dind
|
runs-on: arc-ubuntu-22.04-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: arc-linux-latest-dind
|
runs-on: arc-linux-latest-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: arc-linux-latest-dind
|
runs-on: arc-linux-latest-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-22.04-dind
|
runs-on: arc-ubuntu-22.04-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-22.04-dind
|
runs-on: arc-ubuntu-22.04-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: arc-linux-latest-dind
|
runs-on: arc-linux-latest-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-22.04-dind
|
runs-on: arc-ubuntu-22.04-dind
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Harbor
|
- name: Login to Harbor
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: harbor.nymte.ch
|
registry: harbor.nymte.ch
|
||||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 20
|
||||||
- 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 }}
|
||||||
with:
|
with:
|
||||||
release-tag-or-name-or-id: ${{ inputs.release_tag }}
|
release-tag-or-name-or-id: ${{ inputs.release_tag }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v7
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: Asset Hashes
|
name: Asset Hashes
|
||||||
path: hashes.json
|
path: hashes.json
|
||||||
|
|||||||
@@ -78,11 +78,3 @@ CLAUDE.md
|
|||||||
|
|
||||||
/notes
|
/notes
|
||||||
/target-otel
|
/target-otel
|
||||||
test-tutorials/
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
.pnpm-store/
|
|
||||||
|
|
||||||
tmp/
|
|
||||||
# operator tools
|
|
||||||
scripts/nym-node-setup/auto-bond/nodes.csv
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -4,94 +4,6 @@ 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)
|
## [2026.10-waterloo] (2026-05-27)
|
||||||
|
|
||||||
- Re-order default API urls for network details - Waterloo release ([#6799])
|
- Re-order default API urls for network details - Waterloo release ([#6799])
|
||||||
|
|||||||
Generated
+2171
-2295
File diff suppressed because it is too large
Load Diff
+153
-151
@@ -31,6 +31,7 @@ members = [
|
|||||||
"common/client-libs/mixnet-client",
|
"common/client-libs/mixnet-client",
|
||||||
"common/client-libs/validator-client",
|
"common/client-libs/validator-client",
|
||||||
"common/commands",
|
"common/commands",
|
||||||
|
"common/nym-common",
|
||||||
"common/config",
|
"common/config",
|
||||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||||
"common/cosmwasm-smart-contracts/contracts-common",
|
"common/cosmwasm-smart-contracts/contracts-common",
|
||||||
@@ -40,7 +41,6 @@ members = [
|
|||||||
"common/cosmwasm-smart-contracts/group-contract",
|
"common/cosmwasm-smart-contracts/group-contract",
|
||||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||||
"common/cosmwasm-smart-contracts/multisig-contract",
|
"common/cosmwasm-smart-contracts/multisig-contract",
|
||||||
"common/cosmwasm-smart-contracts/node-families-contract",
|
|
||||||
"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",
|
||||||
@@ -71,15 +71,11 @@ members = [
|
|||||||
"common/node-tester-utils",
|
"common/node-tester-utils",
|
||||||
"common/nonexhaustive-delayqueue",
|
"common/nonexhaustive-delayqueue",
|
||||||
"common/nym-cache",
|
"common/nym-cache",
|
||||||
"common/nym-common",
|
|
||||||
"common/nym-connection-monitor",
|
"common/nym-connection-monitor",
|
||||||
"common/nym-id",
|
"common/nym-id",
|
||||||
"common/nym-kcp",
|
"common/nym-kcp",
|
||||||
"common/nym-kkt",
|
|
||||||
"common/nym-kkt-ciphersuite",
|
|
||||||
"common/nym-kkt-context",
|
|
||||||
"common/nym-lp",
|
"common/nym-lp",
|
||||||
"common/nym-lp-data",
|
"common/nym-kkt",
|
||||||
"common/nym-metrics",
|
"common/nym-metrics",
|
||||||
"common/nym_offline_compact_ecash",
|
"common/nym_offline_compact_ecash",
|
||||||
"common/nymnoise",
|
"common/nymnoise",
|
||||||
@@ -95,9 +91,9 @@ members = [
|
|||||||
"common/nymsphinx/params",
|
"common/nymsphinx/params",
|
||||||
"common/nymsphinx/routing",
|
"common/nymsphinx/routing",
|
||||||
"common/nymsphinx/types",
|
"common/nymsphinx/types",
|
||||||
|
"common/nyxd-scraper-sqlite",
|
||||||
"common/nyxd-scraper-psql",
|
"common/nyxd-scraper-psql",
|
||||||
"common/nyxd-scraper-shared",
|
"common/nyxd-scraper-shared",
|
||||||
"common/nyxd-scraper-sqlite",
|
|
||||||
"common/pemstore",
|
"common/pemstore",
|
||||||
"common/registration",
|
"common/registration",
|
||||||
"common/serde-helpers",
|
"common/serde-helpers",
|
||||||
@@ -127,14 +123,13 @@ members = [
|
|||||||
"common/zulip-client",
|
"common/zulip-client",
|
||||||
"documentation/autodoc",
|
"documentation/autodoc",
|
||||||
"gateway",
|
"gateway",
|
||||||
"integration-tests",
|
|
||||||
"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",
|
||||||
"nym-gateway-probe",
|
|
||||||
"nym-ip-packet-client",
|
"nym-ip-packet-client",
|
||||||
"nym-network-monitor",
|
"nym-network-monitor",
|
||||||
"nym-node",
|
"nym-node",
|
||||||
@@ -146,7 +141,6 @@ members = [
|
|||||||
"nym-outfox",
|
"nym-outfox",
|
||||||
"nym-registration-client",
|
"nym-registration-client",
|
||||||
"nym-signers-monitor",
|
"nym-signers-monitor",
|
||||||
"nym-sqlx-pool-guard",
|
|
||||||
"nym-statistics-api",
|
"nym-statistics-api",
|
||||||
"nym-validator-rewarder",
|
"nym-validator-rewarder",
|
||||||
"nyx-chain-watcher",
|
"nyx-chain-watcher",
|
||||||
@@ -154,18 +148,19 @@ members = [
|
|||||||
"sdk/ffi/go",
|
"sdk/ffi/go",
|
||||||
"sdk/ffi/shared",
|
"sdk/ffi/shared",
|
||||||
"sdk/rust/nym-sdk",
|
"sdk/rust/nym-sdk",
|
||||||
|
"smolmix/core",
|
||||||
"service-providers/common",
|
"service-providers/common",
|
||||||
"service-providers/ip-packet-router",
|
"service-providers/ip-packet-router",
|
||||||
"service-providers/network-requester",
|
"service-providers/network-requester",
|
||||||
"smolmix/core",
|
"nym-sqlx-pool-guard",
|
||||||
"tools/echo-server",
|
"tools/echo-server",
|
||||||
"tools/internal/contract-state-importer/importer-cli",
|
"tools/internal/contract-state-importer/importer-cli",
|
||||||
"tools/internal/contract-state-importer/importer-contract",
|
"tools/internal/contract-state-importer/importer-contract",
|
||||||
"tools/internal/localnet-orchestrator",
|
|
||||||
"tools/internal/localnet-orchestrator/dkg-bypass-contract",
|
|
||||||
"tools/internal/mixnet-connectivity-check",
|
"tools/internal/mixnet-connectivity-check",
|
||||||
# "tools/internal/sdk-version-bump",
|
# "tools/internal/sdk-version-bump",
|
||||||
"tools/internal/ssl-inject",
|
"tools/internal/ssl-inject",
|
||||||
|
"tools/internal/localnet-orchestrator",
|
||||||
|
"tools/internal/localnet-orchestrator/dkg-bypass-contract",
|
||||||
"tools/internal/validator-status-check",
|
"tools/internal/validator-status-check",
|
||||||
"tools/nym-cli",
|
"tools/nym-cli",
|
||||||
"tools/nym-id-cli",
|
"tools/nym-id-cli",
|
||||||
@@ -174,30 +169,35 @@ members = [
|
|||||||
"tools/nymvisor",
|
"tools/nymvisor",
|
||||||
"tools/ts-rs-cli",
|
"tools/ts-rs-cli",
|
||||||
"wasm/client",
|
"wasm/client",
|
||||||
"wasm/smolmix",
|
# "wasm/full-nym-wasm", # If we uncomment this again, remember to also uncomment the profile settings below
|
||||||
|
"wasm/mix-fetch",
|
||||||
|
"wasm/node-tester",
|
||||||
"wasm/zknym-lib",
|
"wasm/zknym-lib",
|
||||||
|
"nym-gateway-probe",
|
||||||
|
"integration-tests",
|
||||||
|
"common/nym-kkt-ciphersuite",
|
||||||
|
"common/nym-kkt-context",
|
||||||
"nym-network-monitor-v3/nym-network-monitor-orchestrator",
|
"nym-network-monitor-v3/nym-network-monitor-orchestrator",
|
||||||
"nym-network-monitor-v3/nym-network-monitor-agent",
|
"nym-network-monitor-v3/nym-network-monitor-agent", "nym-network-monitor-v3/nym-network-monitor-orchestrator-requests",
|
||||||
"nym-network-monitor-v3/nym-network-monitor-orchestrator-requests",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
default-members = [
|
default-members = [
|
||||||
"clients/native",
|
"clients/native",
|
||||||
"clients/socks5",
|
"clients/socks5",
|
||||||
"nym-api",
|
|
||||||
"nym-authenticator-client",
|
"nym-authenticator-client",
|
||||||
|
"nym-api",
|
||||||
"nym-credential-proxy/nym-credential-proxy",
|
"nym-credential-proxy/nym-credential-proxy",
|
||||||
"nym-node",
|
"nym-node",
|
||||||
"nym-registration-client",
|
|
||||||
"nym-statistics-api",
|
"nym-statistics-api",
|
||||||
"nym-validator-rewarder",
|
"nym-validator-rewarder",
|
||||||
"nyx-chain-watcher",
|
"nyx-chain-watcher",
|
||||||
"service-providers/ip-packet-router",
|
"service-providers/ip-packet-router",
|
||||||
"service-providers/network-requester",
|
"service-providers/network-requester",
|
||||||
"tools/internal/localnet-orchestrator",
|
|
||||||
"tools/nymvisor",
|
"tools/nymvisor",
|
||||||
|
"nym-registration-client",
|
||||||
"nym-network-monitor-v3/nym-network-monitor-orchestrator",
|
"nym-network-monitor-v3/nym-network-monitor-orchestrator",
|
||||||
"nym-network-monitor-v3/nym-network-monitor-agent",
|
"nym-network-monitor-v3/nym-network-monitor-agent",
|
||||||
|
"tools/internal/localnet-orchestrator"
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = ["contracts", "nym-wallet", "cpu-cycles"]
|
exclude = ["contracts", "nym-wallet", "cpu-cycles"]
|
||||||
@@ -211,7 +211,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.1"
|
version = "1.20.4"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
addr = "0.15.6"
|
addr = "0.15.6"
|
||||||
@@ -225,17 +225,16 @@ 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"
|
||||||
async-tungstenite = { version = "0.24", default-features = false }
|
axum = "0.7.5"
|
||||||
axum = "0.8.9"
|
axum-client-ip = "0.6.1"
|
||||||
axum-client-ip = "1.3.1"
|
axum-extra = "0.9.4"
|
||||||
axum-extra = "0.12.6"
|
axum-test = "16.2.0"
|
||||||
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"
|
||||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||||
bitvec = "1.0.0"
|
bitvec = "1.0.0"
|
||||||
blake3 = ">=1.7, <1.8.4" # blake3 1.8.4+ requires digest 0.11; workspace is on 0.10
|
blake3 = "1.7.0"
|
||||||
bloomfilter = "3.0.1"
|
bloomfilter = "3.0.1"
|
||||||
bs58 = "0.5.1"
|
bs58 = "0.5.1"
|
||||||
bytecodec = "0.4.15"
|
bytecodec = "0.4.15"
|
||||||
@@ -254,7 +253,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.5.0"
|
console-subscriber = "0.4.1"
|
||||||
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"
|
||||||
@@ -279,27 +278,24 @@ 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 = { version = "0.26.1", default-features = false }
|
hickory-proto = "0.26.1"
|
||||||
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 = { version = "1.6.0", default-features = false }
|
hyper = "1.6.0"
|
||||||
hyper-util = "0.1"
|
hyper-util = "0.1"
|
||||||
indicatif = "0.18.0"
|
indicatif = "0.18.0"
|
||||||
inquire = "0.6.2"
|
inquire = "0.6.2"
|
||||||
@@ -334,7 +330,7 @@ pnet_packet = "0.35.0"
|
|||||||
publicsuffix = "2.3.0"
|
publicsuffix = "2.3.0"
|
||||||
proc_pidinfo = "0.1.3"
|
proc_pidinfo = "0.1.3"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
rand = "0.8.6"
|
rand = "0.8.5"
|
||||||
rand09 = { package = "rand", version = "=0.9.2" }
|
rand09 = { package = "rand", version = "=0.9.2" }
|
||||||
rand_chacha = "0.3"
|
rand_chacha = "0.3"
|
||||||
rand_chacha09 = { package = "rand_chacha", version = "=0.9.0" }
|
rand_chacha09 = { package = "rand_chacha", version = "=0.9.0" }
|
||||||
@@ -345,14 +341,12 @@ 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 = { version = "1.0.140", features = ["float_roundtrip"] }
|
serde_json = "1.0.140"
|
||||||
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"
|
||||||
@@ -360,7 +354,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"
|
smolmix = { version = "0.0.1", path = "smolmix/core" }
|
||||||
smoltcp = "0.12"
|
smoltcp = "0.12"
|
||||||
snow = "0.9.6"
|
snow = "0.9.6"
|
||||||
sphinx-packet = "=0.6.0"
|
sphinx-packet = "=0.6.0"
|
||||||
@@ -369,9 +363,9 @@ strum = "0.28.0"
|
|||||||
strum_macros = "0.28.0"
|
strum_macros = "0.28.0"
|
||||||
subtle-encoding = "0.5"
|
subtle-encoding = "0.5"
|
||||||
syn = "2"
|
syn = "2"
|
||||||
sysinfo = "0.38.4"
|
sysinfo = "0.37.0"
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
tar = "0.4.45"
|
tar = "0.4.44"
|
||||||
test-with = { version = "0.15.4", default-features = false }
|
test-with = { version = "0.15.4", default-features = false }
|
||||||
tempfile = "3.20"
|
tempfile = "3.20"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
@@ -384,7 +378,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 = "0.20.1"
|
tokio-tungstenite = { version = "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"
|
||||||
@@ -402,10 +396,11 @@ 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 = "9.0.2"
|
utoipa-swagger-ui = "8.1"
|
||||||
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"
|
||||||
@@ -428,115 +423,111 @@ 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.1", path = "nym-api/nym-api-requests" }
|
nym-api-requests = { version = "1.20.4", path = "nym-api/nym-api-requests" }
|
||||||
nym-authenticator-requests = { version = "1.21.1", path = "common/authenticator-requests" }
|
nym-authenticator-requests = { version = "1.20.4", path = "common/authenticator-requests" }
|
||||||
nym-async-file-watcher = { version = "1.21.1", path = "common/async-file-watcher" }
|
nym-async-file-watcher = { version = "1.20.4", path = "common/async-file-watcher" }
|
||||||
nym-authenticator-client = { version = "1.21.1", path = "nym-authenticator-client" }
|
nym-authenticator-client = { version = "1.20.4", path = "nym-authenticator-client" }
|
||||||
nym-bandwidth-controller = { version = "1.21.1", path = "common/bandwidth-controller" }
|
nym-bandwidth-controller = { version = "1.20.4", path = "common/bandwidth-controller" }
|
||||||
nym-bin-common = { version = "1.21.1", path = "common/bin-common" }
|
nym-bin-common = { version = "1.20.4", path = "common/bin-common" }
|
||||||
nym-cache = { version = "1.21.1", path = "common/nym-cache" }
|
nym-cache = { version = "1.20.4", path = "common/nym-cache" }
|
||||||
nym-client-core = { version = "1.21.1", path = "common/client-core", default-features = false }
|
nym-client-core = { version = "1.20.4", path = "common/client-core", default-features = false }
|
||||||
nym-client-core-config-types = { version = "1.21.1", path = "common/client-core/config-types" }
|
nym-client-core-config-types = { version = "1.20.4", path = "common/client-core/config-types" }
|
||||||
nym-client-core-gateways-storage = { version = "1.21.1", path = "common/client-core/gateways-storage" }
|
nym-client-core-gateways-storage = { version = "1.20.4", path = "common/client-core/gateways-storage" }
|
||||||
nym-client-core-surb-storage = { version = "1.21.1", path = "common/client-core/surb-storage" }
|
nym-client-core-surb-storage = { version = "1.20.4", path = "common/client-core/surb-storage" }
|
||||||
nym-client-websocket-requests = { version = "1.21.1", path = "clients/native/websocket-requests" }
|
nym-client-websocket-requests = { version = "1.20.4", path = "clients/native/websocket-requests" }
|
||||||
nym-common = { version = "1.21.1", path = "common/nym-common" }
|
nym-common = { version = "1.20.4", path = "common/nym-common" }
|
||||||
nym-compact-ecash = { version = "1.21.1", path = "common/nym_offline_compact_ecash" }
|
nym-compact-ecash = { version = "1.20.4", path = "common/nym_offline_compact_ecash" }
|
||||||
nym-config = { version = "1.21.1", path = "common/config" }
|
nym-config = { version = "1.20.4", path = "common/config" }
|
||||||
nym-contracts-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/contracts-common" }
|
nym-contracts-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/contracts-common" }
|
||||||
nym-coconut-dkg-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/coconut-dkg" }
|
nym-coconut-dkg-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||||
nym-credential-storage = { version = "1.21.1", path = "common/credential-storage" }
|
nym-credential-storage = { version = "1.20.4", path = "common/credential-storage" }
|
||||||
nym-credential-utils = { version = "1.21.1", path = "common/credential-utils" }
|
nym-credential-utils = { version = "1.20.4", path = "common/credential-utils" }
|
||||||
nym-credential-proxy-lib = { version = "1.21.1", path = "common/credential-proxy" }
|
nym-credential-proxy-lib = { version = "1.20.4", path = "common/credential-proxy" }
|
||||||
nym-credentials = { version = "1.21.1", path = "common/credentials", default-features = false }
|
nym-credentials = { version = "1.20.4", path = "common/credentials", default-features = false }
|
||||||
nym-credentials-interface = { version = "1.21.1", path = "common/credentials-interface" }
|
nym-credentials-interface = { version = "1.20.4", path = "common/credentials-interface" }
|
||||||
nym-credential-proxy-requests = { version = "1.21.1", path = "nym-credential-proxy/nym-credential-proxy-requests", default-features = false }
|
nym-credential-proxy-requests = { version = "1.20.4", path = "nym-credential-proxy/nym-credential-proxy-requests", default-features = false }
|
||||||
nym-credential-verification = { version = "1.21.1", path = "common/credential-verification" }
|
nym-credential-verification = { version = "1.20.4", path = "common/credential-verification" }
|
||||||
nym-crypto = { version = "1.21.1", path = "common/crypto", default-features = false }
|
nym-crypto = { version = "1.20.4", path = "common/crypto", default-features = false }
|
||||||
nym-dkg = { version = "1.21.1", path = "common/dkg" }
|
nym-dkg = { version = "1.20.4", path = "common/dkg" }
|
||||||
nym-ecash-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/ecash-contract" }
|
nym-ecash-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/ecash-contract" }
|
||||||
nym-ecash-signer-check = { version = "1.21.1", path = "common/ecash-signer-check" }
|
nym-ecash-signer-check = { version = "1.20.4", path = "common/ecash-signer-check" }
|
||||||
nym-ecash-signer-check-types = { version = "1.21.1", path = "common/ecash-signer-check-types" }
|
nym-ecash-signer-check-types = { version = "1.20.4", path = "common/ecash-signer-check-types" }
|
||||||
nym-ecash-time = { version = "1.21.1", path = "common/ecash-time" }
|
nym-ecash-time = { version = "1.20.4", path = "common/ecash-time" }
|
||||||
nym-exit-policy = { version = "1.21.1", path = "common/exit-policy" }
|
nym-exit-policy = { version = "1.20.4", path = "common/exit-policy" }
|
||||||
nym-ffi-shared = { version = "1.21.1", path = "sdk/ffi/shared" }
|
nym-ffi-shared = { version = "1.20.4", path = "sdk/ffi/shared" }
|
||||||
nym-gateway-client = { version = "1.21.1", path = "common/client-libs/gateway-client", default-features = false }
|
nym-gateway-client = { version = "1.20.4", path = "common/client-libs/gateway-client", default-features = false }
|
||||||
nym-gateway-probe = { version = "1.21.1", path = "nym-gateway-probe" }
|
nym-gateway-probe = { version = "1.18.0", path = "nym-gateway-probe" }
|
||||||
nym-gateway-requests = { version = "1.21.1", path = "common/gateway-requests" }
|
nym-gateway-requests = { version = "1.20.4", path = "common/gateway-requests" }
|
||||||
nym-gateway-storage = { version = "1.21.1", path = "common/gateway-storage" }
|
nym-gateway-storage = { version = "1.20.4", path = "common/gateway-storage" }
|
||||||
nym-gateway-stats-storage = { version = "1.21.1", path = "common/gateway-stats-storage" }
|
nym-gateway-stats-storage = { version = "1.20.4", path = "common/gateway-stats-storage" }
|
||||||
nym-group-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/group-contract" }
|
nym-group-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/group-contract" }
|
||||||
nym-http-api-client = { version = "1.21.1", path = "common/http-api-client" }
|
nym-http-api-client = { version = "1.20.4", path = "common/http-api-client" }
|
||||||
nym-http-api-client-macro = { version = "1.21.1", path = "common/http-api-client-macro" }
|
nym-http-api-client-macro = { version = "1.20.4", path = "common/http-api-client-macro" }
|
||||||
nym-http-api-common = { version = "1.21.1", path = "common/http-api-common", default-features = false }
|
nym-http-api-common = { version = "1.20.4", path = "common/http-api-common", default-features = false }
|
||||||
nym-id = { version = "1.21.1", path = "common/nym-id" }
|
nym-id = { version = "1.20.4", path = "common/nym-id" }
|
||||||
nym-ip-packet-client = { version = "1.21.1", path = "nym-ip-packet-client" }
|
nym-ip-packet-client = { version = "1.20.4", path = "nym-ip-packet-client" }
|
||||||
nym-ip-packet-requests = { version = "1.21.1", path = "common/ip-packet-requests" }
|
nym-ip-packet-requests = { version = "1.20.4", path = "common/ip-packet-requests" }
|
||||||
nym-lp = { version = "1.21.1", path = "common/nym-lp" }
|
nym-lp = { version = "1.20.4", path = "common/nym-lp" }
|
||||||
nym-lp-data = { version = "1.21.1", path = "common/nym-lp-data" }
|
nym-kkt = { version = "0.1.0", path = "common/nym-kkt" }
|
||||||
nym-kkt = { version = "1.21.1", path = "common/nym-kkt" }
|
nym-kkt-ciphersuite = { version = "1.20.4", path = "common/nym-kkt-ciphersuite" }
|
||||||
nym-kkt-ciphersuite = { version = "1.21.1", path = "common/nym-kkt-ciphersuite" }
|
nym-kkt-context = { version = "1.20.4", path = "common/nym-kkt-context" }
|
||||||
nym-kkt-context = { version = "1.21.1", path = "common/nym-kkt-context" }
|
nym-metrics = { version = "1.20.4", path = "common/nym-metrics" }
|
||||||
nym-metrics = { version = "1.21.1", path = "common/nym-metrics" }
|
nym-mixnet-client = { version = "1.20.4", path = "common/client-libs/mixnet-client" }
|
||||||
nym-mixnet-client = { version = "1.21.1", path = "common/client-libs/mixnet-client" }
|
nym-mixnet-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||||
nym-mixnet-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/mixnet-contract" }
|
nym-multisig-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/multisig-contract" }
|
||||||
nym-multisig-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/multisig-contract" }
|
nym-network-defaults = { version = "1.20.4", path = "common/network-defaults" }
|
||||||
nym-network-defaults = { version = "1.21.1", path = "common/network-defaults" }
|
nym-node-tester-utils = { version = "1.20.4", path = "common/node-tester-utils" }
|
||||||
nym-node-tester-utils = { version = "1.21.1", path = "common/node-tester-utils" }
|
nym-noise = { version = "1.20.4", path = "common/nymnoise" }
|
||||||
nym-noise = { version = "1.21.1", path = "common/nymnoise" }
|
nym-noise-keys = { version = "1.20.4", path = "common/nymnoise/keys" }
|
||||||
nym-noise-keys = { version = "1.21.1", path = "common/nymnoise/keys" }
|
nym-nonexhaustive-delayqueue = { version = "1.20.4", path = "common/nonexhaustive-delayqueue" }
|
||||||
nym-nonexhaustive-delayqueue = { version = "1.21.1", path = "common/nonexhaustive-delayqueue" }
|
nym-node-requests = { version = "1.20.4", path = "nym-node/nym-node-requests", default-features = false }
|
||||||
nym-node-requests = { version = "1.21.1", path = "nym-node/nym-node-requests", default-features = false }
|
nym-node-metrics = { version = "1.20.4", path = "nym-node/nym-node-metrics" }
|
||||||
nym-node-metrics = { version = "1.21.1", path = "nym-node/nym-node-metrics" }
|
nym-ordered-buffer = { version = "1.20.4", 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.20.4", path = "nym-outfox" }
|
||||||
nym-ordered-buffer = { version = "1.21.1", path = "common/socks5/ordered-buffer" }
|
nym-registration-common = { version = "1.20.4", path = "common/registration" }
|
||||||
nym-outfox = { version = "1.21.1", path = "nym-outfox" }
|
nym-pemstore = { version = "1.20.4", path = "common/pemstore" }
|
||||||
nym-registration-common = { version = "1.21.1", path = "common/registration" }
|
nym-performance-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/nym-performance-contract" }
|
||||||
nym-pemstore = { version = "1.21.1", path = "common/pemstore" }
|
nym-sdk = { version = "1.20.4", 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.20.4", path = "common/serde-helpers" }
|
||||||
nym-sdk = { version = "1.21.1", path = "sdk/rust/nym-sdk" }
|
nym-service-providers-common = { version = "1.20.4", path = "service-providers/common" }
|
||||||
nym-serde-helpers = { version = "1.21.1", path = "common/serde-helpers" }
|
nym-service-provider-requests-common = { version = "1.20.4", path = "common/service-provider-requests-common" }
|
||||||
nym-service-providers-common = { version = "1.21.1", path = "service-providers/common" }
|
nym-socks5-client-core = { version = "1.20.4", 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.20.4", path = "common/socks5/proxy-helpers" }
|
||||||
nym-socks5-client-core = { version = "1.21.1", path = "common/socks5-client-core" }
|
nym-socks5-requests = { version = "1.20.4", path = "common/socks5/requests" }
|
||||||
nym-socks5-proxy-helpers = { version = "1.21.1", path = "common/socks5/proxy-helpers" }
|
nym-sphinx = { version = "1.20.4", path = "common/nymsphinx" }
|
||||||
nym-socks5-requests = { version = "1.21.1", path = "common/socks5/requests" }
|
nym-sphinx-acknowledgements = { version = "1.20.4", path = "common/nymsphinx/acknowledgements" }
|
||||||
nym-sphinx = { version = "1.21.1", path = "common/nymsphinx" }
|
nym-sphinx-addressing = { version = "1.20.4", path = "common/nymsphinx/addressing" }
|
||||||
nym-sphinx-acknowledgements = { version = "1.21.1", path = "common/nymsphinx/acknowledgements" }
|
nym-sphinx-anonymous-replies = { version = "1.20.4", path = "common/nymsphinx/anonymous-replies" }
|
||||||
nym-sphinx-addressing = { version = "1.21.1", path = "common/nymsphinx/addressing" }
|
nym-sphinx-chunking = { version = "1.20.4", path = "common/nymsphinx/chunking" }
|
||||||
nym-sphinx-anonymous-replies = { version = "1.21.1", path = "common/nymsphinx/anonymous-replies" }
|
nym-sphinx-cover = { version = "1.20.4", path = "common/nymsphinx/cover" }
|
||||||
nym-sphinx-chunking = { version = "1.21.1", path = "common/nymsphinx/chunking" }
|
nym-sphinx-forwarding = { version = "1.20.4", path = "common/nymsphinx/forwarding" }
|
||||||
nym-sphinx-cover = { version = "1.21.1", path = "common/nymsphinx/cover" }
|
nym-sphinx-framing = { version = "1.20.4", path = "common/nymsphinx/framing" }
|
||||||
nym-sphinx-forwarding = { version = "1.21.1", path = "common/nymsphinx/forwarding" }
|
nym-sphinx-params = { version = "1.20.4", path = "common/nymsphinx/params" }
|
||||||
nym-sphinx-framing = { version = "1.21.1", path = "common/nymsphinx/framing" }
|
nym-sphinx-routing = { version = "1.20.4", path = "common/nymsphinx/routing" }
|
||||||
nym-sphinx-params = { version = "1.21.1", path = "common/nymsphinx/params" }
|
nym-sphinx-types = { version = "1.20.4", path = "common/nymsphinx/types" }
|
||||||
nym-sphinx-routing = { version = "1.21.1", path = "common/nymsphinx/routing" }
|
nym-statistics-common = { version = "1.20.4", path = "common/statistics" }
|
||||||
nym-sphinx-types = { version = "1.21.1", path = "common/nymsphinx/types" }
|
nym-store-cipher = { version = "1.20.4", path = "common/store-cipher" }
|
||||||
nym-statistics-common = { version = "1.21.1", path = "common/statistics" }
|
nym-task = { version = "1.20.4", path = "common/task" }
|
||||||
nym-store-cipher = { version = "1.21.1", path = "common/store-cipher" }
|
nym-tun = { version = "1.20.4", path = "common/tun" }
|
||||||
nym-task = { version = "1.21.1", path = "common/task" }
|
nym-test-utils = { version = "1.20.4", path = "common/test-utils" }
|
||||||
nym-tun = { version = "1.21.1", path = "common/tun" }
|
nym-ticketbooks-merkle = { version = "1.20.4", path = "common/ticketbooks-merkle" }
|
||||||
nym-test-utils = { version = "1.21.1", path = "common/test-utils" }
|
nym-topology = { version = "1.20.4", path = "common/topology" }
|
||||||
nym-ticketbooks-merkle = { version = "1.21.1", path = "common/ticketbooks-merkle" }
|
nym-types = { version = "1.20.4", path = "common/types" }
|
||||||
nym-topology = { version = "1.21.1", path = "common/topology" }
|
nym-upgrade-mode-check = { version = "1.20.4", path = "common/upgrade-mode-check" }
|
||||||
nym-types = { version = "1.21.1", path = "common/types" }
|
nym-validator-client = { version = "1.20.4", 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.20.4", path = "common/cosmwasm-smart-contracts/vesting-contract" }
|
||||||
nym-validator-client = { version = "1.21.1", path = "common/client-libs/validator-client", default-features = false }
|
nym-network-monitors-contract-common = { version = "1.20.4", path = "common/cosmwasm-smart-contracts/network-monitors-contract" }
|
||||||
nym-vesting-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/vesting-contract" }
|
nym-verloc = { version = "1.20.4", path = "common/verloc" }
|
||||||
nym-network-monitors-contract-common = { version = "1.21.1", path = "common/cosmwasm-smart-contracts/network-monitors-contract" }
|
nym-wireguard = { version = "1.20.4", path = "common/wireguard" }
|
||||||
nym-verloc = { version = "1.21.1", path = "common/verloc" }
|
nym-wireguard-types = { version = "1.20.4", path = "common/wireguard-types" }
|
||||||
nym-wireguard = { version = "1.21.1", path = "common/wireguard" }
|
nym-wireguard-private-metadata-shared = { version = "1.20.4", path = "common/wireguard-private-metadata/shared" }
|
||||||
nym-wireguard-types = { version = "1.21.1", path = "common/wireguard-types" }
|
nym-wireguard-private-metadata-client = { version = "1.20.4", path = "common/wireguard-private-metadata/client" }
|
||||||
nym-wireguard-private-metadata-shared = { version = "1.21.1", path = "common/wireguard-private-metadata/shared" }
|
nym-wireguard-private-metadata-server = { version = "1.20.4", path = "common/wireguard-private-metadata/server" }
|
||||||
nym-wireguard-private-metadata-client = { version = "1.21.1", path = "common/wireguard-private-metadata/client" }
|
nym-sqlx-pool-guard = { version = "1.2.0", path = "nym-sqlx-pool-guard" }
|
||||||
nym-wireguard-private-metadata-server = { version = "1.21.1", path = "common/wireguard-private-metadata/server" }
|
nym-wasm-client-core = { version = "1.20.4", path = "common/wasm/client-core" }
|
||||||
nym-sqlx-pool-guard = { version = "1.21.1", path = "nym-sqlx-pool-guard" }
|
nym-wasm-storage = { version = "1.20.4", path = "common/wasm/storage" }
|
||||||
nym-wasm-client-core = { version = "1.21.1", path = "common/wasm/client-core" }
|
nym-wasm-utils = { version = "1.20.4", path = "common/wasm/utils", default-features = false }
|
||||||
nym-wasm-storage = { version = "1.21.1", path = "common/wasm/storage" }
|
nyxd-scraper-shared = { version = "1.20.4", path = "common/nyxd-scraper-shared" }
|
||||||
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.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
|
||||||
@@ -603,7 +594,18 @@ opt-level = 3
|
|||||||
# lto = true
|
# lto = true
|
||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
|
|
||||||
[profile.release.package.smolmix-wasm]
|
[profile.release.package.nym-node-tester-wasm]
|
||||||
|
# lto = true
|
||||||
|
opt-level = 'z'
|
||||||
|
|
||||||
|
# Commented out since the crate is also commented out from the inclusion in the
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
|||||||
@@ -104,30 +104,30 @@ $(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/smolmix
|
$(MAKE) -C wasm/node-tester
|
||||||
|
$(MAKE) -C wasm/mix-fetch
|
||||||
# $(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
|
||||||
#
|
|
||||||
# `mix-tunnel` must build before the three feature packages — they import it
|
|
||||||
# via `workspace:*` and the lerna topological sort will respect that as long
|
|
||||||
# as we keep them in the same `--scope` invocation.
|
|
||||||
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-tunnel,@nymproject/mix-fetch,@nymproject/mix-dns,@nymproject/mix-websocket}' build --stream
|
npx lerna run --scope @nymproject/mix-fetch build --stream
|
||||||
pnpm --pwd sdk/typescript/codegen/contract-clients build
|
npx lerna run --scope @nymproject/node-tester build --stream
|
||||||
|
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
|
WASM_CRATES = nym-client-wasm nym-node-tester-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
|
||||||
|
|
||||||
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/smolmix check-fmt
|
$(MAKE) -C wasm/mix-fetch check-fmt
|
||||||
|
|
||||||
# Add to top-level targets
|
# Add to top-level targets
|
||||||
build: sdk-wasm-build
|
build: sdk-wasm-build
|
||||||
@@ -223,7 +223,7 @@ build-nym-cli:
|
|||||||
|
|
||||||
generate-typescript:
|
generate-typescript:
|
||||||
cd tools/ts-rs-cli && cargo run && cd ../..
|
cd tools/ts-rs-cli && cargo run && cd ../..
|
||||||
pnpm types:lint:fix
|
yarn 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
|
||||||
pnpm install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Nym node auto-bonding
|
|
||||||
hosts: all
|
|
||||||
gather_facts: false
|
|
||||||
serial: 1
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- role: postinstall-auto
|
|
||||||
@@ -1,4 +1,21 @@
|
|||||||
---
|
---
|
||||||
|
ansible_ssh_private_key_file: ~/.ssh/<SSH_KEY>
|
||||||
|
|
||||||
|
cli_url: "https://github.com/nymtech/nym/releases/download/nym-binaries-{{ nym_version }}/nym-cli"
|
||||||
|
tunnel_manager_url: "https://github.com/nymtech/nym/raw/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh"
|
||||||
|
quic_bridge_deployment_url: "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## GLOBAL VARS
|
||||||
|
## These values will be used globally unless overwritten per node in inventory/all
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
ansible_user: root # used for ssh, like `ssh root@nym-exit.ch-1.mynodes.net`
|
||||||
|
email: "<EMAIL>" # used in certbot, description.toml and landing page
|
||||||
|
website: "<WEBSITE>" # it is used in the description.toml
|
||||||
|
description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
|
||||||
|
# operator_name: "<OPERATOR_NAME>" # used in landing page if provided
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
## GLOBAL VARS
|
## GLOBAL VARS
|
||||||
## These values will be used globally unless overwritten per node in inventory/all
|
## These values will be used globally unless overwritten per node in inventory/all
|
||||||
@@ -6,41 +23,16 @@
|
|||||||
## Per node changes in inventory/all will overwrite these global vars
|
## Per node changes in inventory/all will overwrite these global vars
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
## MANDATORY - uncomment & define
|
# moniker: "<MONIKER>" # if not setup here not in inventory/all it get's derived from the hostname
|
||||||
|
# mode: <MODE> # entry-gateway/exit-gateway/mixnode
|
||||||
## --SSH--
|
# wireguard_enabled: <WIREGUARD_ENABLED> # true/false
|
||||||
#ansible_user: root # used for ssh, like `ssh root@nym-exit.ch-1.mynodes.net`
|
hostname: "" # this is a fallback, keep it and setup hostname per node in inventory/all
|
||||||
# ansible_ssh_private_key_file: ~/.ssh/<SSH_KEY>
|
|
||||||
|
|
||||||
## --Operator info--
|
|
||||||
# email: "<EMAIL>" # used in certbot, description.toml and landing page
|
|
||||||
# website: "<WEBSITE>" # it is used in the description.toml
|
|
||||||
# description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
|
|
||||||
# moniker: "<MONIKER>"
|
|
||||||
|
|
||||||
## --Node defaults (can override per node in inventory/all)--
|
|
||||||
# accept_operator_terms: true # controls --accept-operator-terms-and-conditions, read: https://nym.com/docs/operators/nodes/nym-node/setup#terms--conditions
|
|
||||||
# mode: exit-gateway # entry-gateway/exit-gateway/mixnode
|
|
||||||
# wireguard_enabled: true # true/false
|
|
||||||
hostname: "" # keep this fallback, keep it and setup hostname per node in inventory/all
|
|
||||||
|
|
||||||
## OPTIONAL - uncomment & define
|
|
||||||
|
|
||||||
# operator_name: "<OPERATOR_NAME>" # used in landing page if provided
|
|
||||||
# nym_version: "nym-binaries-v2026.7-tola" # to use particular version instead of Latest, provide in such form:
|
|
||||||
|
|
||||||
## alternative SSH key var setting, instead of a hardcoded path
|
|
||||||
## useful if the playbook is shared in a repo by more admins with each having own local key
|
|
||||||
# ansible_ssh_private_key_file: "{{ lookup('env', '<YOUR_ANSIBLE SSH_KEY_ENV_VAR>') }}"
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
## GLOBAL PACKAGES & URLs
|
## GLOBAL PACKAGES
|
||||||
## These will be installed during deployment
|
## These will be installed during deployment
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
nym_cli_url: "https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-cli"
|
|
||||||
tunnel_manager_url: "https://github.com/nymtech/nym/raw/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh"
|
|
||||||
quic_bridge_deployment_url: "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh"
|
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
- tmux
|
- tmux
|
||||||
@@ -58,6 +50,24 @@ packages:
|
|||||||
- ufw
|
- ufw
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## OPTIONAL OVERRIDES
|
||||||
|
## All values below already have defaults in the playbook/roles
|
||||||
|
## Uncomment only if you want to override them
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# To use particular version instead of Latest, provide in such form:
|
||||||
|
# nym_version: "nym-binaries-v2026.7-tola"
|
||||||
|
|
||||||
|
## NOTE:
|
||||||
|
## if you want to pin Nym to a specific version instead of using the
|
||||||
|
## latest release from GitHub in /tasks/main.yml then
|
||||||
|
## uncomment the line above and set the tag
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
|
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|||||||
@@ -1,39 +1,34 @@
|
|||||||
[nym_nodes]
|
[nym_nodes]
|
||||||
## READ CONFIGURATION GUIDE:
|
# READ CONFIGURATION GUIDE:
|
||||||
## https://nym.com/docs/operators/orchestration/ansible#configuration
|
# https://nym.com//docs/operators/orchestration/ansible#configuration
|
||||||
|
|
||||||
##############
|
# VARIABLES INFO
|
||||||
## TEMPLATE ##
|
# required vars to set values per node:
|
||||||
##############
|
# `ansible_host`, `hostname`, `location`
|
||||||
## uncomment and exchange the <VARIABLES> with your real values for each node without the <> brackets
|
|
||||||
|
|
||||||
|
# global vars can be set in the group_vars/all.yml, for example:
|
||||||
|
# `email`, `ansible_user`, `moniker`, `description`, `mode`, `wireguard_enabled`
|
||||||
|
# othersise they must be set per node!
|
||||||
|
|
||||||
|
############
|
||||||
|
# TEMPLATE #
|
||||||
|
############
|
||||||
# node1 ansible_host=<YOUR_SERVER_IP> ansible_user=<USER> hostname=<HOSTNAME> location=<LOCATION> email=<EMAIL> mode=<MODE> wireguard_enabled=<true/false> moniker=<MONIKER> description=<DESCRIPTION>
|
# node1 ansible_host=<YOUR_SERVER_IP> ansible_user=<USER> hostname=<HOSTNAME> location=<LOCATION> email=<EMAIL> mode=<MODE> wireguard_enabled=<true/false> moniker=<MONIKER> description=<DESCRIPTION>
|
||||||
|
|
||||||
####################
|
# remove all comments and exchange the <VARIABLES> with your real values for each node
|
||||||
## VARIABLES INFO ##
|
# without <> brackets
|
||||||
####################
|
|
||||||
|
|
||||||
## --REQUIRED VARS--
|
# PRIORITY ORDER
|
||||||
## required per node:
|
# anything setup globaly can be overwritten in this file per node
|
||||||
## ansible_host, hostname, location
|
# if provided here, it takes priority over the global setting
|
||||||
|
|
||||||
## --OPTIONAL VARS--
|
# EXAMPLES
|
||||||
## can be set in the group_vars/all.yml or per node here:
|
# exit + wireguard gateway:
|
||||||
## email, ansible_user, moniker, description, mode, wireguard_enabled
|
|
||||||
|
|
||||||
## --PRIORITY ORDER--
|
|
||||||
## anything setup globaly can be overwritten in this file per node
|
|
||||||
## if provided here, it takes priority over the global setting
|
|
||||||
|
|
||||||
## --EXAMPLES--
|
|
||||||
## exit + wireguard gateway:
|
|
||||||
# node2 ansible_host=11.12.13.14 hostname=nym-exit.ch-1.mydomain.net mode=exit-gateway location=CH wireguard_enabled=true
|
# node2 ansible_host=11.12.13.14 hostname=nym-exit.ch-1.mydomain.net mode=exit-gateway location=CH wireguard_enabled=true
|
||||||
|
|
||||||
## entry gateway, no wireguard:
|
# entry gateway, no wireguard:
|
||||||
# node3 ansible_host=12.13.14.15 hostname=nym-entry.ch-2.mydomain.net mode=entry-gateway location=CH wireguard_enabled=false
|
# node3 ansible_host=12.13.14.15 hostname=nym-entry.ch-2.mydomain.net mode=entry-gateway location=CH wireguard_enabled=false
|
||||||
|
|
||||||
## mixnode (comment out tunnel+quic roles in deploy.yml for these)
|
# NOTE:
|
||||||
# mix-de-1 ansible_host=13.14.15.16 hostname=nym-mix.de-1.example.net location=DE mode=mixnode wireguard_enabled=false
|
# all examples above don't have defined user, email nor description as we use the definition from group_vars/main.yml without an attempt of overwriting it
|
||||||
|
# all examples above don't have moniker defined as there is a function in /templates/description.toml.j2 deriving it from the hostname
|
||||||
## NOTE:
|
|
||||||
## all examples above don't have defined user, email nor description as we use global vars from playbooks/group_vars/all.yml
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# Mitigation playbook for CopyFail (CVE-2026-31431) and DirtyFrag (CVE-2026-43284 / CVE-2026-43500)
|
|
||||||
# This playbook applies interim module blacklists only
|
|
||||||
# Kernel patches are not yet available (May 2026)
|
|
||||||
# Once patched kernels ship, use remove_kernel_CVE_mitigations.yml to reverse everything
|
|
||||||
# This playbook is idempotent - safe to re-run if mitigations were already applied
|
|
||||||
|
|
||||||
- name: Mitigate Copy Fail + Dirty Frag
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
tasks:
|
|
||||||
- name: Blacklist algif_aead (Copy Fail)
|
|
||||||
copy:
|
|
||||||
dest: /etc/modprobe.d/disable-algif_aead.conf
|
|
||||||
content: "install algif_aead /bin/false\n"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Blacklist esp4, esp6, rxrpc (Dirty Frag)
|
|
||||||
copy:
|
|
||||||
dest: /etc/modprobe.d/dirtyfrag.conf
|
|
||||||
content: |
|
|
||||||
install esp4 /bin/false
|
|
||||||
install esp6 /bin/false
|
|
||||||
install rxrpc /bin/false
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Unload all affected modules
|
|
||||||
modprobe:
|
|
||||||
name: "{{ item }}"
|
|
||||||
state: absent
|
|
||||||
loop:
|
|
||||||
- algif_aead
|
|
||||||
- esp4
|
|
||||||
- esp6
|
|
||||||
- rxrpc
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Drop page cache to clear any contamination
|
|
||||||
shell: echo 3 > /proc/sys/vm/drop_caches
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
############################################################################################
|
|
||||||
############################################################################################
|
|
||||||
############################################################################################
|
|
||||||
#### THIS PLAYBOOK IS NOT MEANT TO BE RUN YET, IT IS NOT REFERRED IN ANY DOCUMENTATION! ####
|
|
||||||
############################################################################################
|
|
||||||
############################################################################################
|
|
||||||
############################################################################################
|
|
||||||
#
|
|
||||||
# Reversal playbook for mitigate_kernel_CVE.yml (CopyFail CVE-2026-31431 / DirtyFrag CVE-2026-43284 / CVE-2026-43500).
|
|
||||||
#
|
|
||||||
# Run this AFTER your distro has shipped the patched kernel.
|
|
||||||
# This playbook:
|
|
||||||
# 1. Updates the kernel via apt
|
|
||||||
# 2. Reboots and waits for reconnect
|
|
||||||
# 3. Verifies the running kernel is newer than the pre-patch version
|
|
||||||
# 4. Removes the interim module blacklists
|
|
||||||
# 5. Re-enables the affected modules live (no second reboot needed)
|
|
||||||
#
|
|
||||||
# Debian family only (Debian, Ubuntu). Tested on Debian 11, Debian 12, Ubuntu 20.04, 22.04, 24.04.
|
|
||||||
#
|
|
||||||
# For exit-gateway nodes with --wireguard-enabled true:
|
|
||||||
# After this playbook completes, run the networking restore step on each node via:
|
|
||||||
# ansible-playbook deploy.yml -t ntm
|
|
||||||
# See the CVE patch documentation for details.
|
|
||||||
|
|
||||||
- name: Remove CVE mitigations and apply patched kernel
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Verify OS is Debian family
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- ansible_os_family == "Debian"
|
|
||||||
fail_msg: "This playbook supports Debian-family distros only (Debian, Ubuntu). For other distros, apply the kernel update and mitigation removal manually."
|
|
||||||
|
|
||||||
- name: Update apt cache
|
|
||||||
apt:
|
|
||||||
update_cache: true
|
|
||||||
cache_valid_time: 0
|
|
||||||
|
|
||||||
- name: Upgrade kernel packages
|
|
||||||
apt:
|
|
||||||
upgrade: full
|
|
||||||
only_upgrade: false
|
|
||||||
register: apt_upgrade_result
|
|
||||||
|
|
||||||
- name: Record pre-reboot kernel version
|
|
||||||
command: uname -r
|
|
||||||
register: kernel_before
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Reboot to load patched kernel
|
|
||||||
reboot:
|
|
||||||
msg: "Rebooting to apply patched kernel (CVE-2026-31431 / CVE-2026-43284 / CVE-2026-43500)"
|
|
||||||
reboot_timeout: 300
|
|
||||||
pre_reboot_delay: 5
|
|
||||||
post_reboot_delay: 15
|
|
||||||
|
|
||||||
- name: Record post-reboot kernel version
|
|
||||||
command: uname -r
|
|
||||||
register: kernel_after
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Show kernel versions before and after reboot
|
|
||||||
debug:
|
|
||||||
msg:
|
|
||||||
- "Kernel before reboot: {{ kernel_before.stdout }}"
|
|
||||||
- "Kernel after reboot: {{ kernel_after.stdout }}"
|
|
||||||
|
|
||||||
- name: Warn if kernel did not change after reboot
|
|
||||||
debug:
|
|
||||||
msg: >
|
|
||||||
WARNING: kernel version did not change after reboot ({{ kernel_after.stdout }}).
|
|
||||||
The patched kernel may not have been selected by GRUB, or no kernel update was available.
|
|
||||||
Do NOT remove the interim mitigations until you have confirmed the running kernel is patched.
|
|
||||||
Check: apt-cache policy linux-image-amd64 # Debian
|
|
||||||
Check: apt-cache policy linux-image-generic # Ubuntu
|
|
||||||
when: kernel_before.stdout == kernel_after.stdout
|
|
||||||
|
|
||||||
- name: Remove algif_aead blacklist
|
|
||||||
file:
|
|
||||||
path: /etc/modprobe.d/disable-algif_aead.conf
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Remove DirtyFrag blacklist (esp4, esp6, rxrpc)
|
|
||||||
file:
|
|
||||||
path: /etc/modprobe.d/dirtyfrag.conf
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Re-enable affected modules live
|
|
||||||
modprobe:
|
|
||||||
name: "{{ item }}"
|
|
||||||
state: present
|
|
||||||
loop:
|
|
||||||
- esp4
|
|
||||||
- esp6
|
|
||||||
- rxrpc
|
|
||||||
- algif_aead
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Confirm nym-node service is still running
|
|
||||||
systemd:
|
|
||||||
name: nym-node
|
|
||||||
state: started
|
|
||||||
register: nym_node_status
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Show nym-node status
|
|
||||||
debug:
|
|
||||||
msg: "nym-node service state: {{ nym_node_status.state | default('unknown - service may not exist on this node') }}"
|
|
||||||
@@ -89,6 +89,7 @@
|
|||||||
loop:
|
loop:
|
||||||
- "/etc/nginx/sites-enabled/{{ hostname }}-ssl"
|
- "/etc/nginx/sites-enabled/{{ hostname }}-ssl"
|
||||||
- "/etc/nginx/sites-enabled/nym-wss-config"
|
- "/etc/nginx/sites-enabled/nym-wss-config"
|
||||||
|
when: not le_cert.stat.exists
|
||||||
notify: Restart nginx
|
notify: Restart nginx
|
||||||
|
|
||||||
- name: Ensure nginx is enabled and running (needed for ACME http-01)
|
- name: Ensure nginx is enabled and running (needed for ACME http-01)
|
||||||
@@ -110,13 +111,18 @@
|
|||||||
- name: Obtain/renew certificate
|
- name: Obtain/renew certificate
|
||||||
command:
|
command:
|
||||||
cmd: >-
|
cmd: >-
|
||||||
certbot certonly --nginx
|
{% if le_cert.stat.exists %}
|
||||||
|
certbot certonly --webroot
|
||||||
|
-w /var/www/{{ hostname }}
|
||||||
--non-interactive --agree-tos --keep-until-expiring
|
--non-interactive --agree-tos --keep-until-expiring
|
||||||
-m {{ email }} -d {{ hostname }}
|
-m {{ email }} -d {{ hostname }}
|
||||||
|
{% else %}
|
||||||
|
certbot --nginx
|
||||||
|
--non-interactive --agree-tos --redirect
|
||||||
|
-m {{ email }} -d {{ hostname }}
|
||||||
|
{% endif %}
|
||||||
register: certbot_result
|
register: certbot_result
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# re-check cert after certbot attempt
|
# re-check cert after certbot attempt
|
||||||
- name: Re-check whether certificate exists after certbot
|
- name: Re-check whether certificate exists after certbot
|
||||||
@@ -164,4 +170,4 @@
|
|||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Flush handlers (apply restart after successful tests)
|
- name: Flush handlers (apply restart after successful tests)
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ mixnet_bind_address: "0.0.0.0:1789" # maps to --mixnet-bind-address
|
|||||||
landing_page_assets_base_dir: "/var/www"
|
landing_page_assets_base_dir: "/var/www"
|
||||||
|
|
||||||
# Flag toggles
|
# Flag toggles
|
||||||
accept_operator_terms: false # override in group_vars or inventory
|
# accept_operator_terms: true # controls --accept-operator-terms-and-conditions
|
||||||
nym_write_flag: true # controls -w
|
nym_write_flag: true # controls -w
|
||||||
nym_init_only_flag: true # controls --init-only
|
nym_init_only_flag: true # controls --init-only
|
||||||
wss_port: 9001 # controlls --announce-wss-port
|
wss_port: 9001 # controlls --announce-wss-port
|
||||||
@@ -18,7 +18,7 @@ wss_port: 9001 # controlls --announce-wss-port
|
|||||||
# Optional: extra flags if you want to append more later
|
# Optional: extra flags if you want to append more later
|
||||||
nym_extra_flags: ""
|
nym_extra_flags: ""
|
||||||
|
|
||||||
# CLI URL
|
# CLI URL (nym_version can be set elsewhere / via GitHub API)
|
||||||
nym_cli_url: "https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-cli"
|
nym_cli_url: "https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-cli"
|
||||||
|
|
||||||
# UFW
|
# UFW
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
- name: Show which node is being bonded
|
|
||||||
tags: bonding
|
|
||||||
debug:
|
|
||||||
msg: "Bonding Nym node: {{ hostname }}"
|
|
||||||
|
|
||||||
- name: Get bonding details
|
|
||||||
tags: bonding
|
|
||||||
command: "/root/nym-binaries/nym-node bonding-information"
|
|
||||||
register: bondinfo
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Display bonding info
|
|
||||||
tags: bonding
|
|
||||||
debug:
|
|
||||||
msg: "{{ item }}"
|
|
||||||
loop: "{{ bondinfo.stdout_lines }}"
|
|
||||||
|
|
||||||
- name: Sign bonding contract message on the node
|
|
||||||
tags: bonding
|
|
||||||
command:
|
|
||||||
argv:
|
|
||||||
- /root/nym-binaries/nym-node
|
|
||||||
- sign
|
|
||||||
- --contract-msg
|
|
||||||
- "{{ contract_msg }}"
|
|
||||||
- --output
|
|
||||||
- json
|
|
||||||
register: sign_output
|
|
||||||
|
|
||||||
- name: Display full signed message exactly as returned
|
|
||||||
tags: bonding
|
|
||||||
debug:
|
|
||||||
msg: "{{ sign_output.stdout }}"
|
|
||||||
|
|
||||||
- name: Display encoded signature
|
|
||||||
tags: bonding
|
|
||||||
debug:
|
|
||||||
msg: "ENCODED_SIGNATURE={{ (sign_output.stdout | from_json).encoded_signature }}"
|
|
||||||
@@ -1,20 +1,16 @@
|
|||||||
- name: Download quic_bridge_deployment.sh
|
- name: Download quic_bridge_deployment.sh
|
||||||
command:
|
tags: quic bridge deployment
|
||||||
cmd: "curl -fsSL {{ quic_bridge_deployment_url }} -o /root/nym-binaries/quic_bridge_deployment.sh"
|
get_url:
|
||||||
tags: quic
|
url: "{{ quic_bridge_deployment_url }}"
|
||||||
|
dest: "/root/nym-binaries/quic_bridge_deployment.sh"
|
||||||
- name: Set quic_bridge_deployment permissions
|
|
||||||
file:
|
|
||||||
path: /root/nym-binaries/quic_bridge_deployment.sh
|
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
tags: quic
|
|
||||||
|
|
||||||
- name: Configure tunnel manager
|
- name: Configure tunnel manager
|
||||||
|
tags: quic bridge deployment
|
||||||
become: true
|
become: true
|
||||||
command:
|
command:
|
||||||
cmd: "/root/nym-binaries/quic_bridge_deployment.sh {{ item }}"
|
cmd: "/root/nym-binaries/quic_bridge_deployment.sh {{ item }}"
|
||||||
environment:
|
environment:
|
||||||
NONINTERACTIVE: "1"
|
NONINTERACTIVE: "1"
|
||||||
loop:
|
loop:
|
||||||
- full_bridge_setup
|
- full_bridge_setup
|
||||||
tags: quic
|
|
||||||
@@ -10,17 +10,11 @@
|
|||||||
- ntm
|
- ntm
|
||||||
|
|
||||||
- name: Download network tunnel manager
|
- name: Download network tunnel manager
|
||||||
command:
|
get_url:
|
||||||
cmd: "curl -fsSL {{ tunnel_manager_url }} -o /root/nym-binaries/network-tunnel-manager.sh"
|
url: "{{ tunnel_manager_url }}"
|
||||||
tags:
|
dest: /root/nym-binaries/network-tunnel-manager.sh
|
||||||
- tunnel
|
|
||||||
- network_tunnel_manager
|
|
||||||
- ntm
|
|
||||||
|
|
||||||
- name: Set network tunnel manager permissions
|
|
||||||
file:
|
|
||||||
path: /root/nym-binaries/network-tunnel-manager.sh
|
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
force: yes
|
||||||
tags:
|
tags:
|
||||||
- tunnel
|
- tunnel
|
||||||
- network_tunnel_manager
|
- network_tunnel_manager
|
||||||
|
|||||||
@@ -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.78"
|
version = "1.1.77"
|
||||||
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
|
||||||
|
|||||||
@@ -472,7 +472,6 @@ impl Handler {
|
|||||||
fn prepare_reconstructed_binary(
|
fn prepare_reconstructed_binary(
|
||||||
reconstructed_messages: Vec<ReconstructedMessage>,
|
reconstructed_messages: Vec<ReconstructedMessage>,
|
||||||
) -> Vec<Result<WsMessage, WsError>> {
|
) -> Vec<Result<WsMessage, WsError>> {
|
||||||
#[allow(clippy::result_large_err)] // TODO : remove this once tungstenite is updated
|
|
||||||
reconstructed_messages
|
reconstructed_messages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(ServerResponse::Received)
|
.map(ServerResponse::Received)
|
||||||
@@ -485,7 +484,6 @@ fn prepare_reconstructed_binary(
|
|||||||
fn prepare_reconstructed_text(
|
fn prepare_reconstructed_text(
|
||||||
reconstructed_messages: Vec<ReconstructedMessage>,
|
reconstructed_messages: Vec<ReconstructedMessage>,
|
||||||
) -> Vec<Result<WsMessage, WsError>> {
|
) -> Vec<Result<WsMessage, WsError>> {
|
||||||
#[allow(clippy::result_large_err)] // TODO : remove this once tungstenite is updated
|
|
||||||
reconstructed_messages
|
reconstructed_messages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(ServerResponse::Received)
|
.map(ServerResponse::Received)
|
||||||
|
|||||||
@@ -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.78"
|
version = "1.1.77"
|
||||||
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,8 +25,6 @@ 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))]
|
||||||
@@ -58,10 +56,6 @@ 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))]
|
||||||
@@ -81,8 +75,4 @@ 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,16 +1023,6 @@ 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,17 +11,11 @@ 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, time::Duration};
|
use std::{io, path::Path};
|
||||||
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,
|
||||||
@@ -80,58 +74,13 @@ 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);
|
||||||
|
|
||||||
// On Windows, sqlx may release its OS file handles asynchronously after
|
tokio::fs::rename(db_path, &renamed).await.inspect_err(|_| {
|
||||||
// pool.close() returns, briefly keeping the file locked
|
error!(
|
||||||
// (ERROR_SHARING_VIOLATION, os error 32). Retry with a short delay to
|
"Failed to rename corrupt database file: {} to {}",
|
||||||
// give the OS time to flush the remaining handles.
|
db_path.display(),
|
||||||
for attempt in 0..ARCHIVE_MAX_RETRY_ATTEMPTS {
|
renamed.display()
|
||||||
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>>(
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ where
|
|||||||
let mut pending_acks = Vec::with_capacity(fragments.len());
|
let mut pending_acks = Vec::with_capacity(fragments.len());
|
||||||
let mut to_forward: HashMap<_, Vec<_>> = HashMap::new();
|
let mut to_forward: HashMap<_, Vec<_>> = HashMap::new();
|
||||||
|
|
||||||
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments) {
|
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) {
|
||||||
let lane = raw.0;
|
let lane = raw.0;
|
||||||
let FragmentWithMaxRetransmissions {
|
let FragmentWithMaxRetransmissions {
|
||||||
fragment,
|
fragment,
|
||||||
@@ -670,7 +670,7 @@ where
|
|||||||
|
|
||||||
Ok(fragments
|
Ok(fragments
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(reply_surbs)
|
.zip(reply_surbs.into_iter())
|
||||||
.map(|(fragment, reply_surb)| {
|
.map(|(fragment, reply_surb)| {
|
||||||
// unwrap here is fine as we know we have a valid topology
|
// unwrap here is fine as we know we have a valid topology
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
|
|||||||
@@ -337,8 +337,6 @@ impl ReplyStorageBackend for Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||||
let result = self.stop_client_use().await;
|
self.stop_client_use().await
|
||||||
self.shutdown().await;
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,11 +55,10 @@ 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}")
|
||||||
self.backend.stop_storage_session().await.ok();
|
} else {
|
||||||
return;
|
info!("Data flush is complete")
|
||||||
}
|
}
|
||||||
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")
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ publish = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
dashmap = { workspace = true }
|
dashmap = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
strum = { workspace = true }
|
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["time", "sync"] }
|
tokio = { workspace = true, features = ["time", "sync"] }
|
||||||
tokio-util = { workspace = true, features = ["codec"], optional = true }
|
tokio-util = { workspace = true, features = ["codec"], optional = true }
|
||||||
@@ -27,13 +26,12 @@ tokio-stream = { workspace = true }
|
|||||||
nym-noise = { workspace = true }
|
nym-noise = { workspace = true }
|
||||||
nym-sphinx = { workspace = true }
|
nym-sphinx = { workspace = true }
|
||||||
nym-task = { workspace = true, optional = true }
|
nym-task = { workspace = true, optional = true }
|
||||||
nym-metrics = { workspace = true, optional = true }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["client"]
|
default = ["client"]
|
||||||
client = ["tokio-util", "nym-task", "nym-metrics", "tokio/net", "tokio/rt"]
|
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", "test-util"] }
|
tokio = { workspace = true, features = ["macros", "io-util", "rt", "rt-multi-thread"] }
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::metrics::{MixnetMetric, Traced};
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use futures::{Sink, SinkExt, 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;
|
||||||
@@ -11,7 +10,7 @@ use nym_sphinx::framing::codec::NymCodec;
|
|||||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::ops::{ControlFlow, Deref};
|
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;
|
||||||
@@ -19,7 +18,7 @@ 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;
|
||||||
use tokio::time::{sleep, Instant};
|
use tokio::time::sleep;
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tokio_util::codec::Framed;
|
use tokio_util::codec::Framed;
|
||||||
use tracing::*;
|
use tracing::*;
|
||||||
@@ -31,13 +30,6 @@ pub struct Config {
|
|||||||
pub initial_connection_timeout: Duration,
|
pub initial_connection_timeout: Duration,
|
||||||
pub maximum_connection_buffer_size: usize,
|
pub maximum_connection_buffer_size: usize,
|
||||||
pub use_legacy_packet_encoding: bool,
|
pub use_legacy_packet_encoding: bool,
|
||||||
/// Close an egress connection after this long with no packets sent (0 disables). The cache
|
|
||||||
/// entry is evicted on close and the next packet to that peer transparently reconnects.
|
|
||||||
pub connection_idle_timeout: Duration,
|
|
||||||
/// Max time a single batch flush may block on the peer socket before we give up on it
|
|
||||||
/// (0 disables). One timeout is treated as transient congestion - the batch is abandoned but
|
|
||||||
/// the connection is retained (no re-handshake); only a few *consecutive* timeouts tear it down.
|
|
||||||
pub connection_write_timeout: Duration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -47,8 +39,6 @@ impl Config {
|
|||||||
initial_connection_timeout: Duration,
|
initial_connection_timeout: Duration,
|
||||||
maximum_connection_buffer_size: usize,
|
maximum_connection_buffer_size: usize,
|
||||||
use_legacy_packet_encoding: bool,
|
use_legacy_packet_encoding: bool,
|
||||||
connection_idle_timeout: Duration,
|
|
||||||
connection_write_timeout: Duration,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Config {
|
Config {
|
||||||
initial_reconnection_backoff,
|
initial_reconnection_backoff,
|
||||||
@@ -56,18 +46,14 @@ impl Config {
|
|||||||
initial_connection_timeout,
|
initial_connection_timeout,
|
||||||
maximum_connection_buffer_size,
|
maximum_connection_buffer_size,
|
||||||
use_legacy_packet_encoding,
|
use_legacy_packet_encoding,
|
||||||
connection_idle_timeout,
|
|
||||||
connection_write_timeout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SendWithoutResponse {
|
pub trait SendWithoutResponse {
|
||||||
// Without response in this context means we will not listen for anything we might get back (not
|
// Without response in this context means we will not listen for anything we might get back (not
|
||||||
// that we should get anything), including any possible io errors.
|
// that we should get anything), including any possible io errors
|
||||||
// The packet carries the latency trace started upstream (at receive); the egress stages are
|
fn send_without_response(&self, packet: MixPacket) -> io::Result<()>;
|
||||||
// stamped here and are a no-op for unsampled packets.
|
|
||||||
fn send_without_response(&self, packet: Traced<MixPacket>) -> io::Result<()>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
@@ -103,7 +89,7 @@ impl Deref for ActiveConnections {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ConnectionSender {
|
pub struct ConnectionSender {
|
||||||
channel: mpsc::Sender<Traced<FramedNymPacket>>,
|
channel: mpsc::Sender<FramedNymPacket>,
|
||||||
current_reconnection_attempt: Arc<AtomicU32>,
|
current_reconnection_attempt: Arc<AtomicU32>,
|
||||||
// Identifies the `ManagedConnection` task currently owning this entry; used
|
// Identifies the `ManagedConnection` task currently owning this entry; used
|
||||||
// to ensure drop-time eviction only fires on the still-owning task.
|
// to ensure drop-time eviction only fires on the still-owning task.
|
||||||
@@ -111,7 +97,7 @@ pub struct ConnectionSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectionSender {
|
impl ConnectionSender {
|
||||||
fn new(channel: mpsc::Sender<Traced<FramedNymPacket>>, handle_token: Arc<()>) -> 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)),
|
||||||
@@ -123,10 +109,8 @@ impl ConnectionSender {
|
|||||||
struct ManagedConnection {
|
struct ManagedConnection {
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
noise_config: NoiseConfig,
|
noise_config: NoiseConfig,
|
||||||
message_receiver: ReceiverStream<Traced<FramedNymPacket>>,
|
message_receiver: ReceiverStream<FramedNymPacket>,
|
||||||
connection_timeout: Duration,
|
connection_timeout: Duration,
|
||||||
idle_timeout: Duration,
|
|
||||||
write_timeout: Duration,
|
|
||||||
current_reconnection: Arc<AtomicU32>,
|
current_reconnection: Arc<AtomicU32>,
|
||||||
active_connections: ActiveConnections,
|
active_connections: ActiveConnections,
|
||||||
handle_token: Arc<()>,
|
handle_token: Arc<()>,
|
||||||
@@ -156,14 +140,11 @@ impl Drop for EvictOnDrop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ManagedConnection {
|
impl ManagedConnection {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn new(
|
fn new(
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
noise_config: NoiseConfig,
|
noise_config: NoiseConfig,
|
||||||
message_receiver: mpsc::Receiver<Traced<FramedNymPacket>>,
|
message_receiver: mpsc::Receiver<FramedNymPacket>,
|
||||||
connection_timeout: Duration,
|
connection_timeout: Duration,
|
||||||
idle_timeout: Duration,
|
|
||||||
write_timeout: Duration,
|
|
||||||
current_reconnection: Arc<AtomicU32>,
|
current_reconnection: Arc<AtomicU32>,
|
||||||
active_connections: ActiveConnections,
|
active_connections: ActiveConnections,
|
||||||
handle_token: Arc<()>,
|
handle_token: Arc<()>,
|
||||||
@@ -173,8 +154,6 @@ impl ManagedConnection {
|
|||||||
noise_config,
|
noise_config,
|
||||||
message_receiver: ReceiverStream::new(message_receiver),
|
message_receiver: ReceiverStream::new(message_receiver),
|
||||||
connection_timeout,
|
connection_timeout,
|
||||||
idle_timeout,
|
|
||||||
write_timeout,
|
|
||||||
current_reconnection,
|
current_reconnection,
|
||||||
active_connections,
|
active_connections,
|
||||||
handle_token,
|
handle_token,
|
||||||
@@ -183,8 +162,6 @@ impl ManagedConnection {
|
|||||||
|
|
||||||
async fn run(self) {
|
async fn run(self) {
|
||||||
let address = self.address;
|
let address = self.address;
|
||||||
let idle_timeout = self.idle_timeout;
|
|
||||||
let write_timeout = self.write_timeout;
|
|
||||||
let _evict_guard = EvictOnDrop {
|
let _evict_guard = EvictOnDrop {
|
||||||
active_connections: self.active_connections,
|
active_connections: self.active_connections,
|
||||||
address,
|
address,
|
||||||
@@ -241,11 +218,6 @@ impl ManagedConnection {
|
|||||||
"Managed to establish connection to {}", self.address
|
"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)
|
// 3. perform noise handshake (if applicable)
|
||||||
let noise_start = tokio::time::Instant::now();
|
let noise_start = tokio::time::Instant::now();
|
||||||
let noise_stream = match upgrade_noise_initiator(stream, &self.noise_config).await {
|
let noise_stream = match upgrade_noise_initiator(stream, &self.noise_config).await {
|
||||||
@@ -274,236 +246,79 @@ impl ManagedConnection {
|
|||||||
noise_handshake_ms,
|
noise_handshake_ms,
|
||||||
"Noise initiator handshake completed for {:?}", address
|
"Noise initiator handshake completed for {:?}", address
|
||||||
);
|
);
|
||||||
let mut conn = Framed::new(noise_stream, NymCodec);
|
let 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
|
// 4. start handling the framed stream
|
||||||
run_io_loop(
|
run_io_loop(conn, self.message_receiver, address).await;
|
||||||
conn,
|
|
||||||
self.message_receiver,
|
|
||||||
address,
|
|
||||||
idle_timeout,
|
|
||||||
write_timeout,
|
|
||||||
)
|
|
||||||
.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;
|
|
||||||
|
|
||||||
/// Drive the read half solely to notice peer FIN/RST (the connection is send-only). Returns
|
|
||||||
/// `Break` when the peer closed the connection or the read errored, `Continue` otherwise.
|
|
||||||
fn handle_peer_read<P, E: std::fmt::Display>(
|
|
||||||
msg: Option<Result<P, E>>,
|
|
||||||
address: SocketAddr,
|
|
||||||
) -> ControlFlow<()> {
|
|
||||||
match msg {
|
|
||||||
None => {
|
|
||||||
debug!(
|
|
||||||
peer = %address,
|
|
||||||
exit_reason = "peer_closed",
|
|
||||||
"peer closed mixnet connection to {address}"
|
|
||||||
);
|
|
||||||
ControlFlow::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}"
|
|
||||||
);
|
|
||||||
ControlFlow::Break(())
|
|
||||||
}
|
|
||||||
Some(Ok(_)) => {
|
|
||||||
trace!(
|
|
||||||
peer = %address,
|
|
||||||
"unexpected inbound packet on mixnet connection to {address}; discarding"
|
|
||||||
);
|
|
||||||
ControlFlow::Continue(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Number of consecutive flush timeouts to the same peer we tolerate before dropping the
|
|
||||||
/// connection. A single timeout is transient congestion (batch abandoned, connection retained to
|
|
||||||
/// avoid a re-handshake); this many in a row means the peer is persistently unable to keep up, so
|
|
||||||
/// we tear the connection down (it reconnects on the next packet).
|
|
||||||
const MAX_CONSECUTIVE_WRITE_TIMEOUTS: u32 = 3;
|
|
||||||
|
|
||||||
/// Outcome of attempting to flush one batch to the peer.
|
|
||||||
enum BatchOutcome {
|
|
||||||
/// the batch was flushed to the socket
|
|
||||||
Sent,
|
|
||||||
/// the flush exceeded the write timeout (peer congested): the un-fed tail of the batch is
|
|
||||||
/// dropped, but the already-encoded frames stay buffered for a later flush and the connection
|
|
||||||
/// is left intact - the noise transport stays nonce-consistent across the cancelled flush, so
|
|
||||||
/// resuming the write is sound
|
|
||||||
WriteTimedOut,
|
|
||||||
/// the sink errored: the connection is dead
|
|
||||||
Failed,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Feed a ready batch into the sink and flush it once (far fewer syscalls than per-packet), then
|
|
||||||
/// stamp the egress latency stages: `EgressQueue` before each feed, then `SocketWrite` + the
|
|
||||||
/// end-to-end total once the batch has hit the wire. The flush is bounded by `write_timeout`
|
|
||||||
/// (0 disables) so a congested peer can't block this connection's egress queue into the
|
|
||||||
/// multi-second range. The caller decides what a timeout means (see [`MAX_CONSECUTIVE_WRITE_TIMEOUTS`]).
|
|
||||||
async fn forward_batch<S>(
|
|
||||||
sink: &mut S,
|
|
||||||
batch: Vec<Traced<FramedNymPacket>>,
|
|
||||||
address: SocketAddr,
|
|
||||||
write_timeout: Duration,
|
|
||||||
) -> BatchOutcome
|
|
||||||
where
|
|
||||||
S: Sink<FramedNymPacket> + Unpin,
|
|
||||||
S::Error: std::fmt::Display,
|
|
||||||
{
|
|
||||||
let mut traces = Vec::with_capacity(batch.len());
|
|
||||||
let write = async {
|
|
||||||
for mut traced in batch {
|
|
||||||
// time spent waiting in this connection's egress buffer
|
|
||||||
traced.record(MixnetMetric::EgressQueue);
|
|
||||||
sink.feed(traced.inner).await?;
|
|
||||||
traces.push(traced.trace);
|
|
||||||
}
|
|
||||||
sink.flush().await
|
|
||||||
};
|
|
||||||
|
|
||||||
// bound how long we block on a slow/congested peer socket. On timeout the `write` future is
|
|
||||||
// cancelled, which is safe: every already-encoded frame is buffered (nonce-consistent), so a
|
|
||||||
// later flush resumes the byte stream in order.
|
|
||||||
let write_result = if write_timeout.is_zero() {
|
|
||||||
Ok(write.await)
|
|
||||||
} else {
|
|
||||||
tokio::time::timeout(write_timeout, write).await
|
|
||||||
};
|
|
||||||
|
|
||||||
// socket-write time + end-to-end total for whatever was fed (on a timeout, those frames are
|
|
||||||
// buffered and will hit the wire on a subsequent flush)
|
|
||||||
for mut trace in traces {
|
|
||||||
trace.record(MixnetMetric::SocketWrite);
|
|
||||||
trace.record_total();
|
|
||||||
}
|
|
||||||
|
|
||||||
match write_result {
|
|
||||||
Ok(Ok(())) => BatchOutcome::Sent,
|
|
||||||
Ok(Err(err)) => {
|
|
||||||
debug!(
|
|
||||||
event = "connection.forward_error",
|
|
||||||
peer = %address,
|
|
||||||
error = %err,
|
|
||||||
exit_reason = "forward_error",
|
|
||||||
"failed to forward packet batch to {address}: {err}"
|
|
||||||
);
|
|
||||||
BatchOutcome::Failed
|
|
||||||
}
|
|
||||||
Err(_elapsed) => BatchOutcome::WriteTimedOut,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instant at which a connection idle since `last_activity` should be closed, or `None` if idle
|
|
||||||
/// reaping is disabled (`timeout` is zero).
|
|
||||||
fn idle_deadline(last_activity: Instant, timeout: Duration) -> Option<Instant> {
|
|
||||||
(!timeout.is_zero()).then(|| last_activity + timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The connection is unidirectional (send-only); we read from it solely to
|
// 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
|
// notice peer FIN/RST while idle so we can evict the cache entry before the
|
||||||
// next outbound send finds it stale.
|
// next outbound send finds it stale.
|
||||||
async fn run_io_loop<T>(
|
async fn run_io_loop<T>(
|
||||||
conn: Framed<T, NymCodec>,
|
conn: Framed<T, NymCodec>,
|
||||||
receiver: ReceiverStream<Traced<FramedNymPacket>>,
|
mut receiver: ReceiverStream<FramedNymPacket>,
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
idle_timeout: Duration,
|
|
||||||
write_timeout: Duration,
|
|
||||||
) where
|
) where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
{
|
{
|
||||||
let (mut sink, mut stream) = conn.split();
|
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);
|
|
||||||
|
|
||||||
// reset by every batch we send; drives the idle-connection reaping below
|
|
||||||
let mut last_send = tokio::time::Instant::now();
|
|
||||||
// consecutive flush timeouts; a run of them (a persistently congested peer) drops the connection
|
|
||||||
let mut consecutive_write_timeouts = 0u32;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
msg = stream.next() => {
|
msg = stream.next() => {
|
||||||
if handle_peer_read(msg, address).is_break() {
|
match msg {
|
||||||
break;
|
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() => {
|
outgoing = receiver.next() => {
|
||||||
let Some(batch) = outgoing else {
|
match outgoing {
|
||||||
debug!(
|
None => {
|
||||||
peer = %address,
|
debug!(
|
||||||
exit_reason = "sender_dropped",
|
|
||||||
"connection manager to {address} finished"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
match forward_batch(&mut sink, batch, address, write_timeout).await {
|
|
||||||
BatchOutcome::Sent => {
|
|
||||||
consecutive_write_timeouts = 0;
|
|
||||||
last_send = Instant::now();
|
|
||||||
}
|
|
||||||
BatchOutcome::WriteTimedOut => {
|
|
||||||
consecutive_write_timeouts += 1;
|
|
||||||
warn!(
|
|
||||||
event = "connection.write_congested",
|
|
||||||
peer = %address,
|
peer = %address,
|
||||||
write_ms = write_timeout.as_millis() as u64,
|
exit_reason = "sender_dropped",
|
||||||
attempt = consecutive_write_timeouts,
|
"connection manager to {address} finished"
|
||||||
max_attempts = MAX_CONSECUTIVE_WRITE_TIMEOUTS,
|
|
||||||
"egress flush to {address} timed out (peer congested); abandoned batch, retaining connection"
|
|
||||||
);
|
);
|
||||||
if consecutive_write_timeouts >= MAX_CONSECUTIVE_WRITE_TIMEOUTS {
|
break;
|
||||||
|
}
|
||||||
|
Some(packet) => {
|
||||||
|
if let Err(err) = sink.send(packet).await {
|
||||||
debug!(
|
debug!(
|
||||||
|
event = "connection.forward_error",
|
||||||
peer = %address,
|
peer = %address,
|
||||||
exit_reason = "write_timeout",
|
error = %err,
|
||||||
"egress connection to {address} congested for {MAX_CONSECUTIVE_WRITE_TIMEOUTS} consecutive flushes; dropping it"
|
exit_reason = "forward_error",
|
||||||
|
"Failed to forward packet to {address}: {err}"
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// keep the connection: a single congestion spike shouldn't cost a
|
|
||||||
// re-handshake. `last_send` is deliberately not bumped, so a peer that goes
|
|
||||||
// congested-then-silent still idle-reaps on schedule.
|
|
||||||
}
|
}
|
||||||
BatchOutcome::Failed => break,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// close the connection (freeing the task/socket) if we haven't sent anything for too
|
|
||||||
// long; EvictOnDrop then clears the cache entry and the next packet reconnects
|
|
||||||
_ = async {
|
|
||||||
match idle_deadline(last_send, idle_timeout) {
|
|
||||||
Some(d) => tokio::time::sleep_until(d).await,
|
|
||||||
None => std::future::pending::<()>().await,
|
|
||||||
}
|
|
||||||
} => {
|
|
||||||
debug!(
|
|
||||||
peer = %address,
|
|
||||||
exit_reason = "idle_timeout",
|
|
||||||
idle_secs = idle_timeout.as_secs(),
|
|
||||||
"closing idle egress mixnet connection to {address}"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -543,7 +358,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_connection(&self, address: SocketAddr, pending_packet: Traced<FramedNymPacket>) {
|
fn make_connection(&self, address: SocketAddr, pending_packet: FramedNymPacket) {
|
||||||
let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
|
let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
|
||||||
|
|
||||||
// this CAN'T fail because we just created the channel which has a non-zero capacity
|
// this CAN'T fail because we just created the channel which has a non-zero capacity
|
||||||
@@ -572,10 +387,8 @@ impl Client {
|
|||||||
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
|
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
|
||||||
let backoff = self.determine_backoff(reconnection_attempt);
|
let backoff = self.determine_backoff(reconnection_attempt);
|
||||||
|
|
||||||
// copy the values before moving into another task
|
// copy the value before moving into another task
|
||||||
let initial_connection_timeout = self.config.initial_connection_timeout;
|
let initial_connection_timeout = self.config.initial_connection_timeout;
|
||||||
let connection_idle_timeout = self.config.connection_idle_timeout;
|
|
||||||
let connection_write_timeout = self.config.connection_write_timeout;
|
|
||||||
|
|
||||||
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();
|
||||||
@@ -593,8 +406,6 @@ impl Client {
|
|||||||
noise_config,
|
noise_config,
|
||||||
receiver,
|
receiver,
|
||||||
initial_connection_timeout,
|
initial_connection_timeout,
|
||||||
connection_idle_timeout,
|
|
||||||
connection_write_timeout,
|
|
||||||
current_reconnection_attempt,
|
current_reconnection_attempt,
|
||||||
active_connections,
|
active_connections,
|
||||||
handle_token,
|
handle_token,
|
||||||
@@ -607,17 +418,14 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SendWithoutResponse for Client {
|
impl SendWithoutResponse for Client {
|
||||||
fn send_without_response(&self, packet: Traced<MixPacket>) -> io::Result<()> {
|
fn send_without_response(&self, packet: MixPacket) -> io::Result<()> {
|
||||||
let address = packet.inner.next_hop_address();
|
let address = packet.next_hop_address();
|
||||||
trace!("Sending packet to {address}");
|
trace!("Sending packet to {address}");
|
||||||
|
|
||||||
// capture the sample state before the trace is moved into `queued`
|
|
||||||
let sampled = packet.trace.is_sampled();
|
|
||||||
|
|
||||||
// TODO: optimisation for the future: rather than constantly using legacy encoding,
|
// TODO: optimisation for the future: rather than constantly using legacy encoding,
|
||||||
// use the mix packet type / flags to pick encoding per packet
|
// use the mix packet type / flags to pick encoding per packet
|
||||||
let legacy = self.config.use_legacy_packet_encoding;
|
let framed_packet =
|
||||||
let queued = packet.map(|p| FramedNymPacket::from_mix_packet(p, legacy));
|
FramedNymPacket::from_mix_packet(packet, self.config.use_legacy_packet_encoding);
|
||||||
|
|
||||||
let Some(sender) = self.active_connections.get_mut(&address) else {
|
let Some(sender) = self.active_connections.get_mut(&address) else {
|
||||||
// there was never a connection to begin with
|
// there was never a connection to begin with
|
||||||
@@ -627,7 +435,7 @@ impl SendWithoutResponse for Client {
|
|||||||
result = "not_connected",
|
result = "not_connected",
|
||||||
"establishing initial connection to {address}"
|
"establishing initial connection to {address}"
|
||||||
);
|
);
|
||||||
self.make_connection(address, queued);
|
self.make_connection(address, framed_packet);
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::NotConnected,
|
io::ErrorKind::NotConnected,
|
||||||
"connection is in progress",
|
"connection is in progress",
|
||||||
@@ -638,12 +446,7 @@ impl SendWithoutResponse for Client {
|
|||||||
let channel_available = sender.channel.capacity();
|
let channel_available = sender.channel.capacity();
|
||||||
let channel_used = channel_capacity - channel_available;
|
let channel_used = channel_capacity - channel_available;
|
||||||
|
|
||||||
// record how full this peer's egress buffer was (sampled packets only, to bound cost)
|
let sending_res = sender.channel.try_send(framed_packet);
|
||||||
if sampled {
|
|
||||||
crate::metrics::observe_egress_buffer_fill(channel_used, channel_capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sending_res = sender.channel.try_send(queued);
|
|
||||||
drop(sender);
|
drop(sender);
|
||||||
|
|
||||||
sending_res.map_err(|err| {
|
sending_res.map_err(|err| {
|
||||||
@@ -698,8 +501,6 @@ mod tests {
|
|||||||
initial_connection_timeout: Duration::from_millis(1_500),
|
initial_connection_timeout: Duration::from_millis(1_500),
|
||||||
maximum_connection_buffer_size: 128,
|
maximum_connection_buffer_size: 128,
|
||||||
use_legacy_packet_encoding: false,
|
use_legacy_packet_encoding: false,
|
||||||
connection_idle_timeout: Duration::from_secs(300),
|
|
||||||
connection_write_timeout: Duration::from_millis(500),
|
|
||||||
},
|
},
|
||||||
NoiseConfig::new(
|
NoiseConfig::new(
|
||||||
Arc::new(x25519::KeyPair::new(&mut rng)),
|
Arc::new(x25519::KeyPair::new(&mut rng)),
|
||||||
@@ -746,7 +547,7 @@ mod tests {
|
|||||||
active: &ActiveConnections,
|
active: &ActiveConnections,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
token: Arc<()>,
|
token: Arc<()>,
|
||||||
) -> mpsc::Receiver<Traced<FramedNymPacket>> {
|
) -> mpsc::Receiver<FramedNymPacket> {
|
||||||
let (tx, rx) = mpsc::channel(1);
|
let (tx, rx) = mpsc::channel(1);
|
||||||
active.insert(addr, ConnectionSender::new(tx, token));
|
active.insert(addr, ConnectionSender::new(tx, token));
|
||||||
rx
|
rx
|
||||||
@@ -809,14 +610,7 @@ mod tests {
|
|||||||
let conn = Framed::new(a, NymCodec);
|
let conn = Framed::new(a, NymCodec);
|
||||||
let (_tx, rx) = mpsc::channel(1);
|
let (_tx, rx) = mpsc::channel(1);
|
||||||
|
|
||||||
// idle reaping disabled so only the peer-close path is exercised
|
let task = tokio::spawn(run_io_loop(conn, ReceiverStream::new(rx), test_addr()));
|
||||||
let task = tokio::spawn(run_io_loop(
|
|
||||||
conn,
|
|
||||||
ReceiverStream::new(rx),
|
|
||||||
test_addr(),
|
|
||||||
Duration::ZERO,
|
|
||||||
Duration::ZERO,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Simulate peer closing both directions of the connection.
|
// Simulate peer closing both directions of the connection.
|
||||||
drop(b);
|
drop(b);
|
||||||
@@ -833,13 +627,7 @@ mod tests {
|
|||||||
let conn = Framed::new(a, NymCodec);
|
let conn = Framed::new(a, NymCodec);
|
||||||
let (tx, rx) = mpsc::channel(1);
|
let (tx, rx) = mpsc::channel(1);
|
||||||
|
|
||||||
let task = tokio::spawn(run_io_loop(
|
let task = tokio::spawn(run_io_loop(conn, ReceiverStream::new(rx), test_addr()));
|
||||||
conn,
|
|
||||||
ReceiverStream::new(rx),
|
|
||||||
test_addr(),
|
|
||||||
Duration::ZERO,
|
|
||||||
Duration::ZERO,
|
|
||||||
));
|
|
||||||
|
|
||||||
drop(tx);
|
drop(tx);
|
||||||
|
|
||||||
@@ -848,32 +636,4 @@ mod tests {
|
|||||||
.expect("io_loop must exit when the upstream sender is dropped")
|
.expect("io_loop must exit when the upstream sender is dropped")
|
||||||
.expect("io_loop task must not panic");
|
.expect("io_loop task must not panic");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(start_paused = true)]
|
|
||||||
async fn io_loop_closes_idle_connection() {
|
|
||||||
// With no packets sent and the peer still connected, the idle timeout must eventually
|
|
||||||
// close the connection so the task/socket don't linger forever. The paused clock is
|
|
||||||
// virtual - it auto-advances to the next timer, so this completes instantly despite the
|
|
||||||
// durations below (no real waiting).
|
|
||||||
let (a, _b) = tokio::io::duplex(64);
|
|
||||||
let conn = Framed::new(a, NymCodec);
|
|
||||||
// keep the sender alive so the sender-dropped path can't fire instead
|
|
||||||
let (_tx, rx) = mpsc::channel(1);
|
|
||||||
|
|
||||||
let idle_timeout = Duration::from_millis(50);
|
|
||||||
let task = tokio::spawn(run_io_loop(
|
|
||||||
conn,
|
|
||||||
ReceiverStream::new(rx),
|
|
||||||
test_addr(),
|
|
||||||
idle_timeout,
|
|
||||||
Duration::ZERO,
|
|
||||||
));
|
|
||||||
|
|
||||||
// auto-advance fires the nearest timer (the 50ms idle deadline, sooner than this 500ms
|
|
||||||
// guard) once the task is otherwise idle, reaping the connection
|
|
||||||
tokio::time::timeout(Duration::from_millis(500), task)
|
|
||||||
.await
|
|
||||||
.expect("io_loop must close the connection after the idle timeout")
|
|
||||||
.expect("io_loop task must not panic");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// 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
|
||||||
|
|
||||||
use crate::metrics::PacketTrace;
|
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
use futures::channel::mpsc::SendError;
|
use futures::channel::mpsc::SendError;
|
||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
@@ -44,9 +43,6 @@ 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,
|
pub network_monitor_packet: bool,
|
||||||
/// Latency breadcrumb started at packet receive; stamped as the packet moves through the
|
|
||||||
/// forwarder and egress stages. `PacketTrace::Off` for untraced packets (e.g. acks).
|
|
||||||
pub trace: PacketTrace,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketToForward {
|
impl PacketToForward {
|
||||||
@@ -54,17 +50,15 @@ impl PacketToForward {
|
|||||||
packet: MixPacket,
|
packet: MixPacket,
|
||||||
forward_delay_target: Option<Instant>,
|
forward_delay_target: Option<Instant>,
|
||||||
network_monitor_packet: bool,
|
network_monitor_packet: bool,
|
||||||
trace: PacketTrace,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PacketToForward {
|
PacketToForward {
|
||||||
packet,
|
packet,
|
||||||
forward_delay_target,
|
forward_delay_target,
|
||||||
network_monitor_packet,
|
network_monitor_packet,
|
||||||
trace,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn client_packet_without_delay(packet: MixPacket) -> Self {
|
pub fn client_packet_without_delay(packet: MixPacket) -> Self {
|
||||||
Self::new(packet, None, false, PacketTrace::Off)
|
Self::new(packet, None, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod forwarder;
|
pub mod forwarder;
|
||||||
pub mod metrics;
|
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub use client::{Client, Config, SendWithoutResponse};
|
pub use client::{Client, Config, SendWithoutResponse};
|
||||||
|
|||||||
@@ -1,311 +0,0 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use strum::{AsRefStr, EnumIter, EnumProperty, IntoEnumIterator};
|
|
||||||
use tokio::time::Instant;
|
|
||||||
|
|
||||||
/// Histogram buckets (seconds) for per-stage and total packet latency: exponential, ~100us .. ~6.5s.
|
|
||||||
/// Shared by every latency stage so the waterfall is directly comparable; the top finite bucket is
|
|
||||||
/// intentionally high so a rare multi-second processing spike is measured with magnitude rather than
|
|
||||||
/// being clipped into the `+Inf` overflow.
|
|
||||||
const STAGE_LATENCY_BUCKETS: [f64; 17] = [
|
|
||||||
0.0001, 0.0002, 0.0004, 0.0008, 0.0016, 0.0032, 0.0064, 0.0128, 0.0256, 0.0512, 0.1024, 0.2048,
|
|
||||||
0.4096, 0.8192, 1.6384, 3.2768, 6.5536,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Count buckets (1 .. MAX_DRAIN_BATCH) for the forwarder drain-batch-size histogram.
|
|
||||||
const DRAIN_BATCH_BUCKETS: [f64; 9] = [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0];
|
|
||||||
|
|
||||||
/// Fill-ratio buckets (used/capacity) for the per-connection egress buffer. A ratio near 1.0 means
|
|
||||||
/// the buffer is close to full and packets to that peer are about to be dropped.
|
|
||||||
const EGRESS_FILL_BUCKETS: [f64; 9] = [0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 1.0];
|
|
||||||
|
|
||||||
/// Every histogram this crate emits, defined in one place. `AsRefStr` (`#[strum(to_string=...)]`)
|
|
||||||
/// gives the prometheus metric name - the bare `mixnet_packet_*` family, with no per-crate prefix
|
|
||||||
/// since this is a shared library writing straight to the process-global registry. The `help` prop
|
|
||||||
/// gives the description and [`MixnetMetric::buckets`] gives the bucket layout.
|
|
||||||
///
|
|
||||||
/// Register the whole family at boot with [`register_all`]. Latency-stage variants are observed via
|
|
||||||
/// the [`PacketTrace`] stopwatch; the auxiliary variants via the `observe_*` helpers. (Passing an
|
|
||||||
/// auxiliary variant to `PacketTrace::record` is meaningless but harmless.)
|
|
||||||
#[derive(Clone, Copy, EnumIter, AsRefStr, EnumProperty)]
|
|
||||||
pub enum MixnetMetric {
|
|
||||||
// ----- latency stages: the per-packet waterfall, recorded via `PacketTrace` -----
|
|
||||||
/// receive -> sphinx unwrap (partial: shared secret + header MAC)
|
|
||||||
#[strum(to_string = "mixnet_packet_stage_unwrap_seconds")]
|
|
||||||
#[strum(props(help = "Seconds spent unwrapping a received sphinx packet"))]
|
|
||||||
Unwrap,
|
|
||||||
/// unwrap -> replay-check + finalise (includes the deferral wait)
|
|
||||||
#[strum(to_string = "mixnet_packet_stage_replay_check_seconds")]
|
|
||||||
#[strum(props(
|
|
||||||
help = "Seconds from partial-unwrap to replay-check + finalise (includes the deferral wait)"
|
|
||||||
))]
|
|
||||||
ReplayCheck,
|
|
||||||
/// wait in the ingress -> forwarder channel
|
|
||||||
#[strum(to_string = "mixnet_packet_stage_forwarder_queue_seconds")]
|
|
||||||
#[strum(props(
|
|
||||||
help = "Seconds a forwarded packet waited in the ingress-to-forwarder channel"
|
|
||||||
))]
|
|
||||||
ForwarderQueue,
|
|
||||||
/// the (intended) mix delay
|
|
||||||
#[strum(to_string = "mixnet_packet_stage_delay_queue_seconds")]
|
|
||||||
#[strum(props(help = "Seconds a forwarded packet spent in the (intended) mix delay queue"))]
|
|
||||||
DelayQueue,
|
|
||||||
/// diagnostic overlay on `DelayQueue`: how late beyond the target release the packet was
|
|
||||||
/// actually forwarded (delay-queue scheduling/retrieval overhead, measured vs the deadline)
|
|
||||||
#[strum(to_string = "mixnet_packet_stage_delay_queue_overrun_seconds")]
|
|
||||||
#[strum(props(
|
|
||||||
help = "Seconds a delayed packet was forwarded beyond its target release time (delay-queue scheduling/retrieval overhead)"
|
|
||||||
))]
|
|
||||||
DelayQueueOverrun,
|
|
||||||
/// wait in the per-connection egress buffer
|
|
||||||
#[strum(to_string = "mixnet_packet_stage_egress_queue_seconds")]
|
|
||||||
#[strum(props(
|
|
||||||
help = "Seconds a forwarded packet waited in the per-connection egress buffer"
|
|
||||||
))]
|
|
||||||
EgressQueue,
|
|
||||||
/// flushing the packet batch to the socket
|
|
||||||
#[strum(to_string = "mixnet_packet_stage_socket_write_seconds")]
|
|
||||||
#[strum(props(help = "Seconds spent flushing a forwarded packet batch to the socket"))]
|
|
||||||
SocketWrite,
|
|
||||||
/// end-to-end: receive -> socket write
|
|
||||||
#[strum(to_string = "mixnet_packet_total_latency_seconds")]
|
|
||||||
#[strum(props(help = "Total in-node latency of a forwarded packet, receive to socket write"))]
|
|
||||||
Total,
|
|
||||||
|
|
||||||
// ----- auxiliary histograms: observed directly, not part of the latency waterfall -----
|
|
||||||
/// number of packets the forwarder drained from the ingress channel per wakeup
|
|
||||||
#[strum(to_string = "mixnet_packet_forwarder_drain_batch_size")]
|
|
||||||
#[strum(props(
|
|
||||||
help = "Number of ingress packets the forwarder drained per select! wakeup (batch size)"
|
|
||||||
))]
|
|
||||||
ForwarderDrainBatchSize,
|
|
||||||
/// number of expired packets the forwarder drained from the delay queue per wakeup
|
|
||||||
#[strum(to_string = "mixnet_packet_forwarder_delay_drain_batch_size")]
|
|
||||||
#[strum(props(
|
|
||||||
help = "Number of expired delay-queue packets the forwarder drained per select! wakeup (batch size)"
|
|
||||||
))]
|
|
||||||
ForwarderDelayDrainBatchSize,
|
|
||||||
/// per-connection egress buffer occupancy (used/capacity) at send time
|
|
||||||
#[strum(to_string = "mixnet_packet_egress_buffer_fill_ratio")]
|
|
||||||
#[strum(props(
|
|
||||||
help = "Per-connection egress buffer fill ratio (used/capacity) sampled at packet send time"
|
|
||||||
))]
|
|
||||||
EgressBufferFillRatio,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MixnetMetric {
|
|
||||||
/// Histogram bucket layout for this metric.
|
|
||||||
fn buckets(&self) -> &'static [f64] {
|
|
||||||
match self {
|
|
||||||
MixnetMetric::ForwarderDrainBatchSize | MixnetMetric::ForwarderDelayDrainBatchSize => {
|
|
||||||
&DRAIN_BATCH_BUCKETS
|
|
||||||
}
|
|
||||||
MixnetMetric::EgressBufferFillRatio => &EGRESS_FILL_BUCKETS,
|
|
||||||
// every latency stage shares the seconds buckets
|
|
||||||
_ => &STAGE_LATENCY_BUCKETS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pre-register every histogram (at zero) into the global metrics registry so the whole
|
|
||||||
/// `mixnet_packet_*` family is present on the prometheus endpoint from boot, before anything has
|
|
||||||
/// been observed. Idempotent.
|
|
||||||
pub fn register_all() {
|
|
||||||
let registry = nym_metrics::metrics_registry();
|
|
||||||
for metric in MixnetMetric::iter() {
|
|
||||||
registry.register_histogram(
|
|
||||||
metric.as_ref(),
|
|
||||||
metric.get_str("help"),
|
|
||||||
Some(metric.buckets()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe a value into a metric's histogram in the process-global registry.
|
|
||||||
fn observe(metric: MixnetMetric, value: f64) {
|
|
||||||
nym_metrics::metrics_registry().maybe_register_and_add_to_histogram(
|
|
||||||
metric.as_ref(),
|
|
||||||
value,
|
|
||||||
Some(metric.buckets()),
|
|
||||||
metric.get_str("help"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe how many ingress-channel packets the forwarder drained in a single wakeup.
|
|
||||||
pub fn observe_drain_batch_size(batch_size: usize) {
|
|
||||||
observe(MixnetMetric::ForwarderDrainBatchSize, batch_size as f64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe how many expired delay-queue packets the forwarder drained in a single wakeup.
|
|
||||||
pub fn observe_delay_drain_batch_size(batch_size: usize) {
|
|
||||||
observe(
|
|
||||||
MixnetMetric::ForwarderDelayDrainBatchSize,
|
|
||||||
batch_size as f64,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe how full a per-connection egress buffer was when a packet was queued for it.
|
|
||||||
pub fn observe_egress_buffer_fill(used: usize, capacity: usize) {
|
|
||||||
if capacity == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
observe(
|
|
||||||
MixnetMetric::EgressBufferFillRatio,
|
|
||||||
used as f64 / capacity as f64,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight per-packet stopwatch for attributing forwarding latency to pipeline
|
|
||||||
/// stages. Unsampled packets carry the `Off` variant and do zero clock reads, so the only
|
|
||||||
/// cost on the hot path is moving a small `Copy` value and a branch.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum PacketTrace {
|
|
||||||
Off,
|
|
||||||
On {
|
|
||||||
received_at: Instant,
|
|
||||||
stage_at: Instant,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketTrace {
|
|
||||||
/// Begin tracing. Reads the clock only for sampled packets.
|
|
||||||
pub fn start(sampled: bool) -> Self {
|
|
||||||
if sampled {
|
|
||||||
let now = Instant::now();
|
|
||||||
PacketTrace::On {
|
|
||||||
received_at: now,
|
|
||||||
stage_at: now,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PacketTrace::Off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this packet is being traced (sampled).
|
|
||||||
pub fn is_sampled(&self) -> bool {
|
|
||||||
matches!(self, PacketTrace::On { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Seconds spent in the stage just completed, advancing the cursor to now.
|
|
||||||
/// Returns `None` for unsampled packets.
|
|
||||||
fn lap(&mut self) -> Option<f64> {
|
|
||||||
match self {
|
|
||||||
PacketTrace::Off => None,
|
|
||||||
PacketTrace::On { stage_at, .. } => {
|
|
||||||
let now = Instant::now();
|
|
||||||
let secs = now.duration_since(*stage_at).as_secs_f64();
|
|
||||||
*stage_at = now;
|
|
||||||
Some(secs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Seconds since tracing began (i.e. since the packet was received), or `None` if unsampled.
|
|
||||||
fn total(&self) -> Option<f64> {
|
|
||||||
match self {
|
|
||||||
PacketTrace::Off => None,
|
|
||||||
PacketTrace::On { received_at, .. } => {
|
|
||||||
Some(Instant::now().duration_since(*received_at).as_secs_f64())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Close out the stage just completed: lap the timer and, only if the packet is sampled,
|
|
||||||
/// observe `stage`'s latency histogram.
|
|
||||||
pub fn record(&mut self, stage: MixnetMetric) {
|
|
||||||
if let Some(secs) = self.lap() {
|
|
||||||
observe(stage, secs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe the end-to-end [`MixnetMetric::Total`] latency (since receive) if sampled. Unlike
|
|
||||||
/// [`PacketTrace::record`] this does not lap, so it can be called at the very end.
|
|
||||||
pub fn record_total(&self) {
|
|
||||||
if let Some(secs) = self.total() {
|
|
||||||
observe(MixnetMetric::Total, secs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe an explicit `secs` value for `stage` if the packet is sampled, without lapping the
|
|
||||||
/// stage cursor. For diagnostics that don't fit the sequential waterfall (e.g. delay-queue
|
|
||||||
/// overrun, measured against the target deadline rather than the previous stage).
|
|
||||||
pub fn record_value(&self, stage: MixnetMetric, secs: f64) {
|
|
||||||
if matches!(self, PacketTrace::On { .. }) {
|
|
||||||
observe(stage, secs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A value paired with its in-flight latency trace, so the trace rides along as the value is
|
|
||||||
/// moved between pipeline stages (and transformed via [`Traced::map`]). Used wherever a packet
|
|
||||||
/// crosses a queue/channel: replay batch, delay queue, egress channel.
|
|
||||||
pub struct Traced<T> {
|
|
||||||
pub inner: T,
|
|
||||||
pub trace: PacketTrace,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Traced<T> {
|
|
||||||
pub fn new(inner: T, trace: PacketTrace) -> Self {
|
|
||||||
Traced { inner, trace }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transform the carried value, keeping the same trace.
|
|
||||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Traced<U> {
|
|
||||||
Traced {
|
|
||||||
inner: f(self.inner),
|
|
||||||
trace: self.trace,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Record the stage just completed for the carried trace (see [`PacketTrace::record`]).
|
|
||||||
pub fn record(&mut self, stage: MixnetMetric) {
|
|
||||||
self.trace.record(stage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe an explicit value for the carried trace (see [`PacketTrace::record_value`]).
|
|
||||||
pub fn record_value(&self, stage: MixnetMetric, secs: f64) {
|
|
||||||
self.trace.record_value(stage, secs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// guards that AsRefStr honours `#[strum(to_string = ...)]` (rather than falling back to the
|
|
||||||
// variant name), that every metric is in the `mixnet_packet_*` family, and carries a help
|
|
||||||
// string, and that each metric resolves to a bucket layout.
|
|
||||||
#[test]
|
|
||||||
fn every_metric_has_a_mixnet_packet_name_help_and_buckets() {
|
|
||||||
for metric in MixnetMetric::iter() {
|
|
||||||
assert!(
|
|
||||||
metric.as_ref().starts_with("mixnet_packet_"),
|
|
||||||
"unexpected metric name: {}",
|
|
||||||
metric.as_ref()
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
metric.get_str("help").is_some(),
|
|
||||||
"missing help for {}",
|
|
||||||
metric.as_ref()
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!metric.buckets().is_empty(),
|
|
||||||
"missing buckets for {}",
|
|
||||||
metric.as_ref()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
MixnetMetric::Unwrap.as_ref(),
|
|
||||||
"mixnet_packet_stage_unwrap_seconds"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
MixnetMetric::Total.as_ref(),
|
|
||||||
"mixnet_packet_total_latency_seconds"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
MixnetMetric::ForwarderDrainBatchSize.as_ref(),
|
|
||||||
"mixnet_packet_forwarder_drain_batch_size"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,6 @@ 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-network-monitors-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"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ use nym_api_requests::ecash::VerificationKeyResponse;
|
|||||||
use nym_api_requests::models::network_monitor::{
|
use nym_api_requests::models::network_monitor::{
|
||||||
KnownNetworkMonitorResponse, StressTestBatchSubmission,
|
KnownNetworkMonitorResponse, StressTestBatchSubmission,
|
||||||
};
|
};
|
||||||
use nym_api_requests::models::node_families::NodeFamily;
|
|
||||||
use nym_api_requests::models::{
|
use nym_api_requests::models::{
|
||||||
AnnotationResponseV1, AnnotationResponseV2, ApiHealthResponse, BinaryBuildInformationOwned,
|
AnnotationResponseV1, ApiHealthResponse, BinaryBuildInformationOwned,
|
||||||
ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse,
|
ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse,
|
||||||
NodePerformanceResponse, NodeRefreshBody, NymNodeDescriptionV1, NymNodeDescriptionV2,
|
NodePerformanceResponse, NodeRefreshBody, NymNodeDescriptionV1, NymNodeDescriptionV2,
|
||||||
PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse,
|
PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse,
|
||||||
@@ -394,45 +393,6 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
Ok(bonds)
|
Ok(bonds)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
|
||||||
async fn get_node_families(
|
|
||||||
&self,
|
|
||||||
page: Option<u32>,
|
|
||||||
per_page: Option<u32>,
|
|
||||||
) -> Result<PaginatedResponse<NodeFamily>, NymAPIError> {
|
|
||||||
let mut params = Vec::new();
|
|
||||||
if let Some(page) = page {
|
|
||||||
params.push(("page", page.to_string()))
|
|
||||||
}
|
|
||||||
if let Some(per_page) = per_page {
|
|
||||||
params.push(("per_page", per_page.to_string()))
|
|
||||||
}
|
|
||||||
self.get_json(
|
|
||||||
&[routes::V1_API_VERSION, routes::NODE_FAMILIES_ROUTES],
|
|
||||||
¶ms,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_node_families(&self) -> Result<Vec<NodeFamily>, NymAPIError> {
|
|
||||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
|
||||||
let mut page = 0;
|
|
||||||
let mut families = Vec::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut res = self.get_node_families(Some(page), None).await?;
|
|
||||||
|
|
||||||
families.append(&mut res.data);
|
|
||||||
if families.len() < res.pagination.total {
|
|
||||||
page += 1
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(families)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deprecated]
|
#[deprecated]
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
|
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
|
||||||
@@ -1033,22 +993,6 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_node_annotation_v2(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
) -> Result<AnnotationResponseV2, NymAPIError> {
|
|
||||||
self.get_json(
|
|
||||||
&[
|
|
||||||
routes::V2_API_VERSION,
|
|
||||||
routes::NYM_NODES_ROUTES,
|
|
||||||
routes::NYM_NODES_ANNOTATION,
|
|
||||||
&node_id.to_string(),
|
|
||||||
],
|
|
||||||
NO_PARAMS,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deprecated]
|
#[deprecated]
|
||||||
async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
|
async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
|
||||||
self.get_json(
|
self.get_json(
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ pub mod ecash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const NYM_NODES_ROUTES: &str = "nym-nodes";
|
pub const NYM_NODES_ROUTES: &str = "nym-nodes";
|
||||||
pub const NODE_FAMILIES_ROUTES: &str = "node-families";
|
|
||||||
|
|
||||||
pub use nym_nodes::*;
|
pub use nym_nodes::*;
|
||||||
pub mod nym_nodes {
|
pub mod nym_nodes {
|
||||||
|
|||||||
@@ -867,10 +867,6 @@ mod tests {
|
|||||||
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
||||||
client.testing_resolve_all_pending_events(None).ignore()
|
client.testing_resolve_all_pending_events(None).ignore()
|
||||||
}
|
}
|
||||||
// not expected to be exposed by the client
|
|
||||||
ExecuteMsg::AdminMigrateVestedMixNode { .. }
|
|
||||||
| ExecuteMsg::AdminMigrateVestedDelegation { .. }
|
|
||||||
| ExecuteMsg::AdminBatchMigrateVestedDelegations { .. } => ().ignore(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ 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 network_monitors_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;
|
||||||
|
|
||||||
@@ -25,7 +24,6 @@ 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 network_monitors_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;
|
||||||
|
|
||||||
@@ -38,7 +36,6 @@ pub use multisig_query_client::{MultisigQueryClient, PagedMultisigQueryClient};
|
|||||||
pub use network_monitors_query_client::{
|
pub use network_monitors_query_client::{
|
||||||
NetworkMonitorsQueryClient, PagedNetworkMonitorsQueryClient,
|
NetworkMonitorsQueryClient, PagedNetworkMonitorsQueryClient,
|
||||||
};
|
};
|
||||||
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};
|
||||||
|
|
||||||
@@ -49,7 +46,6 @@ 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 network_monitors_signing_client::NetworkMonitorsSigningClient;
|
||||||
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;
|
||||||
|
|
||||||
@@ -60,7 +56,6 @@ pub trait NymContractsProvider {
|
|||||||
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 network_monitors_contract_address(&self) -> Option<&AccountId>;
|
||||||
fn node_families_contract_address(&self) -> Option<&AccountId>;
|
|
||||||
|
|
||||||
// coconut-related
|
// coconut-related
|
||||||
fn ecash_contract_address(&self) -> Option<&AccountId>;
|
fn ecash_contract_address(&self) -> Option<&AccountId>;
|
||||||
@@ -75,7 +70,6 @@ pub struct TypedNymContracts {
|
|||||||
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 network_monitors_contract_address: Option<AccountId>,
|
||||||
pub node_families_contract_address: Option<AccountId>,
|
|
||||||
|
|
||||||
pub ecash_contract_address: Option<AccountId>,
|
pub ecash_contract_address: Option<AccountId>,
|
||||||
pub group_contract_address: Option<AccountId>,
|
pub group_contract_address: Option<AccountId>,
|
||||||
@@ -104,10 +98,6 @@ impl TryFrom<NymContracts> for TypedNymContracts {
|
|||||||
.network_monitors_contract_address
|
.network_monitors_contract_address
|
||||||
.map(|addr| addr.parse())
|
.map(|addr| addr.parse())
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
node_families_contract_address: value
|
|
||||||
.node_families_contract_address
|
|
||||||
.map(|addr| addr.parse())
|
|
||||||
.transpose()?,
|
|
||||||
ecash_contract_address: value
|
ecash_contract_address: value
|
||||||
.ecash_contract_address
|
.ecash_contract_address
|
||||||
.map(|addr| addr.parse())
|
.map(|addr| addr.parse())
|
||||||
|
|||||||
-447
@@ -1,447 +0,0 @@
|
|||||||
// 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 cosmrs::AccountId;
|
|
||||||
use nym_mixnet_contract_common::NodeId;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
pub use nym_node_families_contract_common::{
|
|
||||||
msg::QueryMsg as NodeFamiliesQueryMsg, AllFamilyMembersPagedResponse,
|
|
||||||
AllPastFamilyInvitationsPagedResponse, Config, FamiliesPagedResponse, FamilyMemberRecord,
|
|
||||||
FamilyMembersPagedResponse, GlobalPastFamilyInvitationCursor, NodeFamily,
|
|
||||||
NodeFamilyByNameResponse, NodeFamilyByOwnerResponse, NodeFamilyId,
|
|
||||||
NodeFamilyMembershipResponse, NodeFamilyResponse, PastFamilyInvitation,
|
|
||||||
PastFamilyInvitationCursor, PastFamilyInvitationForNodeCursor,
|
|
||||||
PastFamilyInvitationsForNodePagedResponse, PastFamilyInvitationsPagedResponse,
|
|
||||||
PastFamilyMember, PastFamilyMemberCursor, PastFamilyMemberForNodeCursor,
|
|
||||||
PastFamilyMembersForNodePagedResponse, PastFamilyMembersPagedResponse,
|
|
||||||
PendingFamilyInvitationDetails, PendingFamilyInvitationResponse,
|
|
||||||
PendingFamilyInvitationsPagedResponse, PendingInvitationsForNodePagedResponse,
|
|
||||||
PendingInvitationsPagedResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
|
||||||
pub trait NodeFamiliesQueryClient {
|
|
||||||
async fn query_node_families_contract<T>(
|
|
||||||
&self,
|
|
||||||
query: NodeFamiliesQueryMsg,
|
|
||||||
) -> Result<T, NyxdError>
|
|
||||||
where
|
|
||||||
for<'a> T: Deserialize<'a>;
|
|
||||||
|
|
||||||
async fn get_config(&self) -> Result<Config, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetConfig {})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_family_by_id(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
) -> Result<NodeFamilyResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetFamilyById { family_id })
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_family_by_owner(
|
|
||||||
&self,
|
|
||||||
owner: &AccountId,
|
|
||||||
) -> Result<NodeFamilyByOwnerResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetFamilyByOwner {
|
|
||||||
owner: owner.to_string(),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_family_by_name(
|
|
||||||
&self,
|
|
||||||
name: String,
|
|
||||||
) -> Result<NodeFamilyByNameResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetFamilyByName { name })
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_families_paged(
|
|
||||||
&self,
|
|
||||||
start_after: Option<NodeFamilyId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<FamiliesPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetFamiliesPaged {
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_family_membership(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
) -> Result<NodeFamilyMembershipResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetFamilyMembership { node_id })
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_family_members_paged(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<NodeId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<FamilyMembersPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetFamilyMembersPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_family_members_paged(
|
|
||||||
&self,
|
|
||||||
start_after: Option<NodeId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<AllFamilyMembersPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetAllFamilyMembersPaged {
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_pending_invitation(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
) -> Result<PendingFamilyInvitationResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetPendingInvitation {
|
|
||||||
family_id,
|
|
||||||
node_id,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_pending_invitations_for_family_paged(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<NodeId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<PendingFamilyInvitationsPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(
|
|
||||||
NodeFamiliesQueryMsg::GetPendingInvitationsForFamilyPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_pending_invitations_for_node_paged(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
start_after: Option<NodeFamilyId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<PendingInvitationsForNodePagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetPendingInvitationsForNodePaged {
|
|
||||||
node_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_pending_invitations_paged(
|
|
||||||
&self,
|
|
||||||
start_after: Option<(NodeFamilyId, NodeId)>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<PendingInvitationsPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetAllPendingInvitationsPaged {
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_past_invitations_for_family_paged(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<PastFamilyInvitationCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<PastFamilyInvitationsPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetPastInvitationsForFamilyPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_past_invitations_for_node_paged(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
start_after: Option<PastFamilyInvitationForNodeCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<PastFamilyInvitationsForNodePagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetPastInvitationsForNodePaged {
|
|
||||||
node_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_past_invitations_paged(
|
|
||||||
&self,
|
|
||||||
start_after: Option<GlobalPastFamilyInvitationCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<AllPastFamilyInvitationsPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetAllPastInvitationsPaged {
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_past_members_for_family_paged(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<PastFamilyMemberCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<PastFamilyMembersPagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetPastMembersForFamilyPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_past_members_for_node_paged(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
start_after: Option<PastFamilyMemberForNodeCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<PastFamilyMembersForNodePagedResponse, NyxdError> {
|
|
||||||
self.query_node_families_contract(NodeFamiliesQueryMsg::GetPastMembersForNodePaged {
|
|
||||||
node_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extension trait to the query client to deal with the paged queries
|
|
||||||
// (it didn't feel appropriate to combine it with the existing trait)
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
|
||||||
pub trait PagedNodeFamiliesQueryClient: NodeFamiliesQueryClient {
|
|
||||||
async fn get_all_families(&self) -> Result<Vec<NodeFamily>, NyxdError> {
|
|
||||||
collect_paged!(self, get_families_paged, families)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_family_members_for_family(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
) -> Result<Vec<FamilyMemberRecord>, NyxdError> {
|
|
||||||
collect_paged!(self, get_family_members_paged, members, family_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_family_members(&self) -> Result<Vec<FamilyMemberRecord>, NyxdError> {
|
|
||||||
collect_paged!(self, get_all_family_members_paged, members)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_pending_invitations_for_family(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
) -> Result<Vec<PendingFamilyInvitationDetails>, NyxdError> {
|
|
||||||
collect_paged!(
|
|
||||||
self,
|
|
||||||
get_pending_invitations_for_family_paged,
|
|
||||||
invitations,
|
|
||||||
family_id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_pending_invitations_for_node(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
) -> Result<Vec<PendingFamilyInvitationDetails>, NyxdError> {
|
|
||||||
collect_paged!(
|
|
||||||
self,
|
|
||||||
get_pending_invitations_for_node_paged,
|
|
||||||
invitations,
|
|
||||||
node_id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_pending_invitations(
|
|
||||||
&self,
|
|
||||||
) -> Result<Vec<PendingFamilyInvitationDetails>, NyxdError> {
|
|
||||||
collect_paged!(self, get_all_pending_invitations_paged, invitations)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_past_invitations_for_family(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
) -> Result<Vec<PastFamilyInvitation>, NyxdError> {
|
|
||||||
collect_paged!(
|
|
||||||
self,
|
|
||||||
get_past_invitations_for_family_paged,
|
|
||||||
invitations,
|
|
||||||
family_id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_past_invitations_for_node(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
) -> Result<Vec<PastFamilyInvitation>, NyxdError> {
|
|
||||||
collect_paged!(
|
|
||||||
self,
|
|
||||||
get_past_invitations_for_node_paged,
|
|
||||||
invitations,
|
|
||||||
node_id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_past_invitations(&self) -> Result<Vec<PastFamilyInvitation>, NyxdError> {
|
|
||||||
collect_paged!(self, get_all_past_invitations_paged, invitations)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_past_members_for_family(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
) -> Result<Vec<PastFamilyMember>, NyxdError> {
|
|
||||||
collect_paged!(self, get_past_members_for_family_paged, members, family_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_all_past_members_for_node(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
) -> Result<Vec<PastFamilyMember>, NyxdError> {
|
|
||||||
collect_paged!(self, get_past_members_for_node_paged, members, node_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<T> PagedNodeFamiliesQueryClient for T where T: NodeFamiliesQueryClient {}
|
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
|
||||||
impl<C> NodeFamiliesQueryClient for C
|
|
||||||
where
|
|
||||||
C: CosmWasmClient + NymContractsProvider + Send + Sync,
|
|
||||||
{
|
|
||||||
async fn query_node_families_contract<T>(
|
|
||||||
&self,
|
|
||||||
query: NodeFamiliesQueryMsg,
|
|
||||||
) -> Result<T, NyxdError>
|
|
||||||
where
|
|
||||||
for<'a> T: Deserialize<'a>,
|
|
||||||
{
|
|
||||||
let node_families_contract_address = &self
|
|
||||||
.node_families_contract_address()
|
|
||||||
.ok_or_else(|| NyxdError::unavailable_contract_address("node families contract"))?;
|
|
||||||
self.query_contract_smart(node_families_contract_address, &query)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
|
||||||
use nym_node_families_contract_common::QueryMsg;
|
|
||||||
|
|
||||||
// it's enough that this compiles and clippy is happy about it
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn all_query_variants_are_covered<C: NodeFamiliesQueryClient + Send + Sync>(
|
|
||||||
client: C,
|
|
||||||
msg: NodeFamiliesQueryMsg,
|
|
||||||
) {
|
|
||||||
match msg {
|
|
||||||
NodeFamiliesQueryMsg::GetConfig {} => client.get_config().ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetFamilyById { family_id } => {
|
|
||||||
client.get_family_by_id(family_id).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesQueryMsg::GetFamilyByOwner { owner } => {
|
|
||||||
client.get_family_by_owner(&owner.parse().unwrap()).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesQueryMsg::GetFamilyByName { name } => {
|
|
||||||
client.get_family_by_name(name).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesQueryMsg::GetFamiliesPaged { start_after, limit } => {
|
|
||||||
client.get_families_paged(start_after, limit).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesQueryMsg::GetFamilyMembership { node_id } => {
|
|
||||||
client.get_family_membership(node_id).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesQueryMsg::GetFamilyMembersPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
} => client
|
|
||||||
.get_family_members_paged(family_id, start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetAllFamilyMembersPaged { start_after, limit } => client
|
|
||||||
.get_all_family_members_paged(start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetPendingInvitation { family_id, node_id } => {
|
|
||||||
client.get_pending_invitation(family_id, node_id).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesQueryMsg::GetPendingInvitationsForFamilyPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
} => client
|
|
||||||
.get_pending_invitations_for_family_paged(family_id, start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetPendingInvitationsForNodePaged {
|
|
||||||
node_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
} => client
|
|
||||||
.get_pending_invitations_for_node_paged(node_id, start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetAllPendingInvitationsPaged { start_after, limit } => client
|
|
||||||
.get_all_pending_invitations_paged(start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetPastInvitationsForFamilyPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
} => client
|
|
||||||
.get_past_invitations_for_family_paged(family_id, start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetPastInvitationsForNodePaged {
|
|
||||||
node_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
} => client
|
|
||||||
.get_past_invitations_for_node_paged(node_id, start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetAllPastInvitationsPaged { start_after, limit } => client
|
|
||||||
.get_all_past_invitations_paged(start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesQueryMsg::GetPastMembersForFamilyPaged {
|
|
||||||
family_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
} => client
|
|
||||||
.get_past_members_for_family_paged(family_id, start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
QueryMsg::GetPastMembersForNodePaged {
|
|
||||||
node_id,
|
|
||||||
start_after,
|
|
||||||
limit,
|
|
||||||
} => client
|
|
||||||
.get_past_members_for_node_paged(node_id, start_after, limit)
|
|
||||||
.ignore(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-281
@@ -1,281 +0,0 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use crate::nyxd::coin::Coin;
|
|
||||||
use crate::nyxd::contract_traits::NymContractsProvider;
|
|
||||||
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
|
|
||||||
use crate::nyxd::error::NyxdError;
|
|
||||||
use crate::nyxd::{Fee, SigningCosmWasmClient};
|
|
||||||
use crate::signing::signer::OfflineSigner;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use nym_mixnet_contract_common::NodeId;
|
|
||||||
use nym_node_families_contract_common::{
|
|
||||||
Config, ExecuteMsg as NodeFamiliesExecuteMsg, NodeFamilyId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
|
||||||
pub trait NodeFamiliesSigningClient {
|
|
||||||
async fn execute_node_families_contract(
|
|
||||||
&self,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
msg: NodeFamiliesExecuteMsg,
|
|
||||||
memo: String,
|
|
||||||
funds: Vec<Coin>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError>;
|
|
||||||
|
|
||||||
async fn update_node_families_config(
|
|
||||||
&self,
|
|
||||||
config: Config,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::UpdateConfig { config },
|
|
||||||
"NodeFamiliesContract::UpdateConfig".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_family(
|
|
||||||
&self,
|
|
||||||
name: String,
|
|
||||||
description: String,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
creation_fee: Vec<Coin>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::CreateFamily { name, description },
|
|
||||||
"NodeFamiliesContract::CreateFamily".to_string(),
|
|
||||||
creation_fee,
|
|
||||||
)
|
|
||||||
.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> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::DisbandFamily {},
|
|
||||||
"NodeFamiliesContract::DisbandFamily".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn invite_to_family(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
validity_secs: Option<u64>,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::InviteToFamily {
|
|
||||||
node_id,
|
|
||||||
validity_secs,
|
|
||||||
},
|
|
||||||
"NodeFamiliesContract::InviteToFamily".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn revoke_family_invitation(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::RevokeFamilyInvitation { node_id },
|
|
||||||
"NodeFamiliesContract::RevokeFamilyInvitation".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn accept_family_invitation(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::AcceptFamilyInvitation { family_id, node_id },
|
|
||||||
"NodeFamiliesContract::AcceptFamilyInvitation".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn reject_family_invitation(
|
|
||||||
&self,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::RejectFamilyInvitation { family_id, node_id },
|
|
||||||
"NodeFamiliesContract::RejectFamilyInvitation".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn leave_family(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::LeaveFamily { node_id },
|
|
||||||
"NodeFamiliesContract::LeaveFamily".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn kick_from_family(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::KickFromFamily { node_id },
|
|
||||||
"NodeFamiliesContract::KickFromFamily".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cross-contract callback fired by the mixnet contract on node unbonding.
|
|
||||||
/// Exposed for completeness; the families contract rejects this call from
|
|
||||||
/// any sender other than the configured mixnet contract address.
|
|
||||||
async fn on_nym_node_unbond(
|
|
||||||
&self,
|
|
||||||
node_id: NodeId,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
self.execute_node_families_contract(
|
|
||||||
fee,
|
|
||||||
NodeFamiliesExecuteMsg::OnNymNodeUnbond { node_id },
|
|
||||||
"NodeFamiliesContract::OnNymNodeUnbond".to_string(),
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
|
||||||
impl<C> NodeFamiliesSigningClient for C
|
|
||||||
where
|
|
||||||
C: SigningCosmWasmClient + NymContractsProvider + Sync,
|
|
||||||
NyxdError: From<<Self as OfflineSigner>::Error>,
|
|
||||||
{
|
|
||||||
async fn execute_node_families_contract(
|
|
||||||
&self,
|
|
||||||
fee: Option<Fee>,
|
|
||||||
msg: NodeFamiliesExecuteMsg,
|
|
||||||
memo: String,
|
|
||||||
funds: Vec<Coin>,
|
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
let node_families_contract_address = &self
|
|
||||||
.node_families_contract_address()
|
|
||||||
.ok_or_else(|| NyxdError::unavailable_contract_address("node families contract"))?;
|
|
||||||
|
|
||||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
|
||||||
|
|
||||||
let signer_address = &self.signer_addresses()[0];
|
|
||||||
self.execute(
|
|
||||||
signer_address,
|
|
||||||
node_families_contract_address,
|
|
||||||
&msg,
|
|
||||||
fee,
|
|
||||||
memo,
|
|
||||||
funds,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
|
||||||
use nym_node_families_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: NodeFamiliesSigningClient + Send + Sync>(
|
|
||||||
client: C,
|
|
||||||
msg: NodeFamiliesExecuteMsg,
|
|
||||||
) {
|
|
||||||
match msg {
|
|
||||||
NodeFamiliesExecuteMsg::UpdateConfig { config } => {
|
|
||||||
client.update_node_families_config(config, None).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesExecuteMsg::CreateFamily { name, description } => client
|
|
||||||
.create_family(name, description, None, vec![])
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesExecuteMsg::UpdateFamily {
|
|
||||||
updated_name,
|
|
||||||
updated_description,
|
|
||||||
} => client
|
|
||||||
.update_family(updated_name, updated_description, None)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesExecuteMsg::DisbandFamily {} => client.disband_family(None).ignore(),
|
|
||||||
NodeFamiliesExecuteMsg::InviteToFamily {
|
|
||||||
node_id,
|
|
||||||
validity_secs,
|
|
||||||
} => client
|
|
||||||
.invite_to_family(node_id, validity_secs, None)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesExecuteMsg::RevokeFamilyInvitation { node_id } => {
|
|
||||||
client.revoke_family_invitation(node_id, None).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesExecuteMsg::AcceptFamilyInvitation { family_id, node_id } => client
|
|
||||||
.accept_family_invitation(family_id, node_id, None)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesExecuteMsg::RejectFamilyInvitation { family_id, node_id } => client
|
|
||||||
.reject_family_invitation(family_id, node_id, None)
|
|
||||||
.ignore(),
|
|
||||||
NodeFamiliesExecuteMsg::LeaveFamily { node_id } => {
|
|
||||||
client.leave_family(node_id, None).ignore()
|
|
||||||
}
|
|
||||||
NodeFamiliesExecuteMsg::KickFromFamily { node_id } => {
|
|
||||||
client.kick_from_family(node_id, None).ignore()
|
|
||||||
}
|
|
||||||
ExecuteMsg::OnNymNodeUnbond { node_id } => {
|
|
||||||
client.on_nym_node_unbond(node_id, None).ignore()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -304,10 +304,6 @@ impl<C, S> NyxdClient<C, S> {
|
|||||||
self.config.contracts.multisig_contract_address = Some(address);
|
self.config.contracts.multisig_contract_address = Some(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_node_families_contract_address(&mut self, address: AccountId) {
|
|
||||||
self.config.contracts.node_families_contract_address = Some(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -336,13 +332,6 @@ impl<C, S> NymContractsProvider for NyxdClient<C, S> {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_families_contract_address(&self) -> Option<&AccountId> {
|
|
||||||
self.config
|
|
||||||
.contracts
|
|
||||||
.node_families_contract_address
|
|
||||||
.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ecash_contract_address(&self) -> Option<&AccountId> {
|
fn ecash_contract_address(&self) -> Option<&AccountId> {
|
||||||
self.config.contracts.ecash_contract_address.as_ref()
|
self.config.contracts.ecash_contract_address.as_ref()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ pub struct Args {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub vesting_contract_address: Option<AccountId>,
|
pub vesting_contract_address: Option<AccountId>,
|
||||||
|
|
||||||
#[clap(long)]
|
|
||||||
pub node_families_contract_address: Option<AccountId>,
|
|
||||||
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub rewarding_denom: Option<String>,
|
pub rewarding_denom: Option<String>,
|
||||||
|
|
||||||
@@ -133,14 +130,6 @@ pub async fn generate(args: Args) {
|
|||||||
.expect("Failed converting vesting contract address to AccountId")
|
.expect("Failed converting vesting contract address to AccountId")
|
||||||
});
|
});
|
||||||
|
|
||||||
let node_families_contract_address = args.node_families_contract_address.unwrap_or_else(|| {
|
|
||||||
let address =
|
|
||||||
std::env::var(nym_network_defaults::var_names::NODE_FAMILIES_CONTRACT_ADDRESS)
|
|
||||||
.expect("node families contract address has to be set");
|
|
||||||
AccountId::from_str(address.as_str())
|
|
||||||
.expect("Failed converting node families contract address to AccountId")
|
|
||||||
});
|
|
||||||
|
|
||||||
let rewarding_denom = args.rewarding_denom.unwrap_or_else(|| {
|
let rewarding_denom = args.rewarding_denom.unwrap_or_else(|| {
|
||||||
std::env::var(nym_network_defaults::var_names::MIX_DENOM)
|
std::env::var(nym_network_defaults::var_names::MIX_DENOM)
|
||||||
.expect("Rewarding (mix) denom has to be set")
|
.expect("Rewarding (mix) denom has to be set")
|
||||||
@@ -153,7 +142,6 @@ pub async fn generate(args: Args) {
|
|||||||
let instantiate_msg = InstantiateMsg {
|
let instantiate_msg = InstantiateMsg {
|
||||||
rewarding_validator_address: rewarding_validator_address.to_string(),
|
rewarding_validator_address: rewarding_validator_address.to_string(),
|
||||||
vesting_contract_address: vesting_contract_address.to_string(),
|
vesting_contract_address: vesting_contract_address.to_string(),
|
||||||
node_families_contract_address: node_families_contract_address.to_string(),
|
|
||||||
rewarding_denom,
|
rewarding_denom,
|
||||||
epochs_in_interval: args.epochs_in_interval,
|
epochs_in_interval: args.epochs_in_interval,
|
||||||
epoch_duration: Duration::from_secs(args.epoch_duration),
|
epoch_duration: Duration::from_secs(args.epoch_duration),
|
||||||
|
|||||||
@@ -26,14 +26,6 @@ pub trait ContractOpts {
|
|||||||
|
|
||||||
fn addr_make(&self, input: &str) -> Addr;
|
fn addr_make(&self, input: &str) -> Addr;
|
||||||
|
|
||||||
fn make_sender_with_funds(&self, input: &str, funds: &[Coin]) -> MessageInfo {
|
|
||||||
message_info(&self.addr_make(input), funds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_sender(&self, input: &str) -> MessageInfo {
|
|
||||||
self.make_sender_with_funds(input, &[])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deps_mut_env(&mut self) -> (DepsMut<'_>, Env) {
|
fn deps_mut_env(&mut self) -> (DepsMut<'_>, Env) {
|
||||||
let env = self.env().clone();
|
let env = self.env().clone();
|
||||||
(self.deps_mut(), env)
|
(self.deps_mut(), env)
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
// 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,
|
||||||
@@ -21,8 +15,6 @@ 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,
|
||||||
@@ -44,8 +36,6 @@ 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>,
|
||||||
@@ -69,8 +59,6 @@ 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,9 +4,6 @@
|
|||||||
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,13 +5,8 @@ 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,
|
||||||
@@ -24,8 +19,6 @@ 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)
|
||||||
@@ -39,13 +32,10 @@ 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"));
|
||||||
@@ -57,16 +47,12 @@ 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,
|
||||||
@@ -74,8 +60,6 @@ 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,
|
||||||
@@ -89,8 +73,6 @@ 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,108 +6,69 @@ 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,
|
||||||
|
|
||||||
/// Redemption-proposal reply found a `proposal_id` attribute that could
|
// realistically this should NEVER be thrown
|
||||||
/// 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})"
|
||||||
)]
|
)]
|
||||||
@@ -116,13 +77,9 @@ 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,7 +1,4 @@
|
|||||||
// 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,21 +1,9 @@
|
|||||||
// 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 names and attribute keys emitted by the ecash contract. Renaming any
|
// event types
|
||||||
//! 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,12 +1,6 @@
|
|||||||
// 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,134 +15,100 @@ 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 {
|
||||||
/// Submitted by clients to escrow funds and register a claimed ed25519
|
/// Used by clients to request ticket books from the signers
|
||||||
/// identity key. Mints a sequential `deposit_id`. The contract does not
|
DepositTicketBookFunds {
|
||||||
/// verify control of the identity key - that proof is checked off-chain by
|
identity_key: String,
|
||||||
/// nym-api signers at blind-sign time.
|
},
|
||||||
DepositTicketBookFunds { identity_key: String },
|
|
||||||
|
|
||||||
/// Submitted by gateways to request batch redemption of spent tickets.
|
/// Used by gateways to batch redeem tokens from the 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,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// **Legacy / dead code.** Only callable by the multisig; bumps the
|
/// The actual message that gets executed, after multisig votes, that transfers the ticket tokens into gateway's (and the holding) account
|
||||||
/// unredeemed-tickets counter and emits a `ticket_redemption` event with
|
RedeemTickets {
|
||||||
/// `moved_to_holding_account = "false"`. No known consumer depends on the
|
n: u16,
|
||||||
/// side effects; candidate for removal in a follow-on breaking-schema
|
gw: String,
|
||||||
/// change.
|
},
|
||||||
RedeemTickets { n: u16, gw: String },
|
|
||||||
|
|
||||||
/// Transfer the contract admin role. Only the current admin may sign.
|
UpdateAdmin {
|
||||||
/// Dispatches via the cw_controllers `execute_update_admin` handshake.
|
admin: String,
|
||||||
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 { new_deposit: Coin },
|
UpdateDefaultDepositValue {
|
||||||
|
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 { address: String, deposit: Coin },
|
SetReducedDepositPrice {
|
||||||
|
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 { address: String },
|
RemoveReducedDepositPrice {
|
||||||
|
address: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// **Stubbed**: always returns `EcashContractError::UnimplementedBlacklisting`.
|
// TODO: properly implement
|
||||||
/// Storage, reply handler, and helper paths exist but are unreachable from
|
ProposeToBlacklist {
|
||||||
/// the public ExecuteMsg surface. Preserved for the redesign.
|
public_key: String,
|
||||||
ProposeToBlacklist { public_key: String },
|
},
|
||||||
|
AddToBlacklist {
|
||||||
/// **Stubbed**: always returns `EcashContractError::UnimplementedBlacklisting`.
|
public_key: String,
|
||||||
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,8 +1,5 @@
|
|||||||
// 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,16 +4,12 @@
|
|||||||
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>,
|
||||||
|
|||||||
@@ -3,121 +3,12 @@
|
|||||||
|
|
||||||
use crate::error::MixnetContractError;
|
use crate::error::MixnetContractError;
|
||||||
use crate::mixnode::PendingMixNodeChanges;
|
use crate::mixnode::PendingMixNodeChanges;
|
||||||
use crate::nym_node::NodeOwnershipResponse;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
EpochEventId, EpochId, Interval, IntervalEventId, MixNodeBond, MixNodeDetails, NodeId,
|
EpochEventId, IntervalEventId, MixNodeBond, MixNodeDetails, NodeId, NodeRewarding, NymNodeBond,
|
||||||
NodeRewarding, NymNodeBond, NymNodeDetails, PendingNodeChanges, QueryMsg,
|
NymNodeDetails, PendingNodeChanges,
|
||||||
};
|
};
|
||||||
use cosmwasm_std::{
|
use cosmwasm_std::{Coin, Decimal, StdError, StdResult, Uint128};
|
||||||
Addr, Binary, Coin, CustomQuery, Decimal, QuerierWrapper, StdError, StdResult, Uint128,
|
|
||||||
from_json,
|
|
||||||
};
|
|
||||||
use cw_storage_plus::{Key, Namespace, Path, PrimaryKey};
|
|
||||||
use nym_contracts_common::IdentityKeyRef;
|
use nym_contracts_common::IdentityKeyRef;
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
pub trait MixnetContractQuerier {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn query_mixnet_contract<T: DeserializeOwned>(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
msg: &QueryMsg,
|
|
||||||
) -> StdResult<T>;
|
|
||||||
|
|
||||||
fn query_mixnet_contract_storage(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
key: impl Into<Binary>,
|
|
||||||
) -> StdResult<Option<Vec<u8>>>;
|
|
||||||
|
|
||||||
fn query_mixnet_contract_storage_value<T: DeserializeOwned>(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
key: impl Into<Binary>,
|
|
||||||
) -> StdResult<Option<T>> {
|
|
||||||
match self.query_mixnet_contract_storage(address, key)? {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(value) => Ok(Some(from_json(&value)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_current_mixnet_interval(&self, address: impl Into<String>) -> StdResult<Interval> {
|
|
||||||
self.query_mixnet_contract_storage_value(address, b"ci")?
|
|
||||||
.ok_or(StdError::not_found(
|
|
||||||
"unable to retrieve interval information from the mixnet contract storage",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_current_absolute_mixnet_epoch_id(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
) -> StdResult<EpochId> {
|
|
||||||
self.query_current_mixnet_interval(address)
|
|
||||||
.map(|interval| interval.current_epoch_absolute_id())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_node_existence(&self, address: impl Into<String>, node_id: NodeId) -> StdResult<bool> {
|
|
||||||
let mixnet_contract_address = address.into();
|
|
||||||
|
|
||||||
if let Some(nym_node) = self.query_nymnode_bond(mixnet_contract_address.clone(), node_id)? {
|
|
||||||
return Ok(!nym_node.is_unbonding);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_nymnode_bond(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
node_id: NodeId,
|
|
||||||
) -> StdResult<Option<NymNodeBond>> {
|
|
||||||
// construct proper map key
|
|
||||||
let pk_namespace = "nn";
|
|
||||||
let path: Path<NymNodeBond> = Path::new(
|
|
||||||
Namespace::from_static_str(pk_namespace).as_slice(),
|
|
||||||
&node_id.key().iter().map(Key::as_ref).collect::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
let storage_key = path.deref();
|
|
||||||
|
|
||||||
self.query_mixnet_contract_storage_value(address, storage_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_nymnode_ownership(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
owner: &Addr,
|
|
||||||
) -> StdResult<Option<NymNodeBond>> {
|
|
||||||
let resp: NodeOwnershipResponse = self.query_mixnet_contract(
|
|
||||||
address,
|
|
||||||
&QueryMsg::GetOwnedNymNode {
|
|
||||||
address: owner.to_string(),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(resp.details.map(|d| d.bond_information))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> MixnetContractQuerier for QuerierWrapper<'_, C>
|
|
||||||
where
|
|
||||||
C: CustomQuery,
|
|
||||||
{
|
|
||||||
fn query_mixnet_contract<T: DeserializeOwned>(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
msg: &QueryMsg,
|
|
||||||
) -> StdResult<T> {
|
|
||||||
self.query_wasm_smart(address, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_mixnet_contract_storage(
|
|
||||||
&self,
|
|
||||||
address: impl Into<String>,
|
|
||||||
key: impl Into<Binary>,
|
|
||||||
) -> StdResult<Option<Vec<u8>>> {
|
|
||||||
self.query_wasm_raw(address, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
|
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ pub use gateway::{
|
|||||||
Gateway, GatewayBond, GatewayBondResponse, GatewayConfigUpdate, GatewayOwnershipResponse,
|
Gateway, GatewayBond, GatewayBondResponse, GatewayConfigUpdate, GatewayOwnershipResponse,
|
||||||
PagedGatewayResponse,
|
PagedGatewayResponse,
|
||||||
};
|
};
|
||||||
pub use helpers::MixnetContractQuerier;
|
|
||||||
pub use interval::{
|
pub use interval::{
|
||||||
CurrentIntervalResponse, EpochId, EpochState, EpochStatus, Interval, IntervalId,
|
CurrentIntervalResponse, EpochId, EpochState, EpochStatus, Interval, IntervalId,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -190,10 +190,6 @@ impl NodeRewarding {
|
|||||||
truncate_reward(self.operator, denom)
|
truncate_reward(self.operator, denom)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delegations_with_reward(&self, denom: impl Into<String>) -> Coin {
|
|
||||||
truncate_reward(self.delegates, denom)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
|
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
|
||||||
let delegator_reward = self.determine_delegation_reward(delegation)?;
|
let delegator_reward = self.determine_delegation_reward(delegation)?;
|
||||||
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
|
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ use nym_contracts_common::{ContractBuildInformation, signing::Nonce};
|
|||||||
pub struct InstantiateMsg {
|
pub struct InstantiateMsg {
|
||||||
pub rewarding_validator_address: String,
|
pub rewarding_validator_address: String,
|
||||||
pub vesting_contract_address: String,
|
pub vesting_contract_address: String,
|
||||||
pub node_families_contract_address: String,
|
|
||||||
|
|
||||||
pub rewarding_denom: String,
|
pub rewarding_denom: String,
|
||||||
pub epochs_in_interval: u32,
|
pub epochs_in_interval: u32,
|
||||||
@@ -306,22 +305,6 @@ pub enum ExecuteMsg {
|
|||||||
MigrateVestedDelegation {
|
MigrateVestedDelegation {
|
||||||
mix_id: NodeId,
|
mix_id: NodeId,
|
||||||
},
|
},
|
||||||
/// Admin-only: forcibly migrate the vested mixnode owned by `owner`.
|
|
||||||
/// Used to drain the last vested entries so the mixnet contract can drop its dependency on the vesting contract.
|
|
||||||
AdminMigrateVestedMixNode {
|
|
||||||
owner: String,
|
|
||||||
},
|
|
||||||
/// Admin-only: forcibly migrate the vested delegation `(mix_id, owner)`.
|
|
||||||
/// Used to drain the last vested entries so the mixnet contract can drop its dependency on the vesting contract.
|
|
||||||
AdminMigrateVestedDelegation {
|
|
||||||
mix_id: NodeId,
|
|
||||||
owner: String,
|
|
||||||
},
|
|
||||||
/// Admin-only: batch variant of [`ExecuteMsg::AdminMigrateVestedDelegation`].
|
|
||||||
/// Reverts the entire batch on the first error, so callers should treat it as all-or-nothing.
|
|
||||||
AdminBatchMigrateVestedDelegations {
|
|
||||||
entries: Vec<VestedDelegationMigrationEntry>,
|
|
||||||
},
|
|
||||||
|
|
||||||
// testing-only
|
// testing-only
|
||||||
#[cfg(feature = "contract-testing")]
|
#[cfg(feature = "contract-testing")]
|
||||||
@@ -411,15 +394,6 @@ impl ExecuteMsg {
|
|||||||
}
|
}
|
||||||
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
|
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
|
||||||
ExecuteMsg::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(),
|
ExecuteMsg::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(),
|
||||||
ExecuteMsg::AdminMigrateVestedMixNode { owner } => {
|
|
||||||
format!("admin migrating vested mixnode of {owner}")
|
|
||||||
}
|
|
||||||
ExecuteMsg::AdminMigrateVestedDelegation { mix_id, owner } => {
|
|
||||||
format!("admin migrating vested delegation of {owner} on mixnode {mix_id}")
|
|
||||||
}
|
|
||||||
ExecuteMsg::AdminBatchMigrateVestedDelegations { entries } => {
|
|
||||||
format!("admin batch migrating {} vested delegations", entries.len())
|
|
||||||
}
|
|
||||||
ExecuteMsg::AssignRoles { .. } => "assigning epoch roles".into(),
|
ExecuteMsg::AssignRoles { .. } => "assigning epoch roles".into(),
|
||||||
ExecuteMsg::MigrateMixnode { .. } => "migrating legacy mixnode".into(),
|
ExecuteMsg::MigrateMixnode { .. } => "migrating legacy mixnode".into(),
|
||||||
ExecuteMsg::MigrateGateway { .. } => "migrating legacy gateway".into(),
|
ExecuteMsg::MigrateGateway { .. } => "migrating legacy gateway".into(),
|
||||||
@@ -907,15 +881,8 @@ pub enum QueryMsg {
|
|||||||
GetKeyRotationId {},
|
GetKeyRotationId {},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct VestedDelegationMigrationEntry {
|
|
||||||
pub mix_id: NodeId,
|
|
||||||
pub owner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct MigrateMsg {
|
pub struct MigrateMsg {
|
||||||
pub unsafe_skip_state_updates: Option<bool>,
|
pub unsafe_skip_state_updates: Option<bool>,
|
||||||
pub vesting_contract_address: Option<String>,
|
pub vesting_contract_address: Option<String>,
|
||||||
pub node_families_contract_address: String,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,10 +212,6 @@ pub struct ContractState {
|
|||||||
/// track-related messages.
|
/// track-related messages.
|
||||||
pub vesting_contract_address: Addr,
|
pub vesting_contract_address: Addr,
|
||||||
|
|
||||||
/// Address of the node families contract. It is called whenever nym-node unbonds
|
|
||||||
/// so that it could be removed from any family it belongs to.
|
|
||||||
pub node_families_contract_address: Addr,
|
|
||||||
|
|
||||||
/// The expected denom used for rewarding (and realistically any other operation).
|
/// The expected denom used for rewarding (and realistically any other operation).
|
||||||
/// Default: `unym`
|
/// Default: `unym`
|
||||||
pub rewarding_denom: String,
|
pub rewarding_denom: String,
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "nym-node-families-contract-common"
|
|
||||||
description = "Common crate for Nym's node families contract"
|
|
||||||
version.workspace = true
|
|
||||||
authors.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
documentation.workspace = true
|
|
||||||
rust-version = "1.85"
|
|
||||||
readme.workspace = true
|
|
||||||
publish = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
|
||||||
schemars = { workspace = true }
|
|
||||||
|
|
||||||
cosmwasm-std = { workspace = true }
|
|
||||||
cosmwasm-schema = { workspace = true }
|
|
||||||
cw-controllers = { workspace = true }
|
|
||||||
cw-utils = { workspace = true }
|
|
||||||
|
|
||||||
nym-contracts-common = { workspace = true }
|
|
||||||
nym-mixnet-contract-common = { workspace = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
schema = []
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
/// Storage key constants used by the node families contract.
|
|
||||||
///
|
|
||||||
/// They are kept in the common crate so that off-chain tooling (indexers, migration
|
|
||||||
/// scripts) can reference them without depending on the contract crate itself.
|
|
||||||
/// Changing any of these values is a breaking change for already-deployed contracts.
|
|
||||||
pub mod storage_keys {
|
|
||||||
/// `Item<Addr>`: address of the mixnet contract used to validate node existence.
|
|
||||||
pub const MIXNET_CONTRACT_ADDRESS: &str = "mixnet-contract-address";
|
|
||||||
|
|
||||||
/// `Item<Config>`: runtime configuration (fees, length limits) set at instantiation.
|
|
||||||
pub const CONFIG: &str = "config";
|
|
||||||
|
|
||||||
/// `Admin` (cw-controllers): admin allowed to perform privileged operations.
|
|
||||||
pub const CONTRACT_ADMIN: &str = "contract-admin";
|
|
||||||
/// `Item<NodeFamilyId>`: monotonically increasing id counter for new families.
|
|
||||||
pub const NODE_FAMILY_ID_COUNTER: &str = "node-family-id-counter";
|
|
||||||
/// Primary namespace for the current family-members `IndexedMap`,
|
|
||||||
/// keyed by `NodeId` with value [`crate::FamilyMembership`].
|
|
||||||
pub const NODE_FAMILY_MEMBERS: &str = "node-family-members";
|
|
||||||
/// Multi-index over current family members keyed by family id —
|
|
||||||
/// enables paginated listing of all nodes in a given family.
|
|
||||||
pub const NODE_FAMILY_MEMBERS_FAMILY_IDX_NAMESPACE: &str = "node-family-members__family";
|
|
||||||
|
|
||||||
/// Primary namespace for the families `IndexedMap`.
|
|
||||||
pub const FAMILIES_NAMESPACE: &str = "families";
|
|
||||||
/// Secondary unique index keyed by `owner` (one family per owner).
|
|
||||||
pub const FAMILIES_OWNER_IDX_NAMESPACE: &str = "families__owner";
|
|
||||||
/// Secondary unique index keyed by `name` (family names are globally unique).
|
|
||||||
pub const FAMILIES_NAME_IDX_NAMESPACE: &str = "families__name";
|
|
||||||
|
|
||||||
/// Primary namespace for the pending invitations `IndexedMap`.
|
|
||||||
pub const INVITATIONS_NAMESPACE: &str = "invitations";
|
|
||||||
/// Multi-index over pending invitations keyed by family id.
|
|
||||||
pub const INVITATIONS_FAMILY_IDX_NAMESPACE: &str = "invitations__family";
|
|
||||||
/// Multi-index over pending invitations keyed by node id
|
|
||||||
/// (a node can be invited to multiple families simultaneously).
|
|
||||||
pub const INVITATIONS_NODE_IDX_NAMESPACE: &str = "invitations__node";
|
|
||||||
|
|
||||||
/// Primary namespace for the archived (accepted/rejected/revoked) invitations `IndexedMap`.
|
|
||||||
pub const PAST_INVITATIONS_NAMESPACE: &str = "past-invitations";
|
|
||||||
/// Multi-index over past invitations keyed by family id.
|
|
||||||
pub const PAST_INVITATIONS_FAMILY_IDX_NAMESPACE: &str = "past-invitations__family";
|
|
||||||
/// Multi-index over past invitations keyed by node id.
|
|
||||||
pub const PAST_INVITATIONS_NODE_IDX_NAMESPACE: &str = "past-invitations__node";
|
|
||||||
/// `Map<(NodeFamilyId, NodeId), u64>`: per-`(family, node)` counter used to
|
|
||||||
/// disambiguate repeat archive entries (a node can be invited and have the
|
|
||||||
/// invitation reach a terminal state more than once).
|
|
||||||
pub const PAST_INVITATIONS_COUNTER_NAMESPACE: &str = "past-invitations-counter";
|
|
||||||
|
|
||||||
/// Primary namespace for the past-members `IndexedMap`.
|
|
||||||
pub const PAST_FAMILY_MEMBER_NAMESPACE: &str = "past-family-member";
|
|
||||||
/// Multi-index over past members keyed by family id.
|
|
||||||
pub const PAST_FAMILY_MEMBER_FAMILY_IDX_NAMESPACE: &str = "past-family-member__family";
|
|
||||||
/// Multi-index over past members keyed by node id.
|
|
||||||
pub const PAST_FAMILY_MEMBER_NODE_IDX_NAMESPACE: &str = "past-family-member__node";
|
|
||||||
/// `Map<(NodeFamilyId, NodeId), u64>`: per-`(family, node)` counter used to
|
|
||||||
/// disambiguate repeat past-membership entries (a node can join and leave
|
|
||||||
/// the same family more than once).
|
|
||||||
pub const PAST_FAMILY_MEMBER_COUNTER_NAMESPACE: &str = "past-family-member-counter";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod events {
|
|
||||||
pub const FAMILY_CREATION_EVENT_NAME: &str = "family_creation";
|
|
||||||
pub const FAMILY_CREATION_EVENT_FAMILY_NAME: &str = "family_name";
|
|
||||||
pub const FAMILY_CREATION_EVENT_OWNER_ADDRESS: &str = "owner_address";
|
|
||||||
pub const FAMILY_CREATION_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_CREATION_EVENT_PAID_FEE: &str = "paid_fee";
|
|
||||||
|
|
||||||
pub const FAMILY_DISBAND_EVENT_NAME: &str = "family_disband";
|
|
||||||
pub const FAMILY_DISBAND_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_DISBAND_EVENT_OWNER_ADDRESS: &str = "owner_address";
|
|
||||||
pub const FAMILY_DISBAND_EVENT_REFUNDED_FEE: &str = "refunded_fee";
|
|
||||||
|
|
||||||
pub const FAMILY_INVITATION_EVENT_NAME: &str = "family_invitation";
|
|
||||||
pub const FAMILY_INVITATION_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_INVITATION_EVENT_NODE_ID: &str = "node_id";
|
|
||||||
pub const FAMILY_INVITATION_EVENT_EXPIRES_AT: &str = "expires_at";
|
|
||||||
|
|
||||||
pub const FAMILY_INVITATION_REVOKED_EVENT_NAME: &str = "family_invitation_revoked";
|
|
||||||
pub const FAMILY_INVITATION_REVOKED_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_INVITATION_REVOKED_EVENT_NODE_ID: &str = "node_id";
|
|
||||||
|
|
||||||
pub const FAMILY_INVITATION_ACCEPTED_EVENT_NAME: &str = "family_invitation_accepted";
|
|
||||||
pub const FAMILY_INVITATION_ACCEPTED_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_INVITATION_ACCEPTED_EVENT_NODE_ID: &str = "node_id";
|
|
||||||
|
|
||||||
pub const FAMILY_INVITATION_REJECTED_EVENT_NAME: &str = "family_invitation_rejected";
|
|
||||||
pub const FAMILY_INVITATION_REJECTED_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_INVITATION_REJECTED_EVENT_NODE_ID: &str = "node_id";
|
|
||||||
|
|
||||||
pub const FAMILY_MEMBER_LEFT_EVENT_NAME: &str = "family_member_left";
|
|
||||||
pub const FAMILY_MEMBER_LEFT_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_MEMBER_LEFT_EVENT_NODE_ID: &str = "node_id";
|
|
||||||
|
|
||||||
pub const FAMILY_MEMBER_KICKED_EVENT_NAME: &str = "family_member_kicked";
|
|
||||||
pub const FAMILY_MEMBER_KICKED_EVENT_FAMILY_ID: &str = "family_id";
|
|
||||||
pub const FAMILY_MEMBER_KICKED_EVENT_NODE_ID: &str = "node_id";
|
|
||||||
|
|
||||||
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 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";
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
use crate::NodeFamilyId;
|
|
||||||
use cosmwasm_std::{Addr, Coin};
|
|
||||||
use cw_controllers::AdminError;
|
|
||||||
use cw_utils::PaymentError;
|
|
||||||
use nym_mixnet_contract_common::NodeId;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Errors returned from any entry point of the node families contract.
|
|
||||||
#[derive(Error, Debug, PartialEq)]
|
|
||||||
pub enum NodeFamiliesContractError {
|
|
||||||
/// Returned from `migrate` when the on-chain state cannot be brought forward
|
|
||||||
/// to the current contract version (e.g. unsupported source version, malformed
|
|
||||||
/// stored data).
|
|
||||||
#[error("could not perform contract migration: {comment}")]
|
|
||||||
FailedMigration { comment: String },
|
|
||||||
|
|
||||||
/// The referenced family does not exist (or no longer exists).
|
|
||||||
#[error("family with id {family_id} does not exist")]
|
|
||||||
FamilyNotFound { family_id: NodeFamilyId },
|
|
||||||
|
|
||||||
/// Disbanding was requested on a family that still has members.
|
|
||||||
#[error("family {family_id} cannot be disbanded: it still has {members} member(s)")]
|
|
||||||
FamilyNotEmpty {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
members: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The given node is not currently a member of any family.
|
|
||||||
#[error("node {node_id} is not currently a member of any family")]
|
|
||||||
NodeNotInFamily { node_id: NodeId },
|
|
||||||
|
|
||||||
/// The given node is a member of a different family than the one the
|
|
||||||
/// caller is acting on. Distinct from [`NodeNotInFamily`] (which means the
|
|
||||||
/// node has no membership at all) — surfaces when, e.g., a family owner
|
|
||||||
/// tries to kick a node that belongs to someone else's family.
|
|
||||||
#[error("node {node_id} is not a member of family {family_id}")]
|
|
||||||
NodeNotMemberOfFamily {
|
|
||||||
node_id: NodeId,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A cross-contract callback (e.g. `OnNymNodeUnbond`) was received from a
|
|
||||||
/// sender that is not the configured mixnet contract address.
|
|
||||||
#[error("address {sender} is not authorised to invoke the mixnet-contract callback")]
|
|
||||||
UnauthorisedMixnetCallback { sender: Addr },
|
|
||||||
|
|
||||||
/// No pending invitation exists for the given `(family, node)` pair.
|
|
||||||
#[error("no pending invitation for node {node_id} from family {family_id}")]
|
|
||||||
InvitationNotFound {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A pending invitation for the given `(family, node)` pair already exists;
|
|
||||||
/// issuing a new one would silently overwrite it.
|
|
||||||
#[error("a pending invitation for node {node_id} from family {family_id} already exists")]
|
|
||||||
PendingInvitationAlreadyExists {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The invitation exists but its `expires_at` is at or before the current
|
|
||||||
/// block time, so it can no longer be acted on.
|
|
||||||
#[error(
|
|
||||||
"invitation for node {node_id} from family {family_id} expired at {expires_at} (now: {now})"
|
|
||||||
)]
|
|
||||||
InvitationExpired {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
expires_at: u64,
|
|
||||||
now: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The funds attached to a paid execution failed `cw_utils` payment
|
|
||||||
/// validation (no funds, wrong/extra denom).
|
|
||||||
#[error("invalid fee provided: {0}")]
|
|
||||||
InvalidDeposit(#[from] PaymentError),
|
|
||||||
|
|
||||||
/// The funds attached to a `CreateFamily` execution don't match the
|
|
||||||
/// configured `create_family_fee`.
|
|
||||||
#[error("expected exactly {expected} as family creation fee; received {received:?}")]
|
|
||||||
InvalidFamilyCreationFee { expected: Coin, received: Vec<Coin> },
|
|
||||||
|
|
||||||
/// The submitted family name normalised to the empty string (i.e. it
|
|
||||||
/// contained no ASCII alphanumeric characters).
|
|
||||||
#[error("family name cannot be empty after normalisation")]
|
|
||||||
EmptyFamilyName,
|
|
||||||
|
|
||||||
/// The submitted family name exceeds the configured length limit.
|
|
||||||
#[error("family name length {length} exceeds the configured limit of {limit}")]
|
|
||||||
FamilyNameTooLong { length: usize, limit: usize },
|
|
||||||
|
|
||||||
/// The submitted family description exceeds the configured length limit.
|
|
||||||
#[error("family description length {length} exceeds the configured limit of {limit}")]
|
|
||||||
FamilyDescriptionTooLong { length: usize, limit: usize },
|
|
||||||
|
|
||||||
/// The transaction sender already owns a family.
|
|
||||||
#[error("address {address} already owns family {family_id}")]
|
|
||||||
SenderAlreadyOwnsAFamily {
|
|
||||||
address: Addr,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The transaction sender does not currently own any family - emitted by
|
|
||||||
/// owner-gated operations like `disband_family` when the sender has
|
|
||||||
/// nothing to act on.
|
|
||||||
#[error("address {address} does not currently own any family")]
|
|
||||||
SenderDoesntOwnAFamily { address: Addr },
|
|
||||||
|
|
||||||
/// The transaction sender is not the controller of the bonded node
|
|
||||||
/// referenced by the message. Covers all of: sender controls no bonded
|
|
||||||
/// node, sender controls a different node id, and sender's node has
|
|
||||||
/// entered the unbonding state.
|
|
||||||
#[error("address {address} is not the controller of bonded node {node_id}")]
|
|
||||||
SenderDoesntControlNode { address: Addr, node_id: NodeId },
|
|
||||||
|
|
||||||
/// A family with the requested (normalised) name already exists.
|
|
||||||
#[error("a family with name {name:?} already exists (id {family_id})")]
|
|
||||||
FamilyNameAlreadyTaken {
|
|
||||||
name: String,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A node controlled by the address is currently a member of a family,
|
|
||||||
/// so the address cannot also become a family owner or join another family.
|
|
||||||
#[error("address {address} controls node {node_id} which is currently in family {family_id}")]
|
|
||||||
AlreadyInFamily {
|
|
||||||
address: Addr,
|
|
||||||
node_id: NodeId,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The node referenced by an invitation does not exist as a bonded node
|
|
||||||
/// in the mixnet contract (or has already unbonded).
|
|
||||||
#[error("node {node_id} is not a bonded node in the mixnet contract")]
|
|
||||||
NodeDoesntExist { node_id: NodeId },
|
|
||||||
|
|
||||||
/// The node referenced by an invitation is already a member of a family,
|
|
||||||
/// so it cannot be invited to another one until it leaves / is removed.
|
|
||||||
#[error("node {node_id} is already a member of family {family_id}")]
|
|
||||||
NodeAlreadyInFamily {
|
|
||||||
node_id: NodeId,
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The sender supplied a `validity_secs` of `0` for an invitation, which
|
|
||||||
/// would create one that is already expired at the moment it is stored.
|
|
||||||
#[error("invitation validity must be strictly positive")]
|
|
||||||
ZeroInvitationValidity,
|
|
||||||
|
|
||||||
/// Wraps errors raised by `cw-controllers::Admin` (e.g. caller is not admin).
|
|
||||||
#[error(transparent)]
|
|
||||||
Admin(#[from] AdminError),
|
|
||||||
|
|
||||||
/// Wraps any underlying `cosmwasm_std::StdError` (storage, serialization, etc.).
|
|
||||||
#[error(transparent)]
|
|
||||||
StdErr(#[from] cosmwasm_std::StdError),
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//! Common types, messages, errors and storage-key constants shared between the
|
|
||||||
//! node families contract and any off-chain client.
|
|
||||||
//!
|
|
||||||
//! Keeping these in a separate crate allows clients to depend on the contract's
|
|
||||||
//! public surface without pulling in `cw-storage-plus` and other on-chain-only
|
|
||||||
//! dependencies.
|
|
||||||
|
|
||||||
/// Storage-key string constants. See [`constants::storage_keys`].
|
|
||||||
pub mod constants;
|
|
||||||
/// Contract-level error type.
|
|
||||||
pub mod error;
|
|
||||||
/// `InstantiateMsg`, `ExecuteMsg`, `QueryMsg`, `MigrateMsg` definitions.
|
|
||||||
pub mod msg;
|
|
||||||
/// Domain types stored in / returned by the contract.
|
|
||||||
pub mod types;
|
|
||||||
|
|
||||||
pub use error::*;
|
|
||||||
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
|
||||||
pub use types::*;
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Config, GlobalPastFamilyInvitationCursor, NodeFamilyId, PastFamilyInvitationCursor,
|
|
||||||
PastFamilyInvitationForNodeCursor, PastFamilyMemberCursor, PastFamilyMemberForNodeCursor,
|
|
||||||
};
|
|
||||||
use cosmwasm_schema::cw_serde;
|
|
||||||
use nym_mixnet_contract_common::NodeId;
|
|
||||||
|
|
||||||
#[cfg(feature = "schema")]
|
|
||||||
use crate::{
|
|
||||||
AllFamilyMembersPagedResponse, AllPastFamilyInvitationsPagedResponse, FamiliesPagedResponse,
|
|
||||||
FamilyMembersPagedResponse, NodeFamilyByNameResponse, NodeFamilyByOwnerResponse,
|
|
||||||
NodeFamilyMembershipResponse, NodeFamilyResponse, PastFamilyInvitationsForNodePagedResponse,
|
|
||||||
PastFamilyInvitationsPagedResponse, PastFamilyMembersForNodePagedResponse,
|
|
||||||
PastFamilyMembersPagedResponse, PendingFamilyInvitationResponse,
|
|
||||||
PendingFamilyInvitationsPagedResponse, PendingInvitationsForNodePagedResponse,
|
|
||||||
PendingInvitationsPagedResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Message used to instantiate the node families contract.
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct InstantiateMsg {
|
|
||||||
pub config: Config,
|
|
||||||
|
|
||||||
pub mixnet_contract_address: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute messages accepted by the contract.
|
|
||||||
#[cw_serde]
|
|
||||||
pub enum ExecuteMsg {
|
|
||||||
/// Replace the contract's runtime [`Config`]. Restricted to the contract
|
|
||||||
/// admin.
|
|
||||||
UpdateConfig { config: Config },
|
|
||||||
|
|
||||||
/// Create a new family owned by the message sender. The configured
|
|
||||||
/// `create_family_fee` must be attached as funds.
|
|
||||||
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
|
|
||||||
/// no current members; any still-pending invitations are revoked.
|
|
||||||
DisbandFamily {},
|
|
||||||
|
|
||||||
/// Invite a node to the family owned by the message sender. If
|
|
||||||
/// `validity_secs` is omitted the invitation expires
|
|
||||||
/// `default_invitation_validity_secs` seconds (from [`Config`]) after the
|
|
||||||
/// current block time.
|
|
||||||
InviteToFamily {
|
|
||||||
node_id: NodeId,
|
|
||||||
validity_secs: Option<u64>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Revoke a still-pending invitation previously issued by the sender's
|
|
||||||
/// family.
|
|
||||||
RevokeFamilyInvitation { node_id: NodeId },
|
|
||||||
|
|
||||||
/// Accept a pending invitation. The sender must control `node_id`.
|
|
||||||
AcceptFamilyInvitation {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Reject a pending invitation. The sender must control `node_id`.
|
|
||||||
RejectFamilyInvitation {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Leave the family `node_id` currently belongs to. The sender must
|
|
||||||
/// control `node_id`.
|
|
||||||
LeaveFamily { node_id: NodeId },
|
|
||||||
|
|
||||||
/// Remove `node_id` from the family owned by the message sender.
|
|
||||||
KickFromFamily { node_id: NodeId },
|
|
||||||
|
|
||||||
/// Cross-contract callback fired by the mixnet contract the moment
|
|
||||||
/// node with `node_id` initiates unbonding.
|
|
||||||
/// Removes the node from any family it currently
|
|
||||||
/// belongs to and rejects every pending invitation issued to it.
|
|
||||||
/// Sender must be the configured mixnet contract address.
|
|
||||||
OnNymNodeUnbond { node_id: NodeId },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query messages accepted by the contract.
|
|
||||||
#[cw_serde]
|
|
||||||
#[cfg_attr(feature = "schema", derive(cosmwasm_schema::QueryResponses))]
|
|
||||||
pub enum QueryMsg {
|
|
||||||
/// Retrieve current contract configuration values
|
|
||||||
#[cfg_attr(feature = "schema", returns(Config))]
|
|
||||||
GetConfig {},
|
|
||||||
|
|
||||||
/// Look up a single family by its id.
|
|
||||||
#[cfg_attr(feature = "schema", returns(NodeFamilyResponse))]
|
|
||||||
GetFamilyById { family_id: NodeFamilyId },
|
|
||||||
|
|
||||||
/// Look up the (at most one) family owned by a given address.
|
|
||||||
#[cfg_attr(feature = "schema", returns(NodeFamilyByOwnerResponse))]
|
|
||||||
GetFamilyByOwner { owner: String },
|
|
||||||
|
|
||||||
/// Look up a single family by its name. The lookup is normalised
|
|
||||||
/// contract-side (lowercased, non-alphanumerics stripped), so equivalent
|
|
||||||
/// inputs resolve to the same family.
|
|
||||||
#[cfg_attr(feature = "schema", returns(NodeFamilyByNameResponse))]
|
|
||||||
GetFamilyByName { name: String },
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "schema", returns(FamiliesPagedResponse))]
|
|
||||||
GetFamiliesPaged {
|
|
||||||
start_after: Option<NodeFamilyId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Look up which family — if any — a node currently belongs to.
|
|
||||||
#[cfg_attr(feature = "schema", returns(NodeFamilyMembershipResponse))]
|
|
||||||
GetFamilyMembership { node_id: NodeId },
|
|
||||||
|
|
||||||
/// Page through every node currently in a given family.
|
|
||||||
#[cfg_attr(feature = "schema", returns(FamilyMembersPagedResponse))]
|
|
||||||
GetFamilyMembersPaged {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<NodeId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every current family member across all families, in
|
|
||||||
/// ascending [`NodeId`] order. Each entry carries the membership record
|
|
||||||
/// (which in turn names the family the node belongs to).
|
|
||||||
#[cfg_attr(feature = "schema", returns(AllFamilyMembersPagedResponse))]
|
|
||||||
GetAllFamilyMembersPaged {
|
|
||||||
start_after: Option<NodeId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Look up the pending invitation for a specific `(family_id, node_id)`
|
|
||||||
/// pair.
|
|
||||||
#[cfg_attr(feature = "schema", returns(PendingFamilyInvitationResponse))]
|
|
||||||
GetPendingInvitation {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
node_id: NodeId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every pending invitation issued by a given family.
|
|
||||||
#[cfg_attr(feature = "schema", returns(PendingFamilyInvitationsPagedResponse))]
|
|
||||||
GetPendingInvitationsForFamilyPaged {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<NodeId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every pending invitation issued for a given node.
|
|
||||||
#[cfg_attr(feature = "schema", returns(PendingInvitationsForNodePagedResponse))]
|
|
||||||
GetPendingInvitationsForNodePaged {
|
|
||||||
node_id: NodeId,
|
|
||||||
start_after: Option<NodeFamilyId>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every pending invitation across all families.
|
|
||||||
#[cfg_attr(feature = "schema", returns(PendingInvitationsPagedResponse))]
|
|
||||||
GetAllPendingInvitationsPaged {
|
|
||||||
start_after: Option<(NodeFamilyId, NodeId)>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every archived (terminal-state) invitation issued by a
|
|
||||||
/// given family.
|
|
||||||
#[cfg_attr(feature = "schema", returns(PastFamilyInvitationsPagedResponse))]
|
|
||||||
GetPastInvitationsForFamilyPaged {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<PastFamilyInvitationCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every archived (terminal-state) invitation issued to a
|
|
||||||
/// given node.
|
|
||||||
#[cfg_attr(feature = "schema", returns(PastFamilyInvitationsForNodePagedResponse))]
|
|
||||||
GetPastInvitationsForNodePaged {
|
|
||||||
node_id: NodeId,
|
|
||||||
start_after: Option<PastFamilyInvitationForNodeCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every archived (terminal-state) invitation across all
|
|
||||||
/// families.
|
|
||||||
#[cfg_attr(feature = "schema", returns(AllPastFamilyInvitationsPagedResponse))]
|
|
||||||
GetAllPastInvitationsPaged {
|
|
||||||
start_after: Option<GlobalPastFamilyInvitationCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every archived membership record for a given family
|
|
||||||
/// (nodes that used to belong to it but have since been removed).
|
|
||||||
#[cfg_attr(feature = "schema", returns(PastFamilyMembersPagedResponse))]
|
|
||||||
GetPastMembersForFamilyPaged {
|
|
||||||
family_id: NodeFamilyId,
|
|
||||||
start_after: Option<PastFamilyMemberCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Page through every archived membership record for a given node
|
|
||||||
/// (every family the node used to belong to but has since been removed
|
|
||||||
/// from), across all families.
|
|
||||||
#[cfg_attr(feature = "schema", returns(PastFamilyMembersForNodePagedResponse))]
|
|
||||||
GetPastMembersForNodePaged {
|
|
||||||
node_id: NodeId,
|
|
||||||
start_after: Option<PastFamilyMemberForNodeCursor>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message passed to the contract's `migrate` entry point.
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct MigrateMsg {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
@@ -1,413 +0,0 @@
|
|||||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
use cosmwasm_schema::cw_serde;
|
|
||||||
use cosmwasm_std::{Addr, Coin};
|
|
||||||
use nym_mixnet_contract_common::NodeId;
|
|
||||||
|
|
||||||
/// Identifier of a node family.
|
|
||||||
///
|
|
||||||
/// Issued sequentially by the contract on family creation; never reused even if the
|
|
||||||
/// family is later disbanded.
|
|
||||||
pub type NodeFamilyId = u32;
|
|
||||||
|
|
||||||
/// Runtime configuration of the node families contract.
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct Config {
|
|
||||||
/// Fee charged on each successful `create_family` execution.
|
|
||||||
pub create_family_fee: Coin,
|
|
||||||
|
|
||||||
/// Maximum allowed length, in characters, of a family name.
|
|
||||||
pub family_name_length_limit: usize,
|
|
||||||
|
|
||||||
/// Maximum allowed length, in characters, of a family description.
|
|
||||||
pub family_description_length_limit: usize,
|
|
||||||
|
|
||||||
/// Default lifetime, in seconds, used by `invite_to_family` when the
|
|
||||||
/// sender doesn't supply an explicit value. Senders may override this
|
|
||||||
/// per-invitation via the optional `validity_secs` argument.
|
|
||||||
pub default_invitation_validity_secs: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On-chain representation of a node family.
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct NodeFamily {
|
|
||||||
/// The id of the node family
|
|
||||||
pub id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The name of the node family
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// Normalised name of the node family used for uniqueness checks
|
|
||||||
pub normalised_name: String,
|
|
||||||
|
|
||||||
/// The optional description of the node family
|
|
||||||
pub description: String,
|
|
||||||
|
|
||||||
/// The owner of the node family
|
|
||||||
pub owner: Addr,
|
|
||||||
|
|
||||||
/// Records the fee paid when the family was created,
|
|
||||||
/// so that the appropriate amount could be returned upon it getting disbanded.
|
|
||||||
pub paid_fee: Coin,
|
|
||||||
|
|
||||||
/// Memoized value of the current number of members in the node family
|
|
||||||
/// Used to detect if the family is empty
|
|
||||||
pub members: u64,
|
|
||||||
|
|
||||||
/// Timestamp of the creation of the node family
|
|
||||||
pub created_at: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pending invitation for a node to join a particular family.
|
|
||||||
///
|
|
||||||
/// Invitations are stored until they are accepted, rejected, or revoked. Once the
|
|
||||||
/// chain advances past `expires_at` an invitation becomes inert but stays in storage
|
|
||||||
/// — 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]
|
|
||||||
pub struct FamilyInvitation {
|
|
||||||
/// The family that issued the invitation.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The node being invited.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// Block timestamp (unix seconds) after which the invitation is no longer valid.
|
|
||||||
pub expires_at: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On-chain record of a node's current family membership.
|
|
||||||
///
|
|
||||||
/// A node belongs to at most one family at a time, so this is keyed by
|
|
||||||
/// `NodeId` alone — `family_id` is carried in the value to support reverse
|
|
||||||
/// lookups (all nodes in a given family) via a secondary index.
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct FamilyMembership {
|
|
||||||
/// The family the node is currently a member of.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// Block timestamp (unix seconds) at which the node accepted its
|
|
||||||
/// invitation and joined the family.
|
|
||||||
pub joined_at: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Historical record of a node that used to be part of a family but has since been
|
|
||||||
/// removed (kicked, left voluntarily, or because the family was disbanded).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PastFamilyMember {
|
|
||||||
/// The family the node used to belong to.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The node that was removed.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// Block timestamp (unix seconds) at which the membership was terminated.
|
|
||||||
pub removed_at: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Terminal status for an invitation that has been moved out of the pending set.
|
|
||||||
///
|
|
||||||
/// Note: an invitation that merely times out is **not** archived here on its own —
|
|
||||||
/// 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]
|
|
||||||
pub enum FamilyInvitationStatus {
|
|
||||||
/// Still awaiting a response. Recorded with a timestamp for completeness even
|
|
||||||
/// though pending invitations live in a separate map.
|
|
||||||
Pending { at: u64 },
|
|
||||||
/// The invitee accepted and joined the family at the given timestamp.
|
|
||||||
Accepted { at: u64 },
|
|
||||||
/// The invitee explicitly rejected the invitation at the given timestamp.
|
|
||||||
Rejected { at: u64 },
|
|
||||||
/// The family revoked the invitation at the given timestamp before it could
|
|
||||||
/// be accepted or rejected.
|
|
||||||
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
|
|
||||||
/// (`Accepted`, `Rejected`, `Revoked`, or `Expired`). A timed-out invitation is
|
|
||||||
/// 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]
|
|
||||||
pub struct PastFamilyInvitation {
|
|
||||||
/// The original invitation as it was issued.
|
|
||||||
pub invitation: FamilyInvitation,
|
|
||||||
|
|
||||||
/// What ultimately happened to it.
|
|
||||||
pub status: FamilyInvitationStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetFamilyById`](crate::QueryMsg::GetFamilyById).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct NodeFamilyResponse {
|
|
||||||
/// The id that was queried, echoed back so paginated callers can correlate.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The matching family, or `None` if no family with `family_id` exists.
|
|
||||||
pub family: Option<NodeFamily>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetFamilyByOwner`](crate::QueryMsg::GetFamilyByOwner).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct NodeFamilyByOwnerResponse {
|
|
||||||
/// The (validated) owner address that was queried, echoed back so callers
|
|
||||||
/// can correlate.
|
|
||||||
pub owner: Addr,
|
|
||||||
|
|
||||||
/// The matching family, or `None` if `owner` does not currently own one.
|
|
||||||
pub family: Option<NodeFamily>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetFamilyByName`](crate::QueryMsg::GetFamilyByName).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct NodeFamilyByNameResponse {
|
|
||||||
/// The name that was queried, echoed back so callers can correlate.
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// The matching family, or `None` if no family with that name exists.
|
|
||||||
pub family: Option<NodeFamily>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetFamilyMembership`](crate::QueryMsg::GetFamilyMembership).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct NodeFamilyMembershipResponse {
|
|
||||||
/// The node that was queried.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// The id of the family the node currently belongs to, or `None` if the
|
|
||||||
/// node is not currently a member of any family.
|
|
||||||
pub family_id: Option<NodeFamilyId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pending [`FamilyInvitation`] paired with whether it has already timed
|
|
||||||
/// out at the time the query was served.
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PendingFamilyInvitationDetails {
|
|
||||||
/// The stored invitation as it was issued.
|
|
||||||
pub invitation: FamilyInvitation,
|
|
||||||
|
|
||||||
/// `true` iff `now >= invitation.expires_at` at query time, i.e. the
|
|
||||||
/// invitation is still in the pending map but can no longer be acted on.
|
|
||||||
pub expired: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetPendingInvitation`](crate::QueryMsg::GetPendingInvitation).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PendingFamilyInvitationResponse {
|
|
||||||
/// The family component of the queried `(family_id, node_id)` key.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The node component of the queried `(family_id, node_id)` key.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// The matching pending invitation along with an explicit expiry flag,
|
|
||||||
/// or `None` if no such invitation exists.
|
|
||||||
pub invitation: Option<PendingFamilyInvitationDetails>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One entry in a [`FamilyMembersPagedResponse`] page — pairs a node id with
|
|
||||||
/// its [`FamilyMembership`] record (notably its `joined_at` timestamp).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct FamilyMemberRecord {
|
|
||||||
/// The node currently in the family.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// The membership record (carries `family_id` and `joined_at`).
|
|
||||||
pub membership: FamilyMembership,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetFamilyMembersPaged`](crate::QueryMsg::GetFamilyMembersPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct FamilyMembersPagedResponse {
|
|
||||||
/// The family whose members were queried, echoed back so paginated
|
|
||||||
/// callers can correlate.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The members on this page, in ascending [`NodeId`] order.
|
|
||||||
pub members: Vec<FamilyMemberRecord>,
|
|
||||||
|
|
||||||
/// Cursor to pass as `start_after` on the next call, or `None` if this
|
|
||||||
/// page is empty (which the caller should treat as end-of-list).
|
|
||||||
pub start_next_after: Option<NodeId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetAllFamilyMembersPaged`](crate::QueryMsg::GetAllFamilyMembersPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct AllFamilyMembersPagedResponse {
|
|
||||||
/// The members on this page, in ascending [`NodeId`] order across every
|
|
||||||
/// family.
|
|
||||||
pub members: Vec<FamilyMemberRecord>,
|
|
||||||
|
|
||||||
/// Cursor (last `node_id`) to pass as `start_after` on the next call,
|
|
||||||
/// or `None` if this page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<NodeId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetPendingInvitationsForFamilyPaged`](crate::QueryMsg::GetPendingInvitationsForFamilyPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PendingFamilyInvitationsPagedResponse {
|
|
||||||
/// The family whose pending invitations were queried, echoed back so
|
|
||||||
/// paginated callers can correlate.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The pending invitations on this page, in ascending invitee
|
|
||||||
/// [`NodeId`] order, each stamped with whether it had already timed out
|
|
||||||
/// at the time the query was served.
|
|
||||||
pub invitations: Vec<PendingFamilyInvitationDetails>,
|
|
||||||
|
|
||||||
/// Cursor (last invitee node id) to pass as `start_after` on the next
|
|
||||||
/// call, or `None` if this page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<NodeId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetPendingInvitationsForNodePaged`](crate::QueryMsg::GetPendingInvitationsForNodePaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PendingInvitationsForNodePagedResponse {
|
|
||||||
/// The node whose pending invitations were queried, echoed back so
|
|
||||||
/// paginated callers can correlate.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// The pending invitations addressed to this node on this page, in
|
|
||||||
/// ascending [`NodeFamilyId`] order, each stamped with whether it had
|
|
||||||
/// already timed out at the time the query was served.
|
|
||||||
pub invitations: Vec<PendingFamilyInvitationDetails>,
|
|
||||||
|
|
||||||
/// Cursor (last issuing family id) to pass as `start_after` on the
|
|
||||||
/// next call, or `None` if this page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<NodeFamilyId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetAllPendingInvitationsPaged`](crate::QueryMsg::GetAllPendingInvitationsPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PendingInvitationsPagedResponse {
|
|
||||||
/// The pending invitations on this page, in ascending
|
|
||||||
/// `(family_id, node_id)` order, each stamped with whether it had
|
|
||||||
/// already timed out at the time the query was served.
|
|
||||||
pub invitations: Vec<PendingFamilyInvitationDetails>,
|
|
||||||
|
|
||||||
/// Cursor (last `(family_id, node_id)` pair) to pass as `start_after`
|
|
||||||
/// on the next call, or `None` if this page is empty (treat as
|
|
||||||
/// end-of-list).
|
|
||||||
pub start_next_after: Option<(NodeFamilyId, NodeId)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cursor for paginating per-family past-invitation listings: identifies a
|
|
||||||
/// single archive entry within a family by `(node_id, counter)`. The
|
|
||||||
/// `counter` is the per-`(family, node)` archive slot — multiple archived
|
|
||||||
/// invitations can exist for the same `(family, node)` pair (a node may be
|
|
||||||
/// invited and have the invitation reach a terminal state more than once).
|
|
||||||
pub type PastFamilyInvitationCursor = (NodeId, u64);
|
|
||||||
|
|
||||||
/// Cursor for paginating per-node past-invitation listings: identifies a
|
|
||||||
/// single archive entry addressed to a fixed node by `(family_id, counter)`.
|
|
||||||
pub type PastFamilyInvitationForNodeCursor = (NodeFamilyId, u64);
|
|
||||||
|
|
||||||
/// Cursor for paginating global past-invitation listings: identifies a
|
|
||||||
/// single archive entry across all families by `((family_id, node_id), counter)`.
|
|
||||||
pub type GlobalPastFamilyInvitationCursor = ((NodeFamilyId, NodeId), u64);
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetPastInvitationsForFamilyPaged`](crate::QueryMsg::GetPastInvitationsForFamilyPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PastFamilyInvitationsPagedResponse {
|
|
||||||
/// The family whose archived invitations were queried, echoed back so
|
|
||||||
/// paginated callers can correlate.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The archived invitations on this page, in ascending
|
|
||||||
/// `(node_id, counter)` order across all terminal statuses.
|
|
||||||
pub invitations: Vec<PastFamilyInvitation>,
|
|
||||||
|
|
||||||
/// Cursor to pass as `start_after` on the next call, or `None` if this
|
|
||||||
/// page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<PastFamilyInvitationCursor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetPastInvitationsForNodePaged`](crate::QueryMsg::GetPastInvitationsForNodePaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PastFamilyInvitationsForNodePagedResponse {
|
|
||||||
/// The node whose past invitations were queried, echoed back so
|
|
||||||
/// paginated callers can correlate.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// The archived invitations addressed to this node on this page, in
|
|
||||||
/// ascending `(family_id, counter)` order across all terminal statuses.
|
|
||||||
pub invitations: Vec<PastFamilyInvitation>,
|
|
||||||
|
|
||||||
/// Cursor to pass as `start_after` on the next call, or `None` if this
|
|
||||||
/// page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<PastFamilyInvitationForNodeCursor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetAllPastInvitationsPaged`](crate::QueryMsg::GetAllPastInvitationsPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct AllPastFamilyInvitationsPagedResponse {
|
|
||||||
/// The archived invitations on this page, in ascending
|
|
||||||
/// `((family_id, node_id), counter)` order across all terminal statuses.
|
|
||||||
pub invitations: Vec<PastFamilyInvitation>,
|
|
||||||
|
|
||||||
/// Cursor to pass as `start_after` on the next call, or `None` if this
|
|
||||||
/// page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<GlobalPastFamilyInvitationCursor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cursor for paginating per-family past-member listings: identifies a single
|
|
||||||
/// archive entry within a family by `(node_id, counter)`. The `counter` is the
|
|
||||||
/// per-`(family, node)` archive slot — multiple archived membership entries
|
|
||||||
/// can exist for the same `(family, node)` pair (a node may join, leave, and
|
|
||||||
/// re-join the same family more than once).
|
|
||||||
pub type PastFamilyMemberCursor = (NodeId, u64);
|
|
||||||
|
|
||||||
/// Cursor for paginating per-node past-member listings: identifies a single
|
|
||||||
/// archive entry for a fixed node by `(family_id, counter)`.
|
|
||||||
pub type PastFamilyMemberForNodeCursor = (NodeFamilyId, u64);
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetPastMembersForFamilyPaged`](crate::QueryMsg::GetPastMembersForFamilyPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PastFamilyMembersPagedResponse {
|
|
||||||
/// The family whose archived memberships were queried, echoed back so
|
|
||||||
/// paginated callers can correlate.
|
|
||||||
pub family_id: NodeFamilyId,
|
|
||||||
|
|
||||||
/// The archived membership records on this page, in ascending
|
|
||||||
/// `(node_id, counter)` order.
|
|
||||||
pub members: Vec<PastFamilyMember>,
|
|
||||||
|
|
||||||
/// Cursor to pass as `start_after` on the next call, or `None` if this
|
|
||||||
/// page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<PastFamilyMemberCursor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetPastMembersForNodePaged`](crate::QueryMsg::GetPastMembersForNodePaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct PastFamilyMembersForNodePagedResponse {
|
|
||||||
/// The node whose archived memberships were queried, echoed back so
|
|
||||||
/// paginated callers can correlate.
|
|
||||||
pub node_id: NodeId,
|
|
||||||
|
|
||||||
/// The archived membership records for this node on this page, in
|
|
||||||
/// ascending `(family_id, counter)` order.
|
|
||||||
pub members: Vec<PastFamilyMember>,
|
|
||||||
|
|
||||||
/// Cursor to pass as `start_after` on the next call, or `None` if this
|
|
||||||
/// page is empty (treat as end-of-list).
|
|
||||||
pub start_next_after: Option<PastFamilyMemberForNodeCursor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to [`QueryMsg::GetFamiliesPaged`](crate::QueryMsg::GetFamiliesPaged).
|
|
||||||
#[cw_serde]
|
|
||||||
pub struct FamiliesPagedResponse {
|
|
||||||
/// The families on this page, in ascending [`NodeFamilyId`] order.
|
|
||||||
pub families: Vec<NodeFamily>,
|
|
||||||
|
|
||||||
/// Cursor to pass as `start_after` on the next call, or `None` if this
|
|
||||||
/// page is empty (which the caller should treat as end-of-list).
|
|
||||||
pub start_next_after: Option<NodeFamilyId>,
|
|
||||||
}
|
|
||||||
@@ -27,9 +27,6 @@ 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 {
|
||||||
@@ -45,7 +42,6 @@ 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
|
||||||
@@ -69,7 +65,7 @@ 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, 4).await?;
|
let res = check_known_dealers(dkg_details).await?;
|
||||||
info!("there are {} known DKG dealers", res.results.len());
|
info!("there are {} known DKG dealers", res.results.len());
|
||||||
|
|
||||||
let Some(signing_threshold) = res.threshold else {
|
let Some(signing_threshold) = res.threshold else {
|
||||||
@@ -111,7 +107,7 @@ impl QuorumStateChecker {
|
|||||||
Ok(available)
|
Ok(available)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_forever(mut self) {
|
pub async fn run_forever(self) {
|
||||||
info!("starting quorum state checker");
|
info!("starting quorum state checker");
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -121,23 +117,7 @@ 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) => {
|
Ok(available) => self.quorum_state.available.store(available, Ordering::SeqCst),
|
||||||
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.total_tickets,
|
total_tickets: t.ticketbook.spent_tickets() as u32,
|
||||||
used_tickets: t.ticketbook.spent_tickets() as u32,
|
used_tickets: t.total_tickets,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -333,339 +333,3 @@ 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,13 +36,5 @@ 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,7 +6,6 @@ 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,
|
||||||
@@ -18,15 +17,8 @@ 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)]
|
||||||
@@ -200,49 +192,6 @@ 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,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user