Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79b8b4d324 | |||
| 69718db6d2 | |||
| 475a01c089 | |||
| 01e6a77cf1 | |||
| a348ff43b0 | |||
| bea4eb5cb0 | |||
| fa45b5e564 | |||
| 1a64442d9c | |||
| 46d8206713 | |||
| b8c1014fea | |||
| 43e4224f53 | |||
| d2817d6782 | |||
| e97c94ef9e | |||
| aa919a5351 | |||
| 7617675dcc | |||
| 276925814f | |||
| 8f9c26e7a6 | |||
| 556ea76cf8 | |||
| b424c6a8ff | |||
| 100eea8f64 | |||
| e52d977d46 | |||
| 30133a06ec | |||
| 261caae7f6 | |||
| 84fff02e12 | |||
| f005693643 | |||
| 5a0b20683e | |||
| 69c36e8cb0 | |||
| b09ac57597 | |||
| d8322d696b | |||
| 430255fea5 | |||
| 85a122f3eb | |||
| 3147d6aef7 | |||
| 74db9ab779 | |||
| 0bb287af89 | |||
| f4a2cec5aa | |||
| 7f434b2b26 | |||
| 3722c6c47d | |||
| 903a60e7c1 | |||
| 2d34a5ec3d | |||
| 0bdf750be9 | |||
| 44ae29b06d | |||
| bfd7240dcd | |||
| 9c680fd7b4 | |||
| c7d025baba | |||
| ec7482e417 | |||
| 307d326f82 | |||
| 2e746e9890 | |||
| e840c1fe93 | |||
| d6599b2933 | |||
| 5e0417ebe7 | |||
| de4239a5dd | |||
| c740f84336 | |||
| 16de47ba57 | |||
| 54a823311b | |||
| 2ca7c7a252 | |||
| e680e8dc49 | |||
| 242bc93807 | |||
| 94c6cdc7b2 | |||
| fce322c789 | |||
| ac5baab693 | |||
| 23da0f4d8e | |||
| 25e3b4cd83 | |||
| 8e4d72a565 | |||
| ad84a6d85d | |||
| 34c5f23684 | |||
| 000f2f1c29 | |||
| b747308f74 | |||
| afdd721cc3 | |||
| 9f5c4c5968 | |||
| 9583a5c6c8 | |||
| da60fc0ade | |||
| 96b54c455e | |||
| cc983963d4 | |||
| 40d9321aec | |||
| e5a29cc76e | |||
| 56c55f6b95 | |||
| 2f051fd943 | |||
| c03cf86000 | |||
| ab11508235 | |||
| e65bfaeb31 | |||
| 5a6982fd10 | |||
| 7abe1f505c | |||
| 1f748ecbe8 | |||
| 2dbfdf377a | |||
| 45f9ffa3a3 |
@@ -6,20 +6,27 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: arc-ubuntu-20.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: documentation/docs
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
|
||||
- name: Install pip3
|
||||
run: sudo apt install -y python3-pip
|
||||
run: sudo apt install -y python3-pip
|
||||
- name: Install Python3 modules
|
||||
run: sudo pip3 install pandas tabulate
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -29,33 +36,13 @@ jobs:
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
- name: Install mdbook and plugins
|
||||
run: cd documentation && ./install_mdbook_deps.sh
|
||||
- name: Remove existing Nym config directory (`~/.nym/`)
|
||||
run: cd documentation && ./remove_existing_config.sh
|
||||
continue-on-error: false
|
||||
# This is the original flow
|
||||
# - name: Build all projects in documentation/ & move to ~/dist/docs/
|
||||
# run: cd documentation && ./build_all_to_dist.sh
|
||||
|
||||
# This is a workaround replacement which builds on the last working commit b332a6b55668f60988e36961f3f62a794ba82ddb and then on current branch
|
||||
- name: Save current branch to ~/current_branch
|
||||
run: git rev-parse --abbrev-ref HEAD > ~/current_branch
|
||||
- name: Git pull, reset & switch to b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: git pull && git reset --hard && git checkout b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/ from b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
|
||||
- name: Switch to current branch
|
||||
run: git checkout $echo "$(cat ~/current_branch)"
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/ on current branch
|
||||
run: cd documentation && ./build_all_to_dist.sh && rm ~/current_branch
|
||||
|
||||
# End of replacemet
|
||||
|
||||
- name: Post process
|
||||
run: cd documentation && ./post_process.sh
|
||||
continue-on-error: false
|
||||
- name: Install project dependencies
|
||||
run: pnpm i
|
||||
- name: Build project
|
||||
run: pnpm run build
|
||||
- name: Move files to /dist/
|
||||
run: ../scripts/move-to-dist.sh
|
||||
|
||||
- name: Create Vercel project file
|
||||
uses: mobiledevops/secret-to-file-action@v1
|
||||
|
||||
@@ -15,24 +15,6 @@ on:
|
||||
type: boolean
|
||||
schedule:
|
||||
- cron: "14 0 * * *"
|
||||
pull_request:
|
||||
paths:
|
||||
- "clients/**"
|
||||
- "common/**"
|
||||
- "explorer-api/**"
|
||||
- "gateway/**"
|
||||
- "integrations/**"
|
||||
- "mixnode/**"
|
||||
- "nym-api/**"
|
||||
- "nym-node/**"
|
||||
- "nym-outfox/**"
|
||||
- 'nym-data-observatory/**'
|
||||
- "nym-validator-rewarder/**"
|
||||
- "sdk/rust/nym-sdk/**"
|
||||
- "service-providers/**"
|
||||
- "tools/**"
|
||||
- "nymvisor/**"
|
||||
- ".github/workflows/ci-build-upload-binaries.yml"
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
cargo-deny:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
checks:
|
||||
|
||||
@@ -2,9 +2,5 @@
|
||||
{
|
||||
"rust":"stable",
|
||||
"runOnEvent":"always"
|
||||
},
|
||||
{
|
||||
"rust":"beta",
|
||||
"runOnEvent":"pull_request"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,11 +2,6 @@ name: ci-contracts-upload-binaries
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- '.github/workflows/ci-contracts-upload-binaries.yml'
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
@@ -3,28 +3,35 @@ name: ci-docs
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore: master
|
||||
branches-ignore: [master]
|
||||
paths:
|
||||
- 'documentation/docs/**'
|
||||
- '.github/workflows/ci-docs.yml'
|
||||
- "documentation/docs/**"
|
||||
- ".github/workflows/ci-docs.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: arc-ubuntu-20.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: documentation/docs
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
|
||||
- name: Install pip3
|
||||
run: sudo apt install -y python3-pip
|
||||
run: sudo apt install -y python3-pip
|
||||
- name: Install Python3 modules
|
||||
run: sudo pip3 install pandas tabulate
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -34,30 +41,13 @@ jobs:
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
- name: Install mdbook and plugins
|
||||
run: cd documentation && ./install_mdbook_deps.sh
|
||||
- name: Remove existing Nym config directory (`~/.nym/`)
|
||||
run: cd documentation && ./remove_existing_config.sh
|
||||
continue-on-error: false
|
||||
|
||||
# This is the original flow
|
||||
# - name: Build all projects in documentation/ & move to ~/dist/docs/
|
||||
# run: cd documentation && ./build_all_to_dist.sh
|
||||
|
||||
# This is a workaround replacement which builds on the last working commit b332a6b55668f60988e36961f3f62a794ba82ddb and then on current branch
|
||||
- name: Save current branch to ~/current_branch
|
||||
run: git rev-parse --abbrev-ref HEAD > ~/current_branch
|
||||
- name: Git pull, reset & switch to b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: git pull && git reset --hard && git checkout b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/ from b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
|
||||
- name: Switch to current branch
|
||||
run: git checkout $echo "$(cat ~/current_branch)"
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/ on current branch
|
||||
run: cd documentation && ./build_all_to_dist.sh && rm ~/current_branch
|
||||
|
||||
# End of replacemet
|
||||
- name: Install project dependencies
|
||||
run: pnpm i
|
||||
- name: Build project
|
||||
run: pnpm run build
|
||||
- name: Move files to /dist/
|
||||
run: ../scripts/move-to-dist.sh
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
@@ -68,5 +58,5 @@ jobs:
|
||||
SOURCE: "dist/docs/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/docs-${{ env.GITHUB_REF_SLUG }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/docs-nextra-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/node_modules/"
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
name: ci-nym-api-tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "nym-api/**"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nym-api/tests
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: nym-api tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: install yarn in root
|
||||
run: cd ../.. && yarn install
|
||||
|
||||
- name: Install npm
|
||||
run: npm install
|
||||
|
||||
- name: Node v18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.1.0
|
||||
|
||||
- name: Install yarn
|
||||
run: yarn install
|
||||
|
||||
- name: Run yarn
|
||||
run: yarn
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test:sandbox
|
||||
working-directory: nym-api/tests
|
||||
@@ -1,45 +0,0 @@
|
||||
name: ci-nym-credential-proxy
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'common/**'
|
||||
- 'nym-credential-proxy/**'
|
||||
- '.github/workspace/ci-nym-credential-proxy.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
MANIFEST_PATH: "--manifest-path nym-credential-proxy/Cargo.toml"
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: ${{ env.MANIFEST_PATH }} --all -- --check
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: ${{ env.MANIFEST_PATH }} --workspace --all-targets
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: ${{ env.MANIFEST_PATH }} --workspace --all-targets -- -D warnings
|
||||
@@ -1,79 +0,0 @@
|
||||
name: ci-sdk-docs-typescript
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "sdk/typescript/**"
|
||||
- "wasm/**"
|
||||
- '.github/workflows/ci-sdk-docs-typescript.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
continue-on-error: true
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.17
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Install wasm-opt
|
||||
uses: ./.github/actions/install-wasm-opt
|
||||
with:
|
||||
version: '116'
|
||||
|
||||
- name: Build branch WASM packages
|
||||
run: make sdk-wasm-build
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
- name: Build
|
||||
run: yarn docs:prod:build
|
||||
- name: Deploy branch to CI www (docs)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "dist/ts/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ts-packages
|
||||
NYM_PROJECT_NAME: "sdk-ts-docs"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}/docs/sdk/typescript"
|
||||
NYM_CI_WWW_LOCATION: "sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -4,7 +4,9 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- 'release/**'
|
||||
- "release/**"
|
||||
paths-ignore:
|
||||
- "documentation/**"
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Build and upload Data observatory container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-data-observatory"
|
||||
CONTAINER_NAME: "data-observatory"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.3
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
|
||||
fi
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
@@ -2,6 +2,10 @@ name: Build and upload Node Status agent container to harbor.nymte.ch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gateway_probe_git_ref:
|
||||
type: string
|
||||
description: Which gateway probe git ref to build the image with
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-node-status-agent"
|
||||
@@ -32,25 +36,26 @@ jobs:
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
- name: cleanup-gateway-probe-ref
|
||||
id: cleanup_gateway_probe_ref
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
|
||||
fi
|
||||
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
|
||||
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
|
||||
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Build and upload nym node container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-node"
|
||||
CONTAINER_NAME: "nym-node"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.3
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
|
||||
fi
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Build and upload Validator Rewarder container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-validator-rewarder"
|
||||
CONTAINER_NAME: "validator-rewarder"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.3
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
Generated
+293
-161
File diff suppressed because it is too large
Load Diff
+32
-28
@@ -19,33 +19,33 @@ members = [
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
"common/authenticator-requests",
|
||||
"common/async-file-watcher",
|
||||
"common/authenticator-requests",
|
||||
"common/bandwidth-controller",
|
||||
"common/bin-common",
|
||||
"common/client-core",
|
||||
"common/client-core/config-types",
|
||||
"common/client-core/surb-storage",
|
||||
"common/client-core/gateways-storage",
|
||||
"common/client-core/surb-storage",
|
||||
"common/client-libs/gateway-client",
|
||||
"common/client-libs/mixnet-client",
|
||||
"common/client-libs/validator-client",
|
||||
"common/commands",
|
||||
"common/config",
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/ecash-contract",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/ecash-contract",
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
"common/cosmwasm-smart-contracts/multisig-contract",
|
||||
"common/cosmwasm-smart-contracts/vesting-contract",
|
||||
"common/country-group",
|
||||
"common/credential-storage",
|
||||
"common/credentials",
|
||||
"common/credential-utils",
|
||||
"common/credentials-interface",
|
||||
"common/credential-verification",
|
||||
"common/credentials",
|
||||
"common/credentials-interface",
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/ecash-double-spending",
|
||||
@@ -54,6 +54,7 @@ members = [
|
||||
"common/exit-policy",
|
||||
"common/gateway-requests",
|
||||
"common/gateway-storage",
|
||||
"common/gateway-stats-storage",
|
||||
"common/http-api-client",
|
||||
"common/http-api-common",
|
||||
"common/inclusion-probability",
|
||||
@@ -64,10 +65,10 @@ members = [
|
||||
"common/network-defaults",
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nymcoconut",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -89,6 +90,7 @@ members = [
|
||||
"common/statistics",
|
||||
"common/store-cipher",
|
||||
"common/task",
|
||||
"common/ticketbooks-merkle",
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
"common/types",
|
||||
@@ -97,26 +99,30 @@ members = [
|
||||
"common/wasm/utils",
|
||||
"common/wireguard",
|
||||
"common/wireguard-types",
|
||||
# "documentation/autodoc",
|
||||
"explorer-api",
|
||||
"explorer-api/explorer-api-requests",
|
||||
"explorer-api/explorer-client",
|
||||
"gateway",
|
||||
"integrations/bity",
|
||||
"mixnode",
|
||||
"sdk/ffi/cpp",
|
||||
"sdk/ffi/go",
|
||||
"sdk/ffi/shared",
|
||||
"sdk/lib/socks5-listener",
|
||||
"sdk/rust/nym-sdk",
|
||||
"sdk/ffi/shared",
|
||||
"sdk/ffi/go",
|
||||
"sdk/ffi/cpp",
|
||||
"service-providers/authenticator",
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"nym-network-monitor",
|
||||
"nym-api",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-data-observatory",
|
||||
"nym-network-monitor",
|
||||
"nym-node",
|
||||
"nym-node/nym-node-http-api",
|
||||
"nym-node/nym-node-requests",
|
||||
@@ -139,11 +145,11 @@ members = [
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -154,6 +160,7 @@ default-members = [
|
||||
"gateway",
|
||||
"mixnode",
|
||||
"nym-api",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-data-observatory",
|
||||
"nym-node",
|
||||
"nym-node-status-api",
|
||||
@@ -189,7 +196,7 @@ aes = "0.8.1"
|
||||
aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.89"
|
||||
anyhow = "1.0.90"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.83"
|
||||
axum-client-ip = "0.6.1"
|
||||
@@ -198,11 +205,8 @@ axum-extra = "0.9.4"
|
||||
base64 = "0.22.1"
|
||||
bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
|
||||
# can we unify those?
|
||||
bit-vec = "0.7.0"
|
||||
bit-vec = "0.7.0" # can we unify those?
|
||||
bitvec = "1.0.0"
|
||||
|
||||
blake3 = "1.5.4"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
@@ -215,7 +219,7 @@ chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.31"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.18"
|
||||
clap = "4.5.20"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.0"
|
||||
@@ -269,23 +273,24 @@ ipnetwork = "0.20"
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.13.0"
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.4.0"
|
||||
lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
log = "0.4"
|
||||
maxminddb = "0.23.0"
|
||||
rs_merkle = "1.4.2"
|
||||
mime = "0.3.17"
|
||||
moka = { version = "0.12", features = ["future"] }
|
||||
nix = "0.27.1"
|
||||
notify = "5.1.0"
|
||||
okapi = "0.7.0"
|
||||
once_cell = "1.7.2"
|
||||
once_cell = "1.20.2"
|
||||
opentelemetry = "0.19.0"
|
||||
opentelemetry-jaeger = "0.18.0"
|
||||
parking_lot = "0.12.3"
|
||||
pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
pin-project = "1.0"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
publicsuffix = "2.2.3"
|
||||
@@ -305,7 +310,7 @@ rocket_okapi = "0.8.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.23"
|
||||
serde = "1.0.210"
|
||||
serde = "1.0.211"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.132"
|
||||
@@ -402,11 +407,10 @@ indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", bra
|
||||
js-sys = "0.3.70"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-futures = "0.4.43"
|
||||
wasm-bindgen = "0.2.95"
|
||||
wasm-bindgen-futures = "0.4.45"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
web-sys = "0.3.72"
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
|
||||
@@ -102,5 +102,10 @@ average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
|
||||
[debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
[debug.stats_reporting]
|
||||
enabled = {{ debug.stats_reporting.enabled }}
|
||||
provider_address = '{{ debug.stats_reporting.provider_address }}'
|
||||
reporting_interval = '{{ debug.stats_reporting.reporting_interval }}'
|
||||
|
||||
|
||||
"#;
|
||||
|
||||
@@ -81,6 +81,7 @@ impl From<Init> for OverrideConfig {
|
||||
|
||||
nyxd_urls: init_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
||||
stats_reporting_address: init_config.common_args.stats_reporting_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use clap::{Parser, Subcommand};
|
||||
use log::{error, info};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_client::client::Recipient;
|
||||
use nym_client_core::cli_helpers::CliClient;
|
||||
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
||||
use nym_config::OptionalSet;
|
||||
@@ -104,6 +105,7 @@ pub(crate) struct OverrideConfig {
|
||||
no_cover: bool,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
stats_reporting_address: Option<Recipient>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
@@ -149,6 +151,11 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
BaseClientConfig::with_disabled_credentials,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
)
|
||||
.with_optional_env_ext(
|
||||
BaseClientConfig::with_enabled_stats_reporting_address,
|
||||
args.stats_reporting_address,
|
||||
nym_network_defaults::var_names::CLIENT_STATS_COLLECTION_PROVIDER,
|
||||
)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
|
||||
|
||||
@@ -43,6 +43,7 @@ impl From<Run> for OverrideConfig {
|
||||
no_cover: run_config.common_args.no_cover,
|
||||
nyxd_urls: run_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
||||
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ impl From<Init> for OverrideConfig {
|
||||
nyxd_urls: init_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
||||
outfox: false,
|
||||
stats_reporting_address: init_config.common_args.stats_reporting_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
||||
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
|
||||
use nym_client_core::config::{GroupBy, TopologyStructure};
|
||||
use nym_config::OptionalSet;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
@@ -111,6 +112,7 @@ pub(crate) struct OverrideConfig {
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
outfox: bool,
|
||||
stats_reporting_address: Option<Recipient>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
@@ -196,6 +198,11 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
BaseClientConfig::with_disabled_credentials,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
)
|
||||
.with_optional_base_env(
|
||||
BaseClientConfig::with_enabled_stats_reporting_address,
|
||||
args.stats_reporting_address,
|
||||
nym_network_defaults::var_names::CLIENT_STATS_COLLECTION_PROVIDER,
|
||||
)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
|
||||
@@ -70,6 +70,7 @@ impl From<Run> for OverrideConfig {
|
||||
nyxd_urls: run_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
||||
outfox: run_config.outfox,
|
||||
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,4 +108,9 @@ average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
|
||||
[core.debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
[core.debug.stats_reporting]
|
||||
enabled = {{ core.debug.stats_reporting.enabled }}
|
||||
provider_address = '{{ core.debug.stats_reporting.provider_address }}'
|
||||
reporting_interval = '{{ core.debug.stats_reporting.reporting_interval }}'
|
||||
|
||||
"#;
|
||||
|
||||
@@ -17,6 +17,7 @@ thiserror = { workspace = true }
|
||||
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-service-provider-requests-common = { path = "../service-provider-requests-common" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
pub mod v1;
|
||||
pub mod v2;
|
||||
pub mod v3;
|
||||
pub mod v4;
|
||||
|
||||
mod error;
|
||||
|
||||
pub use error::Error;
|
||||
pub use v3 as latest;
|
||||
pub use v4 as latest;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 3;
|
||||
pub const CURRENT_VERSION: u8 = 4;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
|
||||
@@ -17,6 +17,11 @@ fn generate_random() -> u64 {
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct QueryMessage {
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
@@ -65,7 +70,7 @@ impl AuthenticatorRequest {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
|
||||
pub fn new_query_request(query_message: QueryMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
@@ -73,7 +78,7 @@ impl AuthenticatorRequest {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
data: AuthenticatorRequestData::QueryBandwidth(query_message.pub_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ impl From<v2::request::AuthenticatorRequest> for v3::request::AuthenticatorReque
|
||||
fn from(authenticator_request: v2::request::AuthenticatorRequest) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: 2,
|
||||
version: 3,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: authenticator_request.data.into(),
|
||||
|
||||
@@ -20,6 +20,11 @@ fn generate_random() -> u64 {
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct QueryMessage {
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
@@ -68,7 +73,7 @@ impl AuthenticatorRequest {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
|
||||
pub fn new_query_request(query_message: QueryMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
@@ -76,7 +81,7 @@ impl AuthenticatorRequest {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
data: AuthenticatorRequestData::QueryBandwidth(query_message.pub_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
use crate::{v3, v4};
|
||||
|
||||
impl From<v3::request::AuthenticatorRequest> for v4::request::AuthenticatorRequest {
|
||||
fn from(authenticator_request: v3::request::AuthenticatorRequest) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: 4,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: authenticator_request.data.into(),
|
||||
reply_to: authenticator_request.reply_to,
|
||||
request_id: authenticator_request.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::request::AuthenticatorRequestData> for v4::request::AuthenticatorRequestData {
|
||||
fn from(authenticator_request_data: v3::request::AuthenticatorRequestData) -> Self {
|
||||
match authenticator_request_data {
|
||||
v3::request::AuthenticatorRequestData::Initial(init_msg) => {
|
||||
v4::request::AuthenticatorRequestData::Initial(init_msg.into())
|
||||
}
|
||||
v3::request::AuthenticatorRequestData::Final(gw_client) => {
|
||||
v4::request::AuthenticatorRequestData::Final(gw_client.into())
|
||||
}
|
||||
v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => {
|
||||
v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
}
|
||||
v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::InitMessage> for v4::registration::InitMessage {
|
||||
fn from(init_msg: v3::registration::InitMessage) -> Self {
|
||||
Self {
|
||||
pub_key: init_msg.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<v3::registration::FinalMessage>> for Box<v4::registration::FinalMessage> {
|
||||
fn from(gw_client: Box<v3::registration::FinalMessage>) -> Self {
|
||||
Box::new(v4::registration::FinalMessage {
|
||||
gateway_client: gw_client.gateway_client.into(),
|
||||
credential: gw_client.credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<v3::topup::TopUpMessage>> for Box<v4::topup::TopUpMessage> {
|
||||
fn from(top_up_message: Box<v3::topup::TopUpMessage>) -> Self {
|
||||
Box::new(v4::topup::TopUpMessage {
|
||||
pub_key: top_up_message.pub_key,
|
||||
credential: top_up_message.credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::GatewayClient> for v4::registration::GatewayClient {
|
||||
fn from(gw_client: v3::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gw_client.pub_key,
|
||||
private_ips: gw_client.private_ip.into(),
|
||||
mac: gw_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::GatewayClient> for v3::registration::GatewayClient {
|
||||
fn from(gw_client: v4::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gw_client.pub_key,
|
||||
private_ip: gw_client.private_ips.ipv4.into(),
|
||||
mac: gw_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::ClientMac> for v4::registration::ClientMac {
|
||||
fn from(mac: v3::registration::ClientMac) -> Self {
|
||||
Self::new(mac.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::ClientMac> for v3::registration::ClientMac {
|
||||
fn from(mac: v4::registration::ClientMac) -> Self {
|
||||
Self::new(mac.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v4::response::AuthenticatorResponse> for v3::response::AuthenticatorResponse {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_response: v4::response::AuthenticatorResponse,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
data: authenticator_response.data.try_into()?,
|
||||
reply_to: authenticator_response.reply_to,
|
||||
protocol: authenticator_response.protocol,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v4::response::AuthenticatorResponseData> for v3::response::AuthenticatorResponseData {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_response_data: v4::response::AuthenticatorResponseData,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match authenticator_response_data {
|
||||
v4::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Ok(
|
||||
v3::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response.into(),
|
||||
),
|
||||
),
|
||||
v4::response::AuthenticatorResponseData::Registered(registered_response) => Ok(
|
||||
v3::response::AuthenticatorResponseData::Registered(registered_response.into()),
|
||||
),
|
||||
v4::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Ok(v3::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response.into(),
|
||||
)),
|
||||
v4::response::AuthenticatorResponseData::TopUpBandwidth(_) => {
|
||||
Err(Self::Error::Conversion(
|
||||
"a v3 request couldn't produce a v4 only type of response".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::PendingRegistrationResponse> for v3::response::PendingRegistrationResponse {
|
||||
fn from(value: v4::response::PendingRegistrationResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::RegisteredResponse> for v3::response::RegisteredResponse {
|
||||
fn from(value: v4::response::RegisteredResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::RemainingBandwidthResponse> for v3::response::RemainingBandwidthResponse {
|
||||
fn from(value: v4::response::RemainingBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistrationData> for v3::registration::RegistrationData {
|
||||
fn from(value: v4::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistredData> for v3::registration::RegistredData {
|
||||
fn from(value: v4::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ips.ipv4.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RemainingBandwidthData> for v3::registration::RemainingBandwidthData {
|
||||
fn from(value: v4::registration::RemainingBandwidthData) -> Self {
|
||||
Self {
|
||||
available_bandwidth: value.available_bandwidth,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod conversion;
|
||||
pub mod registration;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod topup;
|
||||
|
||||
pub const VERSION: u8 = 4;
|
||||
@@ -0,0 +1,281 @@
|
||||
// -2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
use hmac::{Hmac, Mac};
|
||||
#[cfg(feature = "verify")]
|
||||
use nym_crypto::asymmetric::encryption::PrivateKey;
|
||||
#[cfg(feature = "verify")]
|
||||
use sha2::Sha256;
|
||||
|
||||
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = HashMap<IpPair, Taken>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for IpPair {
|
||||
fn from(value: IpAddr) -> Self {
|
||||
let (before_last_byte, last_byte) = match value {
|
||||
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
||||
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
||||
};
|
||||
let last_bytes = (before_last_byte as u16) << 8 | last_byte as u16;
|
||||
let ipv4 = Ipv4Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||
before_last_byte,
|
||||
last_byte,
|
||||
);
|
||||
let ipv6 = Ipv6Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
|
||||
last_bytes,
|
||||
);
|
||||
IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InitMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
impl InitMessage {
|
||||
pub fn new(pub_key: PeerPublicKey) -> Self {
|
||||
InitMessage { pub_key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FinalMessage {
|
||||
/// Gateway client data
|
||||
pub gateway_client: GatewayClient,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: Option<CredentialSpendingData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistrationData {
|
||||
pub nonce: u64,
|
||||
pub gateway_data: GatewayClient,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GatewayClient {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Assigned private IPs (v4 and v6)
|
||||
pub private_ips: IpPair,
|
||||
|
||||
/// Sha256 hmac on the data (alongside the prior nonce)
|
||||
pub mac: ClientMac,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn new(
|
||||
local_secret: &PrivateKey,
|
||||
remote_public: x25519_dalek::PublicKey,
|
||||
private_ips: IpPair,
|
||||
nonce: u64,
|
||||
) -> Self {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
|
||||
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
|
||||
|
||||
let dh = static_secret.diffie_hellman(&remote_public);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(local_public.as_bytes());
|
||||
mac.update(private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
GatewayClient {
|
||||
pub_key: PeerPublicKey::new(local_public),
|
||||
private_ips,
|
||||
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable secret should be gateways Wireguard PK
|
||||
// Client should perform this step when generating its payload, using its own WG PK
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
|
||||
|
||||
let dh = static_secret.diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(self.pub_key.as_bytes());
|
||||
mac.update(self.private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
mac.verify_slice(&self.mac)
|
||||
.map_err(|source| Error::FailedClientMacVerification {
|
||||
client: self.pub_key.to_string(),
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
|
||||
// TODO2: rely on our internal crypto/hmac
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientMac(Vec<u8>);
|
||||
|
||||
impl fmt::Display for ClientMac {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMac {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(mac: Vec<u8>) -> Self {
|
||||
ClientMac(mac)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ClientMac {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClientMac {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mac_bytes: Vec<u8> =
|
||||
general_purpose::STANDARD
|
||||
.decode(s)
|
||||
.map_err(|source| Error::MalformedClientMac {
|
||||
mac: s.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(ClientMac(mac_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ClientMac {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
|
||||
serializer.serialize_str(&encoded_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientMac {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let encoded_key = String::deserialize(deserializer)?;
|
||||
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
|
||||
#[test]
|
||||
fn create_ip_pair() {
|
||||
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
|
||||
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
|
||||
|
||||
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "verify")]
|
||||
fn client_request_roundtrip() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
let client_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
|
||||
let nonce = 1234567890;
|
||||
|
||||
let client = GatewayClient::new(
|
||||
client_key_pair.private_key(),
|
||||
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
|
||||
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
|
||||
nonce,
|
||||
);
|
||||
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
registration::{FinalMessage, InitMessage},
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct QueryMessage {
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub reply_to: Recipient,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(final_message: FinalMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Final(Box::new(final_message)),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(query_message: QueryMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::QueryBandwidth(query_message.pub_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_topup_request(top_up_message: TopUpMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(Box<FinalMessage>),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
TopUpBandwidth(Box<TopUpMessage>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_first_bytes_protocol() {
|
||||
let version = 4;
|
||||
let data = AuthenticatorRequest {
|
||||
protocol: Protocol { version, service_provider_type: ServiceProviderType::Authenticator },
|
||||
data: AuthenticatorRequestData::Initial(InitMessage::new(
|
||||
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
|
||||
)),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
request_id: 1,
|
||||
};
|
||||
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
|
||||
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorResponseData,
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_topup_bandwidth(
|
||||
remaining_bandwidth_data: RemainingBandwidthData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Recipient {
|
||||
self.reply_to
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
TopUpBandwidth(TopUpBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistrationData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TopUpBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RemainingBandwidthData,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TopUpMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: CredentialSpendingData,
|
||||
}
|
||||
@@ -14,14 +14,15 @@ thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client]
|
||||
path = "../client-libs/validator-client"
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// See other comments for other TaskStatus message enumds about abusing the Error trait when we
|
||||
// should have a new trait for TaskStatus messages
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum BandwidthStatusMessage {
|
||||
#[error("remaining bandwidth: {0}")]
|
||||
RemainingBandwidth(i64),
|
||||
|
||||
#[error("no bandwidth left")]
|
||||
NoBandwidth,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BandwidthStatusMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BandwidthStatusMessage::RemainingBandwidth(b) => {
|
||||
write!(f, "remaining bandwidth: {}", b)
|
||||
}
|
||||
BandwidthStatusMessage::NoBandwidth => write!(f, "no bandwidth left"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl nym_task::TaskStatusEvent for BandwidthStatusMessage {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-topology = { path = "../topology", features = ["serializable"] }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
|
||||
@@ -23,4 +23,4 @@ nym-sphinx-addressing = { path = "../../nymsphinx/addressing" }
|
||||
|
||||
|
||||
[features]
|
||||
disk-persistence = ["nym-pemstore"]
|
||||
disk-persistence = ["nym-pemstore"]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
|
||||
use nym_sphinx_addressing::Recipient;
|
||||
use nym_sphinx_params::{PacketSize, PacketType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -61,6 +62,11 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6
|
||||
// 24 hours
|
||||
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
|
||||
|
||||
// stats reporting related
|
||||
|
||||
/// Time interval between reporting statistics to the given provider if it exist
|
||||
const STATS_REPORT_INTERVAL_SECS: Duration = Duration::from_secs(300);
|
||||
|
||||
use crate::error::InvalidTrafficModeFailure;
|
||||
pub use nym_country_group::CountryGroup;
|
||||
|
||||
@@ -133,6 +139,12 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_enabled_stats_reporting_address(mut self, address: Recipient) -> Self {
|
||||
self.debug.stats_reporting.provider_address = Some(address);
|
||||
self.debug.stats_reporting.enabled = true; //since we are overriding the address, we assume the reporting should be enabled
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: this should be refactored properly
|
||||
// as of 12.09.23 the below is true (not sure how this comment will rot in the future)
|
||||
// medium_toggle:
|
||||
@@ -631,6 +643,34 @@ impl Default for ReplySurbs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct StatsReporting {
|
||||
/// Is stats reporting enabled
|
||||
pub enabled: bool,
|
||||
|
||||
/// Address of the stats collector. If this is none, no reporting will happen, regardless of `enabled`
|
||||
#[serde(
|
||||
serialize_with = "ser_maybe_stringified",
|
||||
deserialize_with = "de_maybe_stringified"
|
||||
)]
|
||||
pub provider_address: Option<Recipient>,
|
||||
|
||||
/// With what frequence will statistics be sent
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub reporting_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for StatsReporting {
|
||||
fn default() -> Self {
|
||||
StatsReporting {
|
||||
enabled: true,
|
||||
provider_address: None,
|
||||
reporting_interval: STATS_REPORT_INTERVAL_SECS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct DebugConfig {
|
||||
@@ -651,6 +691,9 @@ pub struct DebugConfig {
|
||||
|
||||
/// Defines all configuration options related to reply SURBs.
|
||||
pub reply_surbs: ReplySurbs,
|
||||
|
||||
/// Defines all configuration options related to stats reporting.
|
||||
pub stats_reporting: StatsReporting,
|
||||
}
|
||||
|
||||
impl DebugConfig {
|
||||
@@ -672,6 +715,7 @@ impl Default for DebugConfig {
|
||||
acknowledgements: Default::default(),
|
||||
topology: Default::default(),
|
||||
reply_surbs: Default::default(),
|
||||
stats_reporting: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ impl From<ConfigV5> for Config {
|
||||
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
||||
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
||||
},
|
||||
stats_reporting: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::{
|
||||
use log::info;
|
||||
use nym_client_core_gateways_storage::GatewayDetails;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -88,6 +89,10 @@ pub struct CommonClientInitArgs {
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
pub no_cover: bool,
|
||||
|
||||
/// Sets the address to report statistics
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
pub stats_reporting_address: Option<Recipient>,
|
||||
}
|
||||
|
||||
pub struct InitResultsWithConfig<T> {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg_attr(feature = "cli", derive(clap::Args))]
|
||||
@@ -56,4 +57,8 @@ pub struct CommonClientRunArgs {
|
||||
// has defined the conflict on that field itself
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
pub no_cover: bool,
|
||||
|
||||
/// Sets the address to report statistics
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
pub stats_reporting_address: Option<Recipient>,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::packet_statistics_control::PacketStatisticsReporter;
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::statistics_control::StatisticsControl;
|
||||
use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
@@ -12,7 +12,6 @@ use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use crate::client::packet_statistics_control::PacketStatisticsControl;
|
||||
use crate::client::real_messages_control;
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
use crate::client::received_buffer::{
|
||||
@@ -49,6 +48,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_statistics_common::generate_client_stats_id;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
@@ -59,6 +60,7 @@ use std::fmt::Debug;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(all(
|
||||
@@ -273,7 +275,7 @@ where
|
||||
self_address: Recipient,
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
@@ -306,7 +308,7 @@ where
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
shutdown: TaskClient,
|
||||
packet_type: PacketType,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
) {
|
||||
info!("Starting real traffic stream...");
|
||||
|
||||
@@ -335,7 +337,7 @@ where
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
shutdown: TaskClient,
|
||||
packet_statistics_control: PacketStatisticsReporter,
|
||||
metrics_reporter: ClientStatsSender,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
let controller: ReceivedMessagesBufferController<SphinxMessageReceiver> =
|
||||
@@ -345,7 +347,7 @@ where
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
packet_statistics_control,
|
||||
metrics_reporter,
|
||||
);
|
||||
controller.start_with_shutdown(shutdown)
|
||||
}
|
||||
@@ -356,6 +358,7 @@ where
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
@@ -371,7 +374,12 @@ where
|
||||
|
||||
let mut gateway_client =
|
||||
if let Some(existing_client) = initialisation_result.authenticated_ephemeral_client {
|
||||
existing_client.upgrade(packet_router, bandwidth_controller, shutdown)
|
||||
existing_client.upgrade(
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
shutdown,
|
||||
)
|
||||
} else {
|
||||
let cfg = GatewayConfig::new(
|
||||
details.gateway_id,
|
||||
@@ -392,6 +400,7 @@ where
|
||||
Some(details.shared_key),
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
shutdown,
|
||||
)
|
||||
};
|
||||
@@ -444,6 +453,7 @@ where
|
||||
Ok(gateway_client)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn setup_gateway_transceiver(
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
config: &Config,
|
||||
@@ -451,6 +461,7 @@ where
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
|
||||
where
|
||||
@@ -481,6 +492,7 @@ where
|
||||
bandwidth_controller,
|
||||
details_store,
|
||||
packet_router,
|
||||
stats_reporter,
|
||||
shutdown,
|
||||
)
|
||||
.await?;
|
||||
@@ -586,11 +598,23 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_packet_statistics_control(shutdown: TaskClient) -> PacketStatisticsReporter {
|
||||
info!("Starting packet statistics control...");
|
||||
let (packet_statistics_control, packet_stats_reporter) = PacketStatisticsControl::new();
|
||||
packet_statistics_control.start_with_shutdown(shutdown);
|
||||
packet_stats_reporter
|
||||
fn start_statistics_control(
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
client_stats_id: String,
|
||||
input_sender: Sender<InputMessage>,
|
||||
shutdown: TaskClient,
|
||||
) -> ClientStatsSender {
|
||||
info!("Starting statistics control...");
|
||||
StatisticsControl::create_and_start_with_shutdown(
|
||||
config.debug.stats_reporting,
|
||||
user_agent
|
||||
.map(|u| u.application)
|
||||
.unwrap_or("unknown".to_string()),
|
||||
client_stats_id,
|
||||
input_sender.clone(),
|
||||
shutdown.with_suffix("controller"),
|
||||
)
|
||||
}
|
||||
|
||||
fn start_mix_traffic_controller(
|
||||
@@ -720,6 +744,14 @@ where
|
||||
self.user_agent.clone(),
|
||||
);
|
||||
|
||||
let stats_reporter = Self::start_statistics_control(
|
||||
self.config,
|
||||
self.user_agent.clone(),
|
||||
generate_client_stats_id(*self_address.identity()),
|
||||
input_sender.clone(),
|
||||
shutdown.fork("statistics_control"),
|
||||
);
|
||||
|
||||
// needs to be started as the first thing to block if required waiting for the gateway
|
||||
Self::start_topology_refresher(
|
||||
topology_provider,
|
||||
@@ -731,9 +763,6 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let packet_stats_reporter =
|
||||
Self::start_packet_statistics_control(shutdown.fork("packet_statistics_control"));
|
||||
|
||||
let gateway_packet_router = PacketRouter::new(
|
||||
ack_sender,
|
||||
mixnet_messages_sender,
|
||||
@@ -747,6 +776,7 @@ where
|
||||
bandwidth_controller,
|
||||
&details_store,
|
||||
gateway_packet_router,
|
||||
stats_reporter.clone(),
|
||||
shutdown.fork("gateway_transceiver"),
|
||||
)
|
||||
.await?;
|
||||
@@ -765,7 +795,7 @@ where
|
||||
reply_storage.key_storage(),
|
||||
reply_controller_sender.clone(),
|
||||
shutdown.fork("received_messages_buffer"),
|
||||
packet_stats_reporter.clone(),
|
||||
stats_reporter.clone(),
|
||||
);
|
||||
|
||||
// The message_sender is the transmitter for any component generating sphinx packets
|
||||
@@ -804,7 +834,7 @@ where
|
||||
client_connection_rx,
|
||||
shutdown.fork("real_traffic_controller"),
|
||||
self.config.debug.traffic.packet_type,
|
||||
packet_stats_reporter.clone(),
|
||||
stats_reporter.clone(),
|
||||
);
|
||||
|
||||
if !self
|
||||
@@ -819,7 +849,7 @@ where
|
||||
self_address,
|
||||
shared_topology_accessor.clone(),
|
||||
message_sender,
|
||||
packet_stats_reporter,
|
||||
stats_reporter.clone(),
|
||||
shutdown.fork("cover_traffic_stream"),
|
||||
);
|
||||
}
|
||||
@@ -847,6 +877,7 @@ where
|
||||
topology_accessor: shared_topology_accessor,
|
||||
gateway_connection: GatewayConnection { gateway_ws_fd },
|
||||
},
|
||||
stats_reporter,
|
||||
task_handle: shutdown,
|
||||
})
|
||||
}
|
||||
@@ -858,6 +889,7 @@ pub struct BaseClient {
|
||||
pub client_input: ClientInputStatus,
|
||||
pub client_output: ClientOutputStatus,
|
||||
pub client_state: ClientState,
|
||||
pub stats_reporter: ClientStatsSender,
|
||||
|
||||
pub task_handle: TaskHandle,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::{config, spawn_future};
|
||||
use futures::task::{Context, Poll};
|
||||
@@ -13,6 +12,7 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::cover::generate_loop_cover_packet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -63,7 +63,7 @@ where
|
||||
|
||||
packet_type: PacketType,
|
||||
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
}
|
||||
|
||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||
@@ -109,7 +109,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
topology_access: TopologyAccessor,
|
||||
traffic_config: config::Traffic,
|
||||
cover_config: config::CoverTraffic,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -198,9 +198,9 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.stats_tx.report(PacketStatisticsEvent::CoverPacketSent(
|
||||
cover_traffic_packet_size.size(),
|
||||
));
|
||||
self.stats_tx.report(
|
||||
PacketStatisticsEvent::CoverPacketSent(cover_traffic_packet_size.size()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
|
||||
|
||||
@@ -7,9 +7,9 @@ pub(crate) mod helpers;
|
||||
pub mod inbound_messages;
|
||||
pub mod key_manager;
|
||||
pub mod mix_traffic;
|
||||
pub(crate) mod packet_statistics_control;
|
||||
pub mod real_messages_control;
|
||||
pub mod received_buffer;
|
||||
pub mod replies;
|
||||
pub mod statistics_control;
|
||||
pub mod topology_control;
|
||||
pub(crate) mod transmission_buffer;
|
||||
|
||||
+7
-7
@@ -1,9 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
|
||||
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_gateway_client::AcknowledgementReceiver;
|
||||
@@ -19,7 +19,7 @@ pub(super) struct AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
@@ -27,7 +27,7 @@ impl AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
@@ -40,7 +40,7 @@ impl AcknowledgementListener {
|
||||
async fn on_ack(&mut self, ack_content: Vec<u8>) {
|
||||
trace!("Received an ack");
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::AckReceived(ack_content.len()));
|
||||
.report(PacketStatisticsEvent::AckReceived(ack_content.len()).into());
|
||||
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
|
||||
.map(FragmentIdentifier::try_from_bytes)
|
||||
@@ -57,13 +57,13 @@ impl AcknowledgementListener {
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("Received an ack for a cover message - no need to do anything");
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::CoverAckReceived(ack_content.len()));
|
||||
.report(PacketStatisticsEvent::CoverAckReceived(ack_content.len()).into());
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("Received {} from the mix network", frag_id);
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()));
|
||||
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()).into());
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
.unwrap();
|
||||
|
||||
@@ -8,7 +8,6 @@ use self::{
|
||||
sent_notification_listener::SentNotificationListener,
|
||||
};
|
||||
use crate::client::inbound_messages::InputMessageReceiver;
|
||||
use crate::client::packet_statistics_control::PacketStatisticsReporter;
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use crate::spawn_future;
|
||||
@@ -24,6 +23,7 @@ use nym_sphinx::{
|
||||
chunking::fragment::{Fragment, FragmentIdentifier},
|
||||
Delay as SphinxDelay,
|
||||
};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
@@ -209,7 +209,7 @@ where
|
||||
connectors: AcknowledgementControllerConnectors,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
) -> Self {
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::client::replies::reply_controller;
|
||||
use crate::config;
|
||||
pub(crate) use acknowledgement_control::{AckActionSender, Action};
|
||||
|
||||
use super::packet_statistics_control::PacketStatisticsReporter;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
|
||||
pub(crate) mod acknowledgement_control;
|
||||
pub(crate) mod message_handler;
|
||||
@@ -145,7 +145,7 @@ impl RealMessagesController<OsRng> {
|
||||
reply_controller_receiver: ReplyControllerReceiver,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use self::sending_delay_controller::SendingDelayController;
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
|
||||
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
@@ -19,6 +18,7 @@ use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
@@ -115,8 +115,8 @@ where
|
||||
/// Report queue lengths so that upstream can backoff sending data, and keep connections open.
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
|
||||
/// Channel used for sending statistics events to `PacketStatisticsControl`.
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
/// Channel used for sending metrics events (specifically `PacketStatistics` events) to the metrics tracker.
|
||||
stats_tx: ClientStatsSender,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -175,7 +175,7 @@ where
|
||||
topology_access: TopologyAccessor,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
@@ -277,7 +277,7 @@ where
|
||||
} else {
|
||||
PacketStatisticsEvent::CoverPacketSent(packet_size)
|
||||
};
|
||||
self.stats_tx.report(event);
|
||||
self.stats_tx.report(event.into());
|
||||
}
|
||||
|
||||
// notify ack controller about sending our message only after we actually managed to push it
|
||||
@@ -373,13 +373,13 @@ where
|
||||
TransmissionLane::Retransmission => Some(PacketStatisticsEvent::RetransmissionQueued),
|
||||
};
|
||||
if let Some(stat_event) = stat_event {
|
||||
self.stats_tx.report(stat_event);
|
||||
self.stats_tx.report(stat_event.into());
|
||||
}
|
||||
// To avoid comparing apples to oranges when presenting the fraction of packets that are
|
||||
// retransmissions, we also need to keep track to the total number of real messages queued,
|
||||
// even though we also track the actual number of messages sent later in the pipeline.
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealPacketQueued);
|
||||
.report(PacketStatisticsEvent::RealPacketQueued.into());
|
||||
|
||||
Some(real_next)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::{
|
||||
packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter},
|
||||
replies::{reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys},
|
||||
use crate::client::replies::{
|
||||
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
|
||||
};
|
||||
use crate::spawn_future;
|
||||
use futures::channel::mpsc;
|
||||
@@ -20,6 +19,7 @@ use nym_sphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEnc
|
||||
use nym_sphinx::message::{NymMessage, PlainMessage};
|
||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -46,7 +46,7 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
|
||||
// and every now and then remove ids older than X
|
||||
recently_reconstructed: HashSet<i32>,
|
||||
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
@@ -61,16 +61,12 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
// received and sent packets due to the sphinx layers being removed by the exit gateway
|
||||
// before it reaches the mixnet client.
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::CoverPacketReceived(
|
||||
fragment_data_size,
|
||||
));
|
||||
.report(PacketStatisticsEvent::CoverPacketReceived(fragment_data_size).into());
|
||||
return None;
|
||||
}
|
||||
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealPacketReceived(
|
||||
fragment_data_size,
|
||||
));
|
||||
.report(PacketStatisticsEvent::RealPacketReceived(fragment_data_size).into());
|
||||
|
||||
let fragment = match self.message_receiver.recover_fragment(fragment_data) {
|
||||
Err(err) => {
|
||||
@@ -163,7 +159,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
stats_tx: ClientStatsSender,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
@@ -504,13 +500,13 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_statistics_reporter: PacketStatisticsReporter,
|
||||
metrics_reporter: ClientStatsSender,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new(
|
||||
local_encryption_keypair,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
packet_statistics_reporter,
|
||||
metrics_reporter,
|
||||
);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! # Statistics collection and reporting.
|
||||
//!
|
||||
//! Modular metrics collection and reporting system. submodules can be added to collect different types of metrics.
|
||||
//! On creation the Statistics controller will start a task that will listen for incoming stats events and
|
||||
//! multiplex them out to the appropriate metrics module based on type.
|
||||
//!
|
||||
//! Adding A new module you need to write a new module that implements the `StatsObj` trait and add it to
|
||||
//! the `stats` hashmap in the `StatisticsControl` struct during it's initialization in the `new` function in
|
||||
//! this file.
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use nym_client_core_config_types::StatsReporting;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||
};
|
||||
use nym_task::connections::TransmissionLane;
|
||||
|
||||
use crate::{
|
||||
client::inbound_messages::{InputMessage, InputMessageSender},
|
||||
spawn_future,
|
||||
};
|
||||
|
||||
/// Time interval between reporting statistics locally (logging/task_client)
|
||||
const LOCAL_REPORT_INTERVAL: Duration = Duration::from_secs(2);
|
||||
/// Interval for taking snapshots of the statistics
|
||||
const SNAPSHOT_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
/// Launches and manages metrics collection and reporting.
|
||||
///
|
||||
/// This is designed to be generic to allow for multiple types of metrics to be collected and
|
||||
/// reported.
|
||||
pub(crate) struct StatisticsControl {
|
||||
/// Keep store the different types of metrics collectors
|
||||
stats: ClientStatsController,
|
||||
|
||||
/// Incoming packet stats events from other tasks
|
||||
stats_rx: ClientStatsReceiver,
|
||||
|
||||
/// Channel to send stats report through the mixnet
|
||||
report_tx: InputMessageSender,
|
||||
|
||||
/// Config for stats reporting (enabled, address, interval)
|
||||
reporting_config: StatsReporting,
|
||||
}
|
||||
|
||||
impl StatisticsControl {
|
||||
pub(crate) fn create(
|
||||
reporting_config: StatsReporting,
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
) -> (Self, ClientStatsSender) {
|
||||
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let stats = ClientStatsController::new(client_stats_id, client_type);
|
||||
|
||||
(
|
||||
StatisticsControl {
|
||||
stats,
|
||||
stats_rx,
|
||||
report_tx,
|
||||
reporting_config,
|
||||
},
|
||||
ClientStatsSender::new(Some(stats_tx)),
|
||||
)
|
||||
}
|
||||
|
||||
async fn report_stats(&mut self, recipient: Recipient) {
|
||||
let stats_report = self.stats.build_report();
|
||||
|
||||
let report_message = InputMessage::new_regular(
|
||||
recipient,
|
||||
stats_report.into(),
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
);
|
||||
if let Err(err) = self.report_tx.send(report_message).await {
|
||||
log::error!("Failed to report client stats: {:?}", err);
|
||||
} else {
|
||||
self.stats.reset();
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
|
||||
log::debug!("Started StatisticsControl with graceful shutdown support");
|
||||
|
||||
let mut stats_report_interval =
|
||||
tokio::time::interval(self.reporting_config.reporting_interval);
|
||||
let mut local_report_interval = tokio::time::interval(LOCAL_REPORT_INTERVAL);
|
||||
let mut snapshot_interval = tokio::time::interval(SNAPSHOT_INTERVAL);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
stats_event = self.stats_rx.recv() => match stats_event {
|
||||
Some(stats_event) => self.stats.handle_event(stats_event),
|
||||
None => {
|
||||
log::trace!("StatisticsControl: shutting down due to closed stats channel");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = snapshot_interval.tick() => {
|
||||
self.stats.snapshot();
|
||||
}
|
||||
_ = stats_report_interval.tick(), if self.reporting_config.enabled && self.reporting_config.provider_address.is_some() => {
|
||||
// SAFTEY : this branch executes only if reporting is not none, so unwrapp is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.report_stats(self.reporting_config.provider_address.unwrap()).await;
|
||||
}
|
||||
|
||||
_ = local_report_interval.tick() => {
|
||||
self.stats.local_report(&mut task_client);
|
||||
}
|
||||
_ = task_client.recv_with_delay() => {
|
||||
log::trace!("StatisticsControl: Received shutdown");
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
task_client.recv_timeout().await;
|
||||
log::debug!("StatisticsControl: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start_with_shutdown(mut self, task_client: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
self.run_with_shutdown(task_client).await;
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn create_and_start_with_shutdown(
|
||||
reporting_config: StatsReporting,
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
task_client: nym_task::TaskClient,
|
||||
) -> ClientStatsSender {
|
||||
let (controller, sender) =
|
||||
Self::create(reporting_config, client_type, client_stats_id, report_tx);
|
||||
controller.start_with_shutdown(task_client);
|
||||
sender
|
||||
}
|
||||
}
|
||||
@@ -212,12 +212,29 @@ pub enum ClientCoreError {
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub enum ClientCoreStatusMessage {
|
||||
// NOTE: The nym-connect frontend listens for these strings, so don't change them until we have a more robust mechanism in place
|
||||
#[error("The connected gateway is slow, or the connection to it is slow")]
|
||||
GatewayIsSlow,
|
||||
// NOTE: The nym-connect frontend listens for these strings, so don't change them until we have a more robust mechanism in place
|
||||
#[error("The connected gateway is very slow, or the connection to it is very slow")]
|
||||
GatewayIsVerySlow,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ClientCoreStatusMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ClientCoreStatusMessage::GatewayIsSlow => write!(
|
||||
f,
|
||||
"The connected gateway is slow, or the connection to it is slow"
|
||||
),
|
||||
ClientCoreStatusMessage::GatewayIsVerySlow => write!(
|
||||
f,
|
||||
"The connected gateway is very slow, or the connection to it is very slow"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl nym_task::TaskStatusEvent for ClientCoreStatusMessage {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ nym-crypto = { path = "../../crypto" }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-statistics-common = { path = "../../statistics" }
|
||||
nym-pemstore = { path = "../../pemstore" }
|
||||
nym-validator-client = { path = "../validator-client", default-features = false }
|
||||
nym-task = { path = "../../task" }
|
||||
|
||||
@@ -25,6 +25,8 @@ use nym_gateway_requests::{
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -94,6 +96,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
|
||||
// currently unused (but populated)
|
||||
negotiated_protocol: Option<u8>,
|
||||
@@ -103,6 +106,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
}
|
||||
|
||||
impl<C, St> GatewayClient<C, St> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
cfg: GatewayClientConfig,
|
||||
gateway_config: GatewayConfig,
|
||||
@@ -111,6 +115,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
@@ -124,6 +129,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
negotiated_protocol: None,
|
||||
task_client,
|
||||
}
|
||||
@@ -714,6 +720,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
{
|
||||
// TODO: make it configurable
|
||||
const TICKETS_TO_SPEND: u32 = 1;
|
||||
const MIXNET_TICKET: TicketType = TicketType::V1MixnetEntry;
|
||||
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
@@ -750,14 +757,23 @@ impl<C, St> GatewayClient<C, St> {
|
||||
let prepared_credential = self
|
||||
.unchecked_bandwidth_controller()
|
||||
.prepare_ecash_ticket(
|
||||
TicketType::V1MixnetEntry,
|
||||
MIXNET_TICKET,
|
||||
self.gateway_identity.to_bytes(),
|
||||
TICKETS_TO_SPEND,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match self.claim_ecash_bandwidth(prepared_credential.data).await {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => {
|
||||
self.stats_reporter.report(
|
||||
ConnectionStatsEvent::TicketSpent {
|
||||
typ: MIXNET_TICKET,
|
||||
amount: TICKETS_TO_SPEND,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to claim ecash bandwidth with the gateway...: {err}");
|
||||
if err.is_ticket_replay() {
|
||||
@@ -1030,6 +1046,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
bandwidth_controller: None,
|
||||
stats_reporter: ClientStatsSender::new(None),
|
||||
negotiated_protocol: None,
|
||||
task_client,
|
||||
}
|
||||
@@ -1039,6 +1056,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
self,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> GatewayClient<C, St> {
|
||||
// invariants that can't be broken
|
||||
@@ -1058,6 +1076,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
connection: self.connection,
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
negotiated_protocol: self.negotiated_protocol,
|
||||
task_client,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use crate::nym_api::NymApiClientExt;
|
||||
use crate::nyxd::{self, NyxdClient};
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::{NoSigner, OfflineSigner};
|
||||
@@ -11,7 +12,8 @@ use crate::{
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||
SpentCredentialsResponse, VerifyEcashTicketBody,
|
||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
|
||||
VerifyEcashTicketBody,
|
||||
};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
@@ -24,16 +26,16 @@ use nym_api_requests::models::{
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use time::Date;
|
||||
use url::Url;
|
||||
|
||||
pub use crate::nym_api::NymApiClientExt;
|
||||
use nym_mixnet_contract_common::NymNodeDetails;
|
||||
pub use nym_mixnet_contract_common::{
|
||||
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId,
|
||||
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
|
||||
};
|
||||
|
||||
// re-export the type to not break existing imports
|
||||
pub use crate::coconut::EcashApiClient;
|
||||
|
||||
@@ -696,4 +698,22 @@ impl NymApiClient {
|
||||
) -> Result<VerificationKeyResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.master_verification_key(epoch_id).await?)
|
||||
}
|
||||
|
||||
pub async fn issued_ticketbooks_for(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
) -> Result<IssuedTicketbooksForResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.issued_ticketbooks_for(expiration_date).await?)
|
||||
}
|
||||
|
||||
pub async fn issued_ticketbooks_challenge(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
deposits: Vec<DepositId>,
|
||||
) -> Result<IssuedTicketbooksChallengeResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.issued_ticketbooks_challenge(expiration_date, deposits)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ use async_trait::async_trait;
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||
VerifyEcashTicketBody,
|
||||
IssuedTicketbooksChallengeRequest, IssuedTicketbooksChallengeResponse,
|
||||
IssuedTicketbooksForResponse, VerifyEcashTicketBody,
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
@@ -18,25 +19,22 @@ use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
pub use nym_api_requests::{
|
||||
ecash::{
|
||||
models::{
|
||||
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
|
||||
IssuedTicketbook, IssuedTicketbookBody, SpentCredentialsResponse,
|
||||
},
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody,
|
||||
models::SpentCredentialsResponse, BlindSignRequestBody, BlindedSignatureResponse,
|
||||
PartialCoinIndicesSignatureResponse, PartialExpirationDateSignatureResponse,
|
||||
VerifyEcashCredentialBody,
|
||||
},
|
||||
models::{
|
||||
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
|
||||
LegacyDescribedGateway, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, LegacyDescribedGateway,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SkimmedNode},
|
||||
};
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_contracts_common::IdentityKey;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
pub use nym_http_api_client::Client;
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
@@ -363,8 +361,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -673,11 +671,12 @@ pub trait NymApiClientExt: ApiClient {
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[allow(deprecated)]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_inclusion_probability(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<InclusionProbabilityResponse, NymAPIError> {
|
||||
) -> Result<nym_api_requests::models::InclusionProbabilityResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
@@ -911,62 +910,44 @@ pub trait NymApiClientExt: ApiClient {
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ecash::MASTER_VERIFICATION_KEY,
|
||||
ecash::MASTER_VERIFICATION_KEY,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn epoch_credentials(
|
||||
async fn issued_ticketbooks_for(
|
||||
&self,
|
||||
dkg_epoch: EpochId,
|
||||
) -> Result<EpochCredentialsResponse, NymAPIError> {
|
||||
expiration_date: Date,
|
||||
) -> Result<IssuedTicketbooksForResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_EPOCH_CREDENTIALS,
|
||||
&dkg_epoch.to_string(),
|
||||
routes::ECASH_ISSUED_TICKETBOOKS_FOR,
|
||||
&expiration_date.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn issued_credential(
|
||||
async fn issued_ticketbooks_challenge(
|
||||
&self,
|
||||
credential_id: i64,
|
||||
) -> Result<IssuedCredentialResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_CREDENTIAL,
|
||||
&credential_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn issued_credentials(
|
||||
&self,
|
||||
credential_ids: Vec<i64>,
|
||||
) -> Result<IssuedCredentialsResponse, NymAPIError> {
|
||||
expiration_date: Date,
|
||||
deposits: Vec<DepositId>,
|
||||
) -> Result<IssuedTicketbooksChallengeResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_CREDENTIALS,
|
||||
routes::ECASH_ISSUED_TICKETBOOKS_CHALLENGE,
|
||||
],
|
||||
NO_PARAMS,
|
||||
&CredentialsRequestBody {
|
||||
credential_ids,
|
||||
pagination: None,
|
||||
&IssuedTicketbooksChallengeRequest {
|
||||
expiration_date,
|
||||
deposits,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -27,9 +27,8 @@ pub mod ecash {
|
||||
pub const PARTIAL_COIN_INDICES_SIGNATURES: &str = "partial-coin-indices-signatures";
|
||||
pub const GLOBAL_COIN_INDICES_SIGNATURES: &str = "aggregated-coin-indices-signatures";
|
||||
pub const MASTER_VERIFICATION_KEY: &str = "master-verification-key";
|
||||
pub const ECASH_EPOCH_CREDENTIALS: &str = "epoch-credentials";
|
||||
pub const ECASH_ISSUED_CREDENTIAL: &str = "issued-credential";
|
||||
pub const ECASH_ISSUED_CREDENTIALS: &str = "issued-credentials";
|
||||
pub const ECASH_ISSUED_TICKETBOOKS_FOR: &str = "issued-ticketbooks-for";
|
||||
pub const ECASH_ISSUED_TICKETBOOKS_CHALLENGE: &str = "issued-ticketbooks-challenge";
|
||||
|
||||
pub const EXPIRATION_DATE_PARAM: &str = "expiration_date";
|
||||
pub const EPOCH_ID_PARAM: &str = "epoch_id";
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmwasm_std::Coin;
|
||||
use nym_ecash_contract_common::deposit::LatestDepositResponse;
|
||||
use nym_ecash_contract_common::msg::QueryMsg as EcashQueryMsg;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -51,6 +52,11 @@ pub trait EcashQueryClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_latest_deposit(&self) -> Result<LatestDepositResponse, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetLatestDeposit {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_deposits_paged(
|
||||
&self,
|
||||
start_after: Option<u32>,
|
||||
@@ -98,7 +104,6 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
use nym_ecash_contract_common::msg::QueryMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
@@ -110,14 +115,17 @@ mod tests {
|
||||
EcashQueryMsg::GetBlacklistedAccount { public_key } => {
|
||||
client.get_blacklisted_account(public_key).ignore()
|
||||
}
|
||||
QueryMsg::GetBlacklistPaged { limit, start_after } => {
|
||||
EcashQueryMsg::GetBlacklistPaged { limit, start_after } => {
|
||||
client.get_blacklist_paged(start_after, limit).ignore()
|
||||
}
|
||||
QueryMsg::GetDeposit { deposit_id } => client.get_deposit(deposit_id).ignore(),
|
||||
QueryMsg::GetDepositsPaged { limit, start_after } => {
|
||||
EcashQueryMsg::GetDeposit { deposit_id } => client.get_deposit(deposit_id).ignore(),
|
||||
EcashQueryMsg::GetDepositsPaged { limit, start_after } => {
|
||||
client.get_deposits_paged(start_after, limit).ignore()
|
||||
}
|
||||
QueryMsg::GetRequiredDepositAmount {} => client.get_required_deposit_amount().ignore(),
|
||||
EcashQueryMsg::GetRequiredDepositAmount {} => {
|
||||
client.get_required_deposit_amount().ignore()
|
||||
}
|
||||
EcashQueryMsg::GetLatestDeposit {} => client.get_latest_deposit().ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,11 +66,6 @@ pub trait MixnetQueryClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_mixnet_contract_settings(&self) -> Result<ContractStateParams, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_mixnet_contract_state_params(&self) -> Result<ContractStateParams, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
|
||||
.await
|
||||
|
||||
+23
-5
@@ -17,7 +17,7 @@ use nym_mixnet_contract_common::reward_params::{
|
||||
ActiveSetUpdate, IntervalRewardingParamsUpdate, NodeRewardingParameters,
|
||||
};
|
||||
use nym_mixnet_contract_common::{
|
||||
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId, NymNode,
|
||||
ContractStateParamsUpdate, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId, NymNode,
|
||||
RoleAssignment,
|
||||
};
|
||||
|
||||
@@ -59,12 +59,27 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn update_contract_state_params(
|
||||
&self,
|
||||
updated_parameters: ContractStateParams,
|
||||
update: ContractStateParamsUpdate,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters },
|
||||
MixnetExecuteMsg::UpdateContractStateParams { update },
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_current_nym_node_semver(
|
||||
&self,
|
||||
current_nym_node_semver: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::UpdateCurrentNymNodeSemver {
|
||||
current_version: current_nym_node_semver,
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
@@ -682,8 +697,11 @@ mod tests {
|
||||
MixnetExecuteMsg::UpdateRewardingValidatorAddress { address } => client
|
||||
.update_rewarding_validator_address(address.parse().unwrap(), None)
|
||||
.ignore(),
|
||||
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters } => client
|
||||
.update_contract_state_params(updated_parameters, None)
|
||||
MixnetExecuteMsg::UpdateContractStateParams { update } => {
|
||||
client.update_contract_state_params(update, None).ignore()
|
||||
}
|
||||
MixnetExecuteMsg::UpdateCurrentNymNodeSemver { current_version } => client
|
||||
.update_current_nym_node_semver(current_version, None)
|
||||
.ignore(),
|
||||
MixnetExecuteMsg::UpdateActiveSetDistribution {
|
||||
update,
|
||||
|
||||
@@ -303,7 +303,7 @@ where
|
||||
feature = "tendermint-rpc-http-client",
|
||||
feature = "tendermint-rpc-websocket-client"
|
||||
))]
|
||||
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
|
||||
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), TendermintRpcError>
|
||||
where
|
||||
T: Into<core::time::Duration> + Send,
|
||||
{
|
||||
|
||||
@@ -823,7 +823,7 @@ where
|
||||
feature = "tendermint-rpc-http-client",
|
||||
feature = "tendermint-rpc-websocket-client"
|
||||
))]
|
||||
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
|
||||
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), TendermintRpcError>
|
||||
where
|
||||
T: Into<core::time::Duration> + Send,
|
||||
{
|
||||
|
||||
@@ -523,7 +523,7 @@ mod non_wasm {
|
||||
))]
|
||||
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
|
||||
where
|
||||
T: Into<Duration> + Send,
|
||||
T: Into<core::time::Duration> + Send,
|
||||
{
|
||||
self.wait_until_healthy(timeout).await
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::utils::CommonConfigsWrapper;
|
||||
use anyhow::{anyhow, bail};
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credential_utils::utils;
|
||||
@@ -150,6 +151,7 @@ async fn issue_to_file(args: Args, client: SigningClient) -> anyhow::Result<()>
|
||||
exported = exported.with_master_verification_key(&EpochVerificationKey { epoch_id, key });
|
||||
}
|
||||
|
||||
info!("the issued ticketbook has expiration of {expiration_date}");
|
||||
let data = exported.pack().data;
|
||||
|
||||
if args.bs58_output {
|
||||
|
||||
@@ -33,6 +33,9 @@ pub struct Args {
|
||||
#[clap(long)]
|
||||
pub rewarding_denom: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub current_nym_node_version: String,
|
||||
|
||||
#[clap(long, default_value_t = 720)]
|
||||
pub epochs_in_interval: u32,
|
||||
|
||||
@@ -143,6 +146,9 @@ pub async fn generate(args: Args) {
|
||||
epochs_in_interval: args.epochs_in_interval,
|
||||
epoch_duration: Duration::from_secs(args.epoch_duration),
|
||||
initial_rewarding_params,
|
||||
current_nym_node_version: args.current_nym_node_version,
|
||||
version_score_weights: Default::default(),
|
||||
version_score_params: Default::default(),
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: args.minimum_profit_margin_percent,
|
||||
maximum: args.maximum_profit_margin_percent,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
use std::fmt::Display;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
@@ -20,6 +20,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ser_maybe_stringified<S, T>(field: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
T: Display,
|
||||
{
|
||||
match field {
|
||||
Some(inner) => serializer.serialize_str(&inner.to_string()),
|
||||
None => serializer.serialize_str(""),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_maybe_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
|
||||
@@ -21,3 +21,6 @@ serde_json = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
|
||||
|
||||
[features]
|
||||
naive_float = []
|
||||
@@ -130,7 +130,7 @@ impl Deref for Percent {
|
||||
}
|
||||
|
||||
// this is not implemented via From traits due to its naive nature and loss of precision
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(feature = "naive_float")]
|
||||
pub trait NaiveFloat {
|
||||
fn naive_to_f64(&self) -> f64;
|
||||
|
||||
@@ -139,8 +139,8 @@ pub trait NaiveFloat {
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl NaiveFloat for Percent {
|
||||
#[cfg(feature = "naive_float")]
|
||||
impl NaiveFloat for Decimal {
|
||||
fn naive_to_f64(&self) -> f64 {
|
||||
use cosmwasm_std::Fraction;
|
||||
|
||||
@@ -181,7 +181,21 @@ impl NaiveFloat for Percent {
|
||||
}
|
||||
|
||||
let (n, d) = to_rational(val);
|
||||
Percent::new(Decimal::from_ratio(n, d))
|
||||
Ok(Decimal::from_ratio(n, d))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_float")]
|
||||
impl NaiveFloat for Percent {
|
||||
fn naive_to_f64(&self) -> f64 {
|
||||
self.0.naive_to_f64()
|
||||
}
|
||||
|
||||
fn naive_try_from_f64(val: f64) -> Result<Self, ContractsCommonError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Percent::new(Decimal::naive_try_from_f64(val)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,12 @@ impl Deposit {
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Default)]
|
||||
pub struct LatestDepositResponse {
|
||||
pub deposit: Option<DepositData>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DepositResponse {
|
||||
pub id: DepositId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
@@ -7,7 +7,7 @@ use cosmwasm_std::Coin;
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::blacklist::{BlacklistedAccountResponse, PagedBlacklistedAccountResponse};
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::deposit::{DepositResponse, PagedDepositsResponse};
|
||||
use crate::deposit::{DepositResponse, LatestDepositResponse, PagedDepositsResponse};
|
||||
#[cfg(feature = "schema")]
|
||||
use cosmwasm_schema::QueryResponses;
|
||||
|
||||
@@ -73,6 +73,9 @@ pub enum QueryMsg {
|
||||
#[cfg_attr(feature = "schema", returns(DepositResponse))]
|
||||
GetDeposit { deposit_id: u32 },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(LatestDepositResponse))]
|
||||
GetLatestDeposit {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(PagedDepositsResponse))]
|
||||
GetDepositsPaged {
|
||||
limit: Option<u32>,
|
||||
|
||||
@@ -17,6 +17,7 @@ cw-controllers = { workspace = true }
|
||||
cw2 = { workspace = true, optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_repr = { workspace = true }
|
||||
semver = { workspace = true, features = ["serde"] }
|
||||
|
||||
# we still have to preserve that import for `JsonSchema` for `Layer` type (since we can't use cw_serde macro due to custom serde impl)
|
||||
schemars = { workspace = true }
|
||||
@@ -26,8 +27,6 @@ serde-json-wasm = { workspace = true }
|
||||
humantime-serde = { workspace = true }
|
||||
utoipa = { workspace = true, optional = true }
|
||||
|
||||
# TO CHECK WHETHER STILL NEEDED:
|
||||
log = { workspace = true }
|
||||
time = { workspace = true, features = ["parsing", "formatting"] }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
@@ -38,6 +37,6 @@ time = { workspace = true, features = ["serde", "macros"] }
|
||||
[features]
|
||||
default = []
|
||||
contract-testing = []
|
||||
utoipa = [ "dep:utoipa" ]
|
||||
utoipa = ["dep:utoipa"]
|
||||
schema = ["cw2"]
|
||||
generate-ts = ['ts-rs']
|
||||
|
||||
@@ -166,6 +166,9 @@ pub enum MixnetContractError {
|
||||
#[error("Provided message to update rewarding params did not contain any updates")]
|
||||
EmptyParamsChangeMsg,
|
||||
|
||||
#[error("provided message to update state parameters did not contain any updates")]
|
||||
EmptyStateUpdateMsg,
|
||||
|
||||
#[error("one of the roles in the new active set is empty")]
|
||||
EmptyRoleAssignment,
|
||||
|
||||
@@ -269,6 +272,9 @@ pub enum MixnetContractError {
|
||||
|
||||
#[error("the total work for this epoch seems to be bigger than 1.0!")]
|
||||
TotalWorkAboveOne,
|
||||
|
||||
#[error("the provided nym-node version is not a valid semver. got: {provided}")]
|
||||
InvalidNymNodeSemver { provided: String },
|
||||
}
|
||||
|
||||
impl MixnetContractError {
|
||||
|
||||
@@ -7,9 +7,9 @@ use crate::mixnode::{MixNodeConfigUpdate, NodeCostParams};
|
||||
use crate::nym_node::Role;
|
||||
use crate::reward_params::{ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate};
|
||||
use crate::rewarding::RewardDistribution;
|
||||
use crate::{BlockHeight, ContractStateParams, EpochId, IdentityKeyRef, Interval, NodeId};
|
||||
use crate::{BlockHeight, ContractStateParamsUpdate, EpochId, IdentityKeyRef, Interval, NodeId};
|
||||
pub use contracts_common::events::*;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, Event};
|
||||
use cosmwasm_std::{attr, Addr, Coin, Decimal, Event};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub const EVENT_VERSION_PREFIX: &str = "v2_";
|
||||
@@ -45,6 +45,7 @@ pub enum MixnetEventType {
|
||||
DelegationOnUnbonding,
|
||||
Undelegation,
|
||||
ContractSettingsUpdate,
|
||||
NymNodeSemverUpdate,
|
||||
RewardingValidatorUpdate,
|
||||
BeginEpochTransition,
|
||||
AdvanceEpoch,
|
||||
@@ -97,6 +98,7 @@ impl Display for MixnetEventType {
|
||||
MixnetEventType::Delegation => "delegation",
|
||||
MixnetEventType::Undelegation => "undelegation",
|
||||
MixnetEventType::ContractSettingsUpdate => "settings_update",
|
||||
MixnetEventType::NymNodeSemverUpdate => "nym_node_semver_update",
|
||||
MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update",
|
||||
MixnetEventType::BeginEpochTransition => "beginning_epoch_transition",
|
||||
MixnetEventType::AdvanceEpoch => "advance_epoch",
|
||||
@@ -132,11 +134,13 @@ pub const NODE_ID_KEY: &str = "node_id";
|
||||
pub const NODE_IDENTITY_KEY: &str = "identity";
|
||||
|
||||
// settings change
|
||||
pub const OLD_MINIMUM_PLEDGE_KEY: &str = "old_minimum_pledge";
|
||||
pub const OLD_MINIMUM_DELEGATION_KEY: &str = "old_minimum_delegation";
|
||||
|
||||
pub const NEW_MINIMUM_PLEDGE_KEY: &str = "new_minimum_pledge";
|
||||
pub const NEW_MINIMUM_DELEGATION_KEY: &str = "new_minimum_delegation";
|
||||
pub const NEW_PROFIT_MARGIN_RANGE_KEY: &str = "new_profit_margin_range";
|
||||
pub const NEW_INTERVAL_OPERATING_COST_RANGE_KEY: &str = "new_interval_operating_cost_range";
|
||||
pub const NEW_VERSION_WEIGHTS_RANGE_KEY: &str = "new_version_weights_range";
|
||||
pub const NEW_VERSION_SCORE_FORMULA_PARAMS_KEY: &str = "new_version_score_formula_params";
|
||||
pub const NYM_NODE_CURRENT_SEMVER_KEY: &str = "new_current_semver";
|
||||
|
||||
pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_address";
|
||||
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
|
||||
@@ -440,40 +444,73 @@ pub fn new_rewarding_validator_address_update_event(old: Addr, new: Addr) -> Eve
|
||||
.add_attribute(NEW_REWARDING_VALIDATOR_ADDRESS_KEY, new)
|
||||
}
|
||||
|
||||
pub fn new_settings_update_event(
|
||||
old_params: &ContractStateParams,
|
||||
new_params: &ContractStateParams,
|
||||
) -> Event {
|
||||
pub fn new_settings_update_event(update: &ContractStateParamsUpdate) -> Event {
|
||||
let mut event = Event::new(MixnetEventType::ContractSettingsUpdate);
|
||||
|
||||
if old_params.minimum_pledge != new_params.minimum_pledge {
|
||||
event = event
|
||||
.add_attribute(
|
||||
OLD_MINIMUM_PLEDGE_KEY,
|
||||
old_params.minimum_pledge.to_string(),
|
||||
)
|
||||
.add_attribute(
|
||||
NEW_MINIMUM_PLEDGE_KEY,
|
||||
new_params.minimum_pledge.to_string(),
|
||||
)
|
||||
// check for delegations params updates
|
||||
if let Some(delegations_update) = &update.delegations_params {
|
||||
event.attributes.push(attr(
|
||||
NEW_MINIMUM_DELEGATION_KEY,
|
||||
delegations_update
|
||||
.minimum_delegation
|
||||
.as_ref()
|
||||
.map(|d| d.to_string())
|
||||
.unwrap_or("empty".to_string()),
|
||||
));
|
||||
}
|
||||
|
||||
if old_params.minimum_delegation != new_params.minimum_delegation {
|
||||
if let Some(ref old) = old_params.minimum_delegation {
|
||||
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, old.to_string())
|
||||
} else {
|
||||
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, "None")
|
||||
// check for operators params updates
|
||||
if let Some(operators_update) = &update.operators_params {
|
||||
if let Some(minimum_pledge) = &operators_update.minimum_pledge {
|
||||
event
|
||||
.attributes
|
||||
.push(attr(NEW_MINIMUM_PLEDGE_KEY, minimum_pledge.to_string()))
|
||||
}
|
||||
if let Some(ref new) = new_params.minimum_delegation {
|
||||
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, new.to_string())
|
||||
} else {
|
||||
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, "None")
|
||||
if let Some(profit_margin) = &operators_update.profit_margin {
|
||||
event
|
||||
.attributes
|
||||
.push(attr(NEW_PROFIT_MARGIN_RANGE_KEY, profit_margin.to_string()))
|
||||
}
|
||||
if let Some(interval_operating_cost) = &operators_update.interval_operating_cost {
|
||||
event.attributes.push(attr(
|
||||
NEW_INTERVAL_OPERATING_COST_RANGE_KEY,
|
||||
interval_operating_cost.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// check for config score params updates
|
||||
if let Some(config_score_update) = &update.config_score_params {
|
||||
if let Some(current_nym_node_semver) = &config_score_update.current_nym_node_semver {
|
||||
event.attributes.push(attr(
|
||||
NYM_NODE_CURRENT_SEMVER_KEY,
|
||||
current_nym_node_semver.to_string(),
|
||||
))
|
||||
}
|
||||
if let Some(version_weights) = &config_score_update.version_weights {
|
||||
event.attributes.push(attr(
|
||||
NEW_VERSION_WEIGHTS_RANGE_KEY,
|
||||
format!("{version_weights:?}"),
|
||||
))
|
||||
}
|
||||
if let Some(version_score_formula_params) =
|
||||
&config_score_update.version_score_formula_params
|
||||
{
|
||||
event.attributes.push(attr(
|
||||
NEW_VERSION_SCORE_FORMULA_PARAMS_KEY,
|
||||
format!("{version_score_formula_params:?}"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
event
|
||||
}
|
||||
|
||||
pub fn new_update_nym_node_semver_event(new_version: &str) -> Event {
|
||||
Event::new(MixnetEventType::NymNodeSemverUpdate)
|
||||
.add_attribute(NYM_NODE_CURRENT_SEMVER_KEY, new_version)
|
||||
}
|
||||
|
||||
pub fn new_not_found_node_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
|
||||
Event::new(MixnetEventType::NodeRewarding)
|
||||
.add_attribute(
|
||||
|
||||
@@ -12,8 +12,11 @@ use crate::reward_params::{
|
||||
ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate, NodeRewardingParameters,
|
||||
Performance, RewardedSetParams, RewardingParams, WorkFactor,
|
||||
};
|
||||
use crate::types::{ContractStateParams, NodeId};
|
||||
use crate::{NymNode, RoleAssignment};
|
||||
use crate::types::NodeId;
|
||||
use crate::{
|
||||
ContractStateParamsUpdate, NymNode, OutdatedVersionWeights, RoleAssignment,
|
||||
VersionScoreFormulaParams,
|
||||
};
|
||||
use crate::{OperatingCostRange, ProfitMarginRange};
|
||||
use contracts_common::{signing::MessageSignature, IdentityKey, Percent};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
@@ -47,7 +50,7 @@ use crate::{
|
||||
PendingIntervalEventResponse, PendingIntervalEventsResponse,
|
||||
},
|
||||
rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse},
|
||||
types::ContractState,
|
||||
types::{ContractState, ContractStateParams},
|
||||
};
|
||||
#[cfg(feature = "schema")]
|
||||
use contracts_common::{signing::Nonce, ContractBuildInformation};
|
||||
@@ -64,6 +67,14 @@ pub struct InstantiateMsg {
|
||||
pub epoch_duration: Duration,
|
||||
pub initial_rewarding_params: InitialRewardingParams,
|
||||
|
||||
pub current_nym_node_version: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub version_score_weights: OutdatedVersionWeights,
|
||||
|
||||
#[serde(default)]
|
||||
pub version_score_params: VersionScoreFormulaParams,
|
||||
|
||||
#[serde(default)]
|
||||
pub profit_margin: ProfitMarginRange,
|
||||
|
||||
@@ -126,7 +137,10 @@ pub enum ExecuteMsg {
|
||||
address: String,
|
||||
},
|
||||
UpdateContractStateParams {
|
||||
updated_parameters: ContractStateParams,
|
||||
update: ContractStateParamsUpdate,
|
||||
},
|
||||
UpdateCurrentNymNodeSemver {
|
||||
current_version: String,
|
||||
},
|
||||
UpdateActiveSetDistribution {
|
||||
update: ActiveSetUpdate,
|
||||
@@ -295,6 +309,9 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::UpdateContractStateParams { .. } => {
|
||||
"updating mixnet state parameters".into()
|
||||
}
|
||||
ExecuteMsg::UpdateCurrentNymNodeSemver { current_version } => {
|
||||
format!("updating current nym-node semver to {current_version}")
|
||||
}
|
||||
ExecuteMsg::UpdateActiveSetDistribution {
|
||||
force_immediately, ..
|
||||
} => format!("updating active set distribution. forced: {force_immediately}"),
|
||||
@@ -829,6 +846,13 @@ pub enum QueryMsg {
|
||||
|
||||
#[cw_serde]
|
||||
pub struct MigrateMsg {
|
||||
pub vesting_contract_address: Option<String>,
|
||||
pub unsafe_skip_state_updates: Option<bool>,
|
||||
pub vesting_contract_address: Option<String>,
|
||||
pub current_nym_node_semver: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub version_score_weights: OutdatedVersionWeights,
|
||||
|
||||
#[serde(default)]
|
||||
pub version_score_params: VersionScoreFormulaParams,
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
use crate::nym_node::Role;
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
// type aliases for better reasoning about available data
|
||||
@@ -161,19 +161,156 @@ pub struct ContractState {
|
||||
/// Contract parameters that could be adjusted in a transaction by the contract admin.
|
||||
#[cw_serde]
|
||||
pub struct ContractStateParams {
|
||||
/// Parameters to do with delegations.
|
||||
pub delegations_params: DelegationsParams,
|
||||
|
||||
/// Parameters to do with node operators.
|
||||
pub operators_params: OperatorsParams,
|
||||
|
||||
/// Parameters to do with the config score
|
||||
pub config_score_params: ConfigScoreParams,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ContractStateParamsUpdate {
|
||||
pub delegations_params: Option<DelegationsParams>,
|
||||
pub operators_params: Option<OperatorsParamsUpdate>,
|
||||
pub config_score_params: Option<ConfigScoreParamsUpdate>,
|
||||
}
|
||||
|
||||
impl ContractStateParamsUpdate {
|
||||
pub fn contains_updates(&self) -> bool {
|
||||
self.delegations_params.is_some()
|
||||
|| self.operators_params.is_some()
|
||||
|| self.config_score_params.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DelegationsParams {
|
||||
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
|
||||
pub minimum_delegation: Option<Coin>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct OperatorsParams {
|
||||
/// Minimum amount a node must pledge to get into the system.
|
||||
pub minimum_pledge: Coin,
|
||||
|
||||
/// Defines the allowed profit margin range of operators.
|
||||
/// default: 0% - 100%
|
||||
#[serde(default)]
|
||||
pub profit_margin: ProfitMarginRange,
|
||||
|
||||
/// Defines the allowed interval operating cost range of operators.
|
||||
/// default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)
|
||||
#[serde(default)]
|
||||
pub interval_operating_cost: OperatingCostRange,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct OperatorsParamsUpdate {
|
||||
pub minimum_pledge: Option<Coin>,
|
||||
pub profit_margin: Option<ProfitMarginRange>,
|
||||
pub interval_operating_cost: Option<OperatingCostRange>,
|
||||
}
|
||||
|
||||
impl OperatorsParamsUpdate {
|
||||
pub fn contains_updates(&self) -> bool {
|
||||
self.minimum_pledge.is_some()
|
||||
|| self.profit_margin.is_some()
|
||||
|| self.interval_operating_cost.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ConfigScoreParams {
|
||||
/// Current version of the nym node that is going to be used for determining the version score of a node.
|
||||
/// note: value stored here is pre-validated `semver::Version`
|
||||
pub current_nym_node_semver: String,
|
||||
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
pub version_weights: OutdatedVersionWeights,
|
||||
|
||||
/// Defines the parameters of the formula for calculating the version score
|
||||
pub version_score_formula_params: VersionScoreFormulaParams,
|
||||
}
|
||||
|
||||
impl ConfigScoreParams {
|
||||
// SAFETY: the value stored in the contract is always valid
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn unchecked_nym_node_version(&self) -> semver::Version {
|
||||
self.current_nym_node_semver.parse().unwrap()
|
||||
}
|
||||
|
||||
pub fn versions_behind(&self, node_semver: &semver::Version) -> u32 {
|
||||
let expected = self.unchecked_nym_node_version();
|
||||
|
||||
let major_diff = (node_semver.major as i64 - expected.major as i64).unsigned_abs() as u32;
|
||||
let minor_diff = (node_semver.minor as i64 - expected.minor as i64).unsigned_abs() as u32;
|
||||
let patch_diff = (node_semver.patch as i64 - expected.patch as i64).unsigned_abs() as u32;
|
||||
let prerelease_diff = if node_semver.pre == expected.pre {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
major_diff * self.version_weights.major
|
||||
+ minor_diff * self.version_weights.minor
|
||||
+ patch_diff * self.version_weights.patch
|
||||
+ prerelease_diff * self.version_weights.prerelease
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct OutdatedVersionWeights {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
pub prerelease: u32,
|
||||
}
|
||||
|
||||
impl Default for OutdatedVersionWeights {
|
||||
fn default() -> Self {
|
||||
OutdatedVersionWeights {
|
||||
major: 100,
|
||||
minor: 10,
|
||||
patch: 1,
|
||||
prerelease: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling)
|
||||
/// define the relevant parameters
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct VersionScoreFormulaParams {
|
||||
pub penalty: Decimal,
|
||||
pub penalty_scaling: Decimal,
|
||||
}
|
||||
|
||||
impl Default for VersionScoreFormulaParams {
|
||||
fn default() -> Self {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
VersionScoreFormulaParams {
|
||||
penalty: "0.8".parse().unwrap(),
|
||||
penalty_scaling: "2.0".parse().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ConfigScoreParamsUpdate {
|
||||
pub current_nym_node_semver: Option<String>,
|
||||
pub version_weights: Option<OutdatedVersionWeights>,
|
||||
pub version_score_formula_params: Option<VersionScoreFormulaParams>,
|
||||
}
|
||||
|
||||
impl ConfigScoreParamsUpdate {
|
||||
pub fn contains_updates(&self) -> bool {
|
||||
self.current_nym_node_semver.is_some()
|
||||
|| self.version_weights.is_some()
|
||||
|| self.version_score_formula_params.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ pub mod error;
|
||||
mod helpers;
|
||||
mod state;
|
||||
|
||||
const TIME_RANGE_SEC: i64 = 30;
|
||||
pub const TIME_RANGE_SEC: i64 = 30;
|
||||
|
||||
pub struct EcashManager<S> {
|
||||
shared_state: SharedState<S>,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "nym-gateway-stats-storage"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
"time",
|
||||
] }
|
||||
time = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let database_path = format!("{}/gateway-stats-example.sqlite", out_dir);
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
CREATE TABLE sessions_active
|
||||
(
|
||||
client_address TEXT NOT NULL PRIMARY KEY UNIQUE,
|
||||
start_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
typ TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sessions_finished
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
day DATE NOT NULL,
|
||||
duration_ms INTEGER NOT NULL,
|
||||
typ TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sessions_unique_users
|
||||
(
|
||||
day DATE NOT NULL,
|
||||
client_address TEXT NOT NULL,
|
||||
PRIMARY KEY (day, client_address)
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StatsStorageError {
|
||||
#[error("Database experienced an internal error: {0}")]
|
||||
InternalDatabaseError(#[from] sqlx::Error),
|
||||
|
||||
#[error("Failed to perform database migration: {0}")]
|
||||
MigrationError(#[from] sqlx::migrate::MigrateError),
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use error::StatsStorageError;
|
||||
use models::{ActiveSession, FinishedSession, SessionType, StoredFinishedSession};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use sessions::SessionManager;
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
use time::Date;
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub mod error;
|
||||
pub mod models;
|
||||
mod sessions;
|
||||
|
||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||
#[derive(Clone)]
|
||||
pub struct PersistentStatsStorage {
|
||||
session_manager: SessionManager,
|
||||
}
|
||||
|
||||
impl PersistentStatsStorage {
|
||||
/// Initialises `PersistentStatsStorage` using the provided path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `database_path`: path to the database.
|
||||
pub async fn init<P: AsRef<Path> + Send>(database_path: P) -> Result<Self, StatsStorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
);
|
||||
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
// TODO: do we want auto_vacuum ?
|
||||
|
||||
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
|
||||
Ok(db) => db,
|
||||
Err(err) => {
|
||||
error!("Failed to connect to SQLx database: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
|
||||
error!("Failed to perform migration on the SQLx database: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
// the cloning here are cheap as connection pool is stored behind an Arc
|
||||
Ok(PersistentStatsStorage {
|
||||
session_manager: sessions::SessionManager::new(connection_pool),
|
||||
})
|
||||
}
|
||||
|
||||
//Sessions fn
|
||||
pub async fn insert_finished_session(
|
||||
&self,
|
||||
date: Date,
|
||||
session: FinishedSession,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.insert_finished_session(
|
||||
date,
|
||||
session.duration.whole_milliseconds() as i64,
|
||||
session.typ.to_string().into(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_finished_sessions(
|
||||
&self,
|
||||
date: Date,
|
||||
) -> Result<Vec<StoredFinishedSession>, StatsStorageError> {
|
||||
Ok(self.session_manager.get_finished_sessions(date).await?)
|
||||
}
|
||||
|
||||
pub async fn delete_finished_sessions(
|
||||
&self,
|
||||
before_date: Date,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_finished_sessions(before_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn insert_unique_user(
|
||||
&self,
|
||||
date: Date,
|
||||
client_address_bs58: String,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.insert_unique_user(date, client_address_bs58)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_unique_users(&self, date: Date) -> Result<Vec<String>, StatsStorageError> {
|
||||
Ok(self.session_manager.get_unique_users(date).await?)
|
||||
}
|
||||
|
||||
pub async fn delete_unique_users(&self, before_date: Date) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_unique_users(before_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn insert_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
session: ActiveSession,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.insert_active_session(
|
||||
client_address.as_base58_string(),
|
||||
session.start,
|
||||
session.typ.to_string().into(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn update_active_session_type(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
session_type: SessionType,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.update_active_session_type(
|
||||
client_address.as_base58_string(),
|
||||
session_type.to_string().into(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<ActiveSession>, StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.get_active_session(client_address.as_base58_string())
|
||||
.await?
|
||||
.map(Into::into))
|
||||
}
|
||||
|
||||
pub async fn get_all_active_sessions(&self) -> Result<Vec<ActiveSession>, StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.get_all_active_sessions()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn get_started_sessions_count(
|
||||
&self,
|
||||
start_date: Date,
|
||||
) -> Result<i32, StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.get_started_sessions_count(start_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_active_users(&self) -> Result<Vec<String>, StatsStorageError> {
|
||||
Ok(self.session_manager.get_active_users().await?)
|
||||
}
|
||||
|
||||
pub async fn delete_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_active_session(client_address.as_base58_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn cleanup_active_sessions(&self) -> Result<(), StatsStorageError> {
|
||||
Ok(self.session_manager.cleanup_active_sessions().await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_credentials_interface::TicketType;
|
||||
use sqlx::prelude::FromRow;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct StoredFinishedSession {
|
||||
duration_ms: i64,
|
||||
typ: String,
|
||||
}
|
||||
|
||||
impl StoredFinishedSession {
|
||||
pub fn serialize(&self) -> (u64, String) {
|
||||
(
|
||||
self.duration_ms as u64, //we are sure that it fits in a u64, see `fn end_at`
|
||||
self.typ.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FinishedSession {
|
||||
pub duration: Duration,
|
||||
pub typ: SessionType,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum SessionType {
|
||||
Vpn,
|
||||
Mixnet,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl SessionType {
|
||||
pub fn to_string(&self) -> &str {
|
||||
match self {
|
||||
Self::Vpn => "vpn",
|
||||
Self::Mixnet => "mixnet",
|
||||
Self::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(s: &str) -> Self {
|
||||
match s {
|
||||
"vpn" => Self::Vpn,
|
||||
"mixnet" => Self::Mixnet,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TicketType> for SessionType {
|
||||
fn from(value: TicketType) -> Self {
|
||||
match value {
|
||||
TicketType::V1MixnetEntry => Self::Mixnet,
|
||||
TicketType::V1MixnetExit => Self::Mixnet,
|
||||
TicketType::V1WireguardEntry => Self::Vpn,
|
||||
TicketType::V1WireguardExit => Self::Vpn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub(crate) struct StoredActiveSession {
|
||||
start_time: OffsetDateTime,
|
||||
typ: String,
|
||||
}
|
||||
|
||||
pub struct ActiveSession {
|
||||
pub start: OffsetDateTime,
|
||||
pub typ: SessionType,
|
||||
}
|
||||
|
||||
impl ActiveSession {
|
||||
pub fn new(start_time: OffsetDateTime) -> Self {
|
||||
ActiveSession {
|
||||
start: start_time,
|
||||
typ: SessionType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_type(&mut self, ticket_type: TicketType) {
|
||||
self.typ = ticket_type.into();
|
||||
}
|
||||
|
||||
pub fn end_at(self, stop_time: OffsetDateTime) -> Option<FinishedSession> {
|
||||
let session_duration = stop_time - self.start;
|
||||
//ensure duration is positive to fit in a u64
|
||||
//u64::max milliseconds is 500k millenia so no overflow issue
|
||||
if session_duration > Duration::ZERO {
|
||||
Some(FinishedSession {
|
||||
duration: session_duration,
|
||||
typ: self.typ,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredActiveSession> for ActiveSession {
|
||||
fn from(value: StoredActiveSession) -> Self {
|
||||
ActiveSession {
|
||||
start: value.start_time,
|
||||
typ: SessionType::from_string(&value.typ),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
use crate::models::{StoredActiveSession, StoredFinishedSession};
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, sqlx::Error>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SessionManager {
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
impl SessionManager {
|
||||
/// Creates new instance of the `SessionsManager` with the provided sqlite connection pool.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `connection_pool`: database connection pool to use.
|
||||
pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self {
|
||||
SessionManager { connection_pool }
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_finished_session(
|
||||
&self,
|
||||
date: Date,
|
||||
duration_ms: i64,
|
||||
typ: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO sessions_finished (day, duration_ms, typ) VALUES (?, ?, ?)",
|
||||
date,
|
||||
duration_ms,
|
||||
typ
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_finished_sessions(
|
||||
&self,
|
||||
date: Date,
|
||||
) -> Result<Vec<StoredFinishedSession>> {
|
||||
sqlx::query_as("SELECT duration_ms, typ FROM sessions_finished WHERE day = ?")
|
||||
.bind(date)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_finished_sessions(&self, before_date: Date) -> Result<()> {
|
||||
sqlx::query!("DELETE FROM sessions_finished WHERE day <= ? ", before_date)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_unique_user(
|
||||
&self,
|
||||
date: Date,
|
||||
client_address_b58: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT OR IGNORE INTO sessions_unique_users (day, client_address) VALUES (?, ?)",
|
||||
date,
|
||||
client_address_b58,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_unique_users(&self, date: Date) -> Result<Vec<String>> {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT client_address as count FROM sessions_unique_users WHERE day = ?",
|
||||
date
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_unique_users(&self, before_date: Date) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions_unique_users WHERE day <= ? ",
|
||||
before_date
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_active_session(
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
start_time: OffsetDateTime,
|
||||
typ: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO sessions_active (client_address, start_time, typ) VALUES (?, ?, ?)",
|
||||
client_address_b58,
|
||||
start_time,
|
||||
typ
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_active_session_type(
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
typ: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE sessions_active SET typ = ? WHERE client_address = ?",
|
||||
typ,
|
||||
client_address_b58,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_active_session(
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
) -> Result<Option<StoredActiveSession>> {
|
||||
sqlx::query_as("SELECT start_time, typ FROM sessions_active WHERE client_address = ?")
|
||||
.bind(client_address_b58)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_active_sessions(&self) -> Result<Vec<StoredActiveSession>> {
|
||||
sqlx::query_as("SELECT start_time, typ FROM sessions_active")
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_started_sessions_count(&self, start_date: Date) -> Result<i32> {
|
||||
Ok(sqlx::query!(
|
||||
"SELECT COUNT(*) as count FROM sessions_active WHERE date(start_time) = ?",
|
||||
start_date
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?
|
||||
.count)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_active_users(&self) -> Result<Vec<String>> {
|
||||
Ok(sqlx::query!("SELECT client_address from sessions_active")
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|record| record.client_address)
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_active_session(&self, client_address_b58: String) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions_active WHERE client_address = ?",
|
||||
client_address_b58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn cleanup_active_sessions(&self) -> Result<()> {
|
||||
sqlx::query!("DELETE FROM sessions_active")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -248,7 +248,7 @@ mod tests {
|
||||
data: IpPacketRequestData::StaticConnect(
|
||||
StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()),
|
||||
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("fc00::1").unwrap()),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
reply_to_hops: None,
|
||||
reply_to_avg_mix_delays: None,
|
||||
|
||||
@@ -429,7 +429,7 @@ mod tests {
|
||||
SignedStaticConnectRequest {
|
||||
request: StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()),
|
||||
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("fc00::1").unwrap()),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
reply_to_hops: None,
|
||||
reply_to_avg_mix_delays: None,
|
||||
|
||||
@@ -23,3 +23,7 @@ default = ["env", "network"]
|
||||
env = ["dotenvy", "log"]
|
||||
network = ["schemars", "serde", "url"]
|
||||
utoipa = [ "dep:utoipa" ]
|
||||
|
||||
[build-dependencies]
|
||||
regex = { workspace = true }
|
||||
cargo_metadata = { version = "0.18" }
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use regex::Regex;
|
||||
use std::{collections::HashMap, fs, path::PathBuf};
|
||||
|
||||
/// Sync variable values defined in code with .env file
|
||||
fn main() {
|
||||
let source_of_truth = include_str!("src/mainnet.rs");
|
||||
let mut output_path = workspace_root();
|
||||
output_path.push("envs");
|
||||
output_path.push("mainnet.env");
|
||||
|
||||
println!("{}", output_path.display());
|
||||
|
||||
let variables_to_track = [
|
||||
"NETWORK_NAME",
|
||||
"BECH32_PREFIX",
|
||||
"MIXNET_CONTRACT_ADDRESS",
|
||||
"VESTING_CONTRACT_ADDRESS",
|
||||
"GROUP_CONTRACT_ADDRESS",
|
||||
"ECASH_CONTRACT_ADDRESS",
|
||||
"MULTISIG_CONTRACT_ADDRESS",
|
||||
"COCONUT_DKG_CONTRACT_ADDRESS",
|
||||
"REWARDING_VALIDATOR_ADDRESS",
|
||||
"NYM_API",
|
||||
"NYXD_WS",
|
||||
"EXPLORER_API",
|
||||
"NYM_VPN_API",
|
||||
];
|
||||
|
||||
let mut replace_with = HashMap::new();
|
||||
|
||||
for var in variables_to_track {
|
||||
// if script fails, debug with `cargo check -vv``
|
||||
println!("Looking for {}", var);
|
||||
|
||||
// read pattern that looks like:
|
||||
// <var>: &str = "<whatever is between quotes>"
|
||||
let pattern = format!(r#"{}: &str\s*=\s*"([^"]*)""#, regex::escape(var));
|
||||
|
||||
let re = Regex::new(&pattern).unwrap();
|
||||
let value = re
|
||||
.captures(source_of_truth)
|
||||
.and_then(|caps| caps.get(1).map(|match_| match_.as_str().to_string()))
|
||||
.expect("Couldn't find var in source file");
|
||||
println!("Storing {}={}", var, value);
|
||||
replace_with.insert(var, value);
|
||||
}
|
||||
|
||||
let mut contents = fs::read_to_string(&output_path).unwrap();
|
||||
|
||||
for (var, value) in replace_with {
|
||||
// match a pattern that looks like:
|
||||
// <var> = <value>
|
||||
// where `<var>` is a variable name inserted into search pattern
|
||||
let pattern = format!(r#"{}\s*=\s*([^\n]*)"#, regex::escape(var));
|
||||
|
||||
// replace matched pattern with
|
||||
// <var>=<value>
|
||||
let re = Regex::new(&pattern).unwrap();
|
||||
contents = re
|
||||
.replace(&contents, |_: ®ex::Captures| {
|
||||
format!(r#"{}={}"#, var, value)
|
||||
})
|
||||
.to_string();
|
||||
}
|
||||
|
||||
println!("File contents to write:\n{}", contents);
|
||||
if output_path.exists() {
|
||||
fs::write(output_path, contents).unwrap();
|
||||
} else {
|
||||
panic!("{} doesn't exist", output_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_root() -> PathBuf {
|
||||
let metadata = MetadataCommand::new()
|
||||
.exec()
|
||||
.expect("Failed to get cargo metadata");
|
||||
|
||||
metadata
|
||||
.workspace_root
|
||||
.into_std_path_buf()
|
||||
.canonicalize()
|
||||
.expect("Failed to canonicalize path")
|
||||
}
|
||||
@@ -45,13 +45,15 @@ pub mod nyx {
|
||||
}
|
||||
|
||||
pub mod wireguard {
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub const WG_PORT: u16 = 51822;
|
||||
|
||||
// The interface used to route traffic
|
||||
pub const WG_TUN_BASE_NAME: &str = "nymwg";
|
||||
pub const WG_TUN_DEVICE_ADDRESS: &str = "10.1.0.1";
|
||||
pub const WG_TUN_DEVICE_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 1, 0, 1));
|
||||
pub const WG_TUN_DEVICE_NETMASK: &str = "255.255.255.0";
|
||||
pub const WG_TUN_DEVICE_IP_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 1, 0, 1);
|
||||
pub const WG_TUN_DEVICE_NETMASK_V4: u8 = 16;
|
||||
pub const WG_TUN_DEVICE_IP_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1); // fc01::1
|
||||
pub const WG_TUN_DEVICE_NETMASK_V6: u8 = 112;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
||||
pub const EXPLORER_API: &str = "EXPLORER_API";
|
||||
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
||||
pub const NYM_VPN_API: &str = "NYM_VPN_API";
|
||||
pub const CLIENT_STATS_COLLECTION_PROVIDER: &str = "CLIENT_STATS_COLLECTION_PROVIDER";
|
||||
|
||||
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ nym-sphinx-types = { path = "../types" }
|
||||
nym-topology = { path = "../../topology" }
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2.93"
|
||||
version = "0.2.95"
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = { workspace = true }
|
||||
|
||||
@@ -28,3 +28,9 @@ impl From<ConnectionError> for Socks5ClientCoreError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl nym_task::TaskStatusEvent for Socks5ClientCoreError {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ use nym_client_core::init::types::GatewaySetup;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::manager::TaskStatus;
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_task::{TaskClient, TaskHandle, TaskStatus};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use nym_validator_client::UserAgent;
|
||||
|
||||
@@ -11,7 +11,18 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures = { workspace = true }
|
||||
log = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::ClientStatsEvents;
|
||||
|
||||
use nym_credentials_interface::TicketType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct ConnectionStats {
|
||||
//tickets
|
||||
mixnet_entry_spent: u32,
|
||||
vpn_entry_spent: u32,
|
||||
mixnet_exit_spent: u32,
|
||||
vpn_exit_spent: u32,
|
||||
|
||||
//country_connection
|
||||
wg_exit_country_code: String,
|
||||
mix_exit_country_code: String,
|
||||
}
|
||||
|
||||
/// Event space for Nym API statistics tracking
|
||||
#[derive(Debug)]
|
||||
pub enum ConnectionStatsEvent {
|
||||
/// ecash ticket was spend
|
||||
TicketSpent {
|
||||
typ: TicketType,
|
||||
amount: u32,
|
||||
},
|
||||
WgCountry(String),
|
||||
MixCountry(String),
|
||||
}
|
||||
impl From<ConnectionStatsEvent> for ClientStatsEvents {
|
||||
fn from(event: ConnectionStatsEvent) -> ClientStatsEvents {
|
||||
ClientStatsEvents::Connection(event)
|
||||
}
|
||||
}
|
||||
|
||||
/// Nym API statistics tracking object
|
||||
#[derive(Default)]
|
||||
pub struct ConnectionStatsControl {
|
||||
// Keep track of packet statistics over time
|
||||
stats: ConnectionStats,
|
||||
}
|
||||
|
||||
impl ConnectionStatsControl {
|
||||
pub(crate) fn handle_event(&mut self, event: ConnectionStatsEvent) {
|
||||
match event {
|
||||
ConnectionStatsEvent::TicketSpent { typ, amount } => match typ {
|
||||
TicketType::V1MixnetEntry => self.stats.mixnet_entry_spent += amount,
|
||||
TicketType::V1MixnetExit => self.stats.mixnet_exit_spent += amount,
|
||||
TicketType::V1WireguardEntry => self.stats.vpn_entry_spent += amount,
|
||||
TicketType::V1WireguardExit => self.stats.vpn_exit_spent += amount,
|
||||
},
|
||||
ConnectionStatsEvent::WgCountry(cc) => {
|
||||
self.stats.wg_exit_country_code = cc;
|
||||
}
|
||||
ConnectionStatsEvent::MixCountry(cc) => {
|
||||
self.stats.mix_exit_country_code = cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report(&self) -> ConnectionStats {
|
||||
self.stats.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! # Gateway Connection statistics
|
||||
//!
|
||||
//! Metrics collected by the client while establishing and maintaining connections to the gateway.
|
||||
|
||||
use super::ClientStatsEvents;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use nym_metrics::{inc, inc_by};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct GatewayStats {
|
||||
// Sent
|
||||
real_packets_sent: u64,
|
||||
real_packets_sent_size: usize,
|
||||
|
||||
/// failed connection statistics
|
||||
failures: VecDeque<()>, // TODO
|
||||
}
|
||||
|
||||
impl GatewayStats {
|
||||
fn handle(&mut self, event: GatewayStatsEvent) {
|
||||
match event {
|
||||
GatewayStatsEvent::RealPacketSent(packet_size) => {
|
||||
self.real_packets_sent += 1;
|
||||
self.real_packets_sent_size += packet_size;
|
||||
inc!("real_packets_sent");
|
||||
inc_by!("real_packets_sent_size", packet_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn summary(&self) -> (String, String) {
|
||||
(
|
||||
format!("packets sent: {}", self.real_packets_sent),
|
||||
"packets received: todo".to_owned(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GatewayStatsEvent> for ClientStatsEvents {
|
||||
fn from(event: GatewayStatsEvent) -> ClientStatsEvents {
|
||||
ClientStatsEvents::GatewayConn(event)
|
||||
}
|
||||
}
|
||||
|
||||
/// Event space for Gateway Connection Events
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayStatsEvent {
|
||||
/// The real packets sent. Recall that acks are sent by the gateway, so it's not included here.
|
||||
RealPacketSent(usize),
|
||||
}
|
||||
|
||||
/// Gateway Statistics Tracking
|
||||
#[derive(Default)]
|
||||
pub struct GatewayStatsControl {
|
||||
// Keep track of packet statistics over time
|
||||
stats: GatewayStats,
|
||||
}
|
||||
|
||||
impl GatewayStatsControl {
|
||||
pub(crate) fn handle_event(&mut self, event: GatewayStatsEvent) {
|
||||
self.stats.handle(event)
|
||||
}
|
||||
|
||||
pub(crate) fn report(&self) -> GatewayStats {
|
||||
self.stats.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn local_report(&self) {
|
||||
self.report_counters();
|
||||
}
|
||||
|
||||
fn report_counters(&self) {
|
||||
log::trace!("packet statistics: {:?}", &self.stats);
|
||||
let (summary_sent, summary_recv) = self.stats.summary();
|
||||
log::debug!("{}", summary_sent);
|
||||
log::debug!("{}", summary_recv);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::report::{ClientStatsReport, OsInformation};
|
||||
|
||||
use nym_task::TaskClient;
|
||||
use time::{OffsetDateTime, Time};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
/// Active gateway connection statistics.
|
||||
pub mod gateway_conn_statistics;
|
||||
|
||||
/// Nym API connection statistics.
|
||||
pub mod nym_api_statistics;
|
||||
|
||||
/// Packet count based statistics.
|
||||
pub mod packet_statistics;
|
||||
|
||||
pub mod connection;
|
||||
|
||||
/// Channel receiving generic stats events to be used by a statistics aggregator.
|
||||
pub type ClientStatsReceiver = tokio::sync::mpsc::UnboundedReceiver<ClientStatsEvents>;
|
||||
|
||||
/// Channel allowing generic statistics events to be reported to a stats event aggregator
|
||||
#[derive(Clone)]
|
||||
pub struct ClientStatsSender {
|
||||
stats_tx: Option<UnboundedSender<ClientStatsEvents>>,
|
||||
}
|
||||
|
||||
impl ClientStatsSender {
|
||||
/// Create a new statistics Sender
|
||||
pub fn new(stats_tx: Option<UnboundedSender<ClientStatsEvents>>) -> Self {
|
||||
ClientStatsSender { stats_tx }
|
||||
}
|
||||
|
||||
/// Report a statistics event using the sender.
|
||||
pub fn report(&self, event: ClientStatsEvents) {
|
||||
if let Some(tx) = &self.stats_tx {
|
||||
if let Err(err) = tx.send(event) {
|
||||
log::error!("Failed to send stats event: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Client Statistics events (static for now)
|
||||
pub enum ClientStatsEvents {
|
||||
/// Packet count events
|
||||
PacketStatistics(packet_statistics::PacketStatisticsEvent),
|
||||
/// Gateway Connection events
|
||||
GatewayConn(gateway_conn_statistics::GatewayStatsEvent),
|
||||
/// Nym API connection events
|
||||
NymApi(nym_api_statistics::NymApiStatsEvent),
|
||||
/// Credential events
|
||||
Connection(connection::ConnectionStatsEvent),
|
||||
}
|
||||
|
||||
/// Controls stats event handling and reporting
|
||||
pub struct ClientStatsController {
|
||||
//static infos
|
||||
last_update_time: OffsetDateTime,
|
||||
client_id: String,
|
||||
client_type: String,
|
||||
os_information: OsInformation,
|
||||
|
||||
// stats collection modules
|
||||
packet_stats: packet_statistics::PacketStatisticsControl,
|
||||
gateway_conn_stats: gateway_conn_statistics::GatewayStatsControl,
|
||||
nym_api_stats: nym_api_statistics::NymApiStatsControl,
|
||||
connection_stats: connection::ConnectionStatsControl,
|
||||
}
|
||||
|
||||
impl ClientStatsController {
|
||||
/// Creates a ClientStatsController given a client_id
|
||||
pub fn new(client_id: String, client_type: String) -> Self {
|
||||
ClientStatsController {
|
||||
last_update_time: ClientStatsController::get_update_time(),
|
||||
client_id,
|
||||
client_type,
|
||||
os_information: OsInformation::new(),
|
||||
packet_stats: Default::default(),
|
||||
gateway_conn_stats: Default::default(),
|
||||
nym_api_stats: Default::default(),
|
||||
connection_stats: Default::default(),
|
||||
}
|
||||
}
|
||||
/// Returns a static ClientStatsReport that can be sent somewhere
|
||||
pub fn build_report(&self) -> ClientStatsReport {
|
||||
ClientStatsReport {
|
||||
last_update_time: self.last_update_time,
|
||||
client_id: self.client_id.clone(),
|
||||
client_type: self.client_type.clone(),
|
||||
os_information: self.os_information.clone(),
|
||||
packet_stats: self.packet_stats.report(),
|
||||
gateway_conn_stats: self.gateway_conn_stats.report(),
|
||||
nym_api_stats: self.nym_api_stats.report(),
|
||||
connection_stats: self.connection_stats.report(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle and dispatch incoming stats event
|
||||
pub fn handle_event(&mut self, stats_event: ClientStatsEvents) {
|
||||
match stats_event {
|
||||
ClientStatsEvents::PacketStatistics(event) => self.packet_stats.handle_event(event),
|
||||
ClientStatsEvents::GatewayConn(event) => self.gateway_conn_stats.handle_event(event),
|
||||
ClientStatsEvents::NymApi(event) => self.nym_api_stats.handle_event(event),
|
||||
ClientStatsEvents::Connection(event) => self.connection_stats.handle_event(event),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the metrics to their initial state.
|
||||
///
|
||||
/// Used to periodically reset the metrics in accordance with periodic reporting strategy
|
||||
pub fn reset(&mut self) {
|
||||
self.nym_api_stats = Default::default();
|
||||
self.gateway_conn_stats = Default::default();
|
||||
self.connection_stats = Default::default();
|
||||
//no periodic reset for packet stats
|
||||
|
||||
self.last_update_time = ClientStatsController::get_update_time();
|
||||
}
|
||||
|
||||
/// snapshot the current state of the metrics for module that needs it
|
||||
pub fn snapshot(&mut self) {
|
||||
//no snapshot for gateway_conn_stats
|
||||
//no snapshot for nym_api_stats
|
||||
self.packet_stats.snapshot();
|
||||
}
|
||||
|
||||
pub fn local_report(&mut self, task_client: &mut TaskClient) {
|
||||
self.packet_stats.local_report(task_client);
|
||||
self.gateway_conn_stats.local_report();
|
||||
self.nym_api_stats.local_report();
|
||||
}
|
||||
|
||||
fn get_update_time() -> OffsetDateTime {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
//Safety : 0 is always a valid number of seconds, hours and minutes comes from a valid source
|
||||
let new_time = Time::from_hms(now.hour(), now.minute(), 0).unwrap();
|
||||
//allows a bigger anonymity by hiding exact sending time
|
||||
now.replace_time(new_time)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! # API Connection statistics
|
||||
//!
|
||||
//! Metrics collected by the client while attempting to pull config from the API.
|
||||
|
||||
use super::ClientStatsEvents;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use nym_metrics::{inc, inc_by};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct NymApiStats {
|
||||
// Sent
|
||||
real_packets_sent: u64,
|
||||
real_packets_sent_size: usize,
|
||||
|
||||
/// API connection failure statistics
|
||||
failures: VecDeque<()>, // TODO
|
||||
}
|
||||
|
||||
impl NymApiStats {
|
||||
fn handle(&mut self, event: NymApiStatsEvent) {
|
||||
match event {
|
||||
NymApiStatsEvent::RealPacketSent(packet_size) => {
|
||||
self.real_packets_sent += 1;
|
||||
self.real_packets_sent_size += packet_size;
|
||||
inc!("real_packets_sent");
|
||||
inc_by!("real_packets_sent_size", packet_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn summary(&self) -> (String, String) {
|
||||
(
|
||||
format!("packets sent: {}", self.real_packets_sent,),
|
||||
"packets received: todo".to_owned(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Event space for Nym API statistics tracking
|
||||
#[derive(Debug)]
|
||||
pub enum NymApiStatsEvent {
|
||||
/// The real packets sent. Recall that acks are sent by the Api, so it's not included here.
|
||||
RealPacketSent(usize),
|
||||
}
|
||||
|
||||
impl From<NymApiStatsEvent> for ClientStatsEvents {
|
||||
fn from(event: NymApiStatsEvent) -> ClientStatsEvents {
|
||||
ClientStatsEvents::NymApi(event)
|
||||
}
|
||||
}
|
||||
|
||||
/// Nym API statistics tracking object
|
||||
#[derive(Default)]
|
||||
pub struct NymApiStatsControl {
|
||||
// Keep track of packet statistics over time
|
||||
stats: NymApiStats,
|
||||
}
|
||||
|
||||
impl NymApiStatsControl {
|
||||
pub(crate) fn handle_event(&mut self, event: NymApiStatsEvent) {
|
||||
self.stats.handle(event)
|
||||
}
|
||||
|
||||
pub(crate) fn report(&self) -> NymApiStats {
|
||||
self.stats.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn local_report(&self) {
|
||||
self.report_counters();
|
||||
}
|
||||
|
||||
fn report_counters(&self) {
|
||||
log::trace!("packet statistics: {:?}", &self.stats);
|
||||
let (summary_sent, summary_recv) = self.stats.summary();
|
||||
log::debug!("{}", summary_sent);
|
||||
log::debug!("{}", summary_recv);
|
||||
}
|
||||
}
|
||||
+107
-213
@@ -1,48 +1,25 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::ClientStatsEvents;
|
||||
use core::fmt;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use nym_metrics::{inc, inc_by};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use si_scale::helpers::bibytes2;
|
||||
|
||||
// Metrics server
|
||||
use futures::future::{FusedFuture, OptionFuture};
|
||||
use futures::FutureExt;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use http_body_util::Full;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use hyper::body::Bytes;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use hyper::server::conn::http1;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use hyper::service::service_fn;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use hyper::{Request, Response};
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use hyper_util::rt::TokioIo;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use std::convert::Infallible;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
#[cfg(feature = "metrics-server")]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::spawn_future;
|
||||
|
||||
// Time interval between reporting packet statistics
|
||||
const PACKET_REPORT_INTERVAL_SECS: u64 = 2;
|
||||
// Interval for taking snapshots of the packet statistics
|
||||
const SNAPSHOT_INTERVAL_MS: u64 = 500;
|
||||
// When computing rates, we include snapshots that are up to this old. We set it to some odd number
|
||||
// a tad larger than an integer number of snapshot intervals, so that we don't have to worry about
|
||||
// threshold effects.
|
||||
// Also, set it larger than the packet report interval so that we don't miss notable singular events
|
||||
const RECORDING_WINDOW_MS: u64 = 2300;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct PacketStatistics {
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct PacketStatistics {
|
||||
// Sent
|
||||
real_packets_sent: u64,
|
||||
real_packets_sent_size: usize,
|
||||
@@ -72,7 +49,7 @@ struct PacketStatistics {
|
||||
}
|
||||
|
||||
impl PacketStatistics {
|
||||
fn handle_event(&mut self, event: PacketStatisticsEvent) {
|
||||
fn handle(&mut self, event: PacketStatisticsEvent) {
|
||||
match event {
|
||||
PacketStatisticsEvent::RealPacketSent(packet_size) => {
|
||||
self.real_packets_sent += 1;
|
||||
@@ -189,29 +166,64 @@ impl std::ops::Sub for PacketStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MixnetBandwidthStatisticsEvent {
|
||||
pub rates: PacketRates,
|
||||
}
|
||||
|
||||
impl MixnetBandwidthStatisticsEvent {
|
||||
pub fn new(rates: PacketRates) -> Self {
|
||||
Self { rates }
|
||||
}
|
||||
}
|
||||
|
||||
impl nym_task::TaskStatusEvent for MixnetBandwidthStatisticsEvent {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MixnetBandwidthStatisticsEvent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.rates.summary())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PacketRates {
|
||||
real_packets_sent: f64,
|
||||
real_packets_sent_size: f64,
|
||||
cover_packets_sent: f64,
|
||||
cover_packets_sent_size: f64,
|
||||
pub struct PacketRates {
|
||||
pub real_packets_sent: f64,
|
||||
pub real_packets_sent_size: f64,
|
||||
pub cover_packets_sent: f64,
|
||||
pub cover_packets_sent_size: f64,
|
||||
|
||||
real_packets_received: f64,
|
||||
real_packets_received_size: f64,
|
||||
cover_packets_received: f64,
|
||||
cover_packets_received_size: f64,
|
||||
pub real_packets_received: f64,
|
||||
pub real_packets_received_size: f64,
|
||||
pub cover_packets_received: f64,
|
||||
pub cover_packets_received_size: f64,
|
||||
|
||||
total_acks_received: f64,
|
||||
total_acks_received_size: f64,
|
||||
real_acks_received: f64,
|
||||
real_acks_received_size: f64,
|
||||
cover_acks_received: f64,
|
||||
cover_acks_received_size: f64,
|
||||
pub total_acks_received: f64,
|
||||
pub total_acks_received_size: f64,
|
||||
pub real_acks_received: f64,
|
||||
pub real_acks_received_size: f64,
|
||||
pub cover_acks_received: f64,
|
||||
pub cover_acks_received_size: f64,
|
||||
|
||||
real_packets_queued: f64,
|
||||
retransmissions_queued: f64,
|
||||
reply_surbs_queued: f64,
|
||||
additional_reply_surbs_queued: f64,
|
||||
pub real_packets_queued: f64,
|
||||
pub retransmissions_queued: f64,
|
||||
pub reply_surbs_queued: f64,
|
||||
pub additional_reply_surbs_queued: f64,
|
||||
}
|
||||
|
||||
impl fmt::Display for PacketRates {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"down: {}/s, up: {}/s (cover down: {}/s, cover up: {}/s)",
|
||||
bibytes2(self.real_packets_received_size),
|
||||
bibytes2(self.real_packets_sent_size),
|
||||
bibytes2(self.cover_packets_received_size),
|
||||
bibytes2(self.cover_packets_sent_size),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PacketStatistics> for PacketRates {
|
||||
@@ -330,56 +342,46 @@ impl PacketRates {
|
||||
}
|
||||
}
|
||||
|
||||
/// Event Space used for counting the Packet types used in a connection.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PacketStatisticsEvent {
|
||||
// The real packets sent. Recall that acks are sent by the gateway, so it's not included here.
|
||||
pub enum PacketStatisticsEvent {
|
||||
/// The real packets sent. Recall that acks are sent by the gateway, so it's not included here.
|
||||
RealPacketSent(usize),
|
||||
// The cover packets sent
|
||||
/// The cover packets sent
|
||||
CoverPacketSent(usize),
|
||||
|
||||
// Real packets received
|
||||
/// Real packets received
|
||||
RealPacketReceived(usize),
|
||||
// Cover packets received
|
||||
/// Cover packets received
|
||||
CoverPacketReceived(usize),
|
||||
|
||||
// Ack of any type received. This is mostly used as a consistency check, and should be the sum
|
||||
// of real and cover acks received.
|
||||
/// Ack of any type received. This is mostly used as a consistency check, and should be the sum
|
||||
/// of real and cover acks received.
|
||||
AckReceived(usize),
|
||||
// Out of the total acks received, this is the subset of those that were real
|
||||
/// Out of the total acks received, this is the subset of those that were real
|
||||
RealAckReceived(usize),
|
||||
// Out of the total acks received, this is the subset of those that were for cover traffic
|
||||
/// Out of the total acks received, this is the subset of those that were for cover traffic
|
||||
CoverAckReceived(usize),
|
||||
|
||||
// Types of packets queued
|
||||
/// Types of packets queued
|
||||
RealPacketQueued,
|
||||
/// Types of packets queued
|
||||
RetransmissionQueued,
|
||||
/// Types of packets queued
|
||||
ReplySurbRequestQueued,
|
||||
/// Types of packets queued
|
||||
AdditionalReplySurbRequestQueued,
|
||||
}
|
||||
|
||||
type PacketStatisticsReceiver = tokio::sync::mpsc::UnboundedReceiver<PacketStatisticsEvent>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PacketStatisticsReporter {
|
||||
stats_tx: tokio::sync::mpsc::UnboundedSender<PacketStatisticsEvent>,
|
||||
}
|
||||
|
||||
impl PacketStatisticsReporter {
|
||||
pub(crate) fn new(stats_tx: tokio::sync::mpsc::UnboundedSender<PacketStatisticsEvent>) -> Self {
|
||||
Self { stats_tx }
|
||||
}
|
||||
|
||||
pub(crate) fn report(&self, event: PacketStatisticsEvent) {
|
||||
self.stats_tx.send(event).unwrap_or_else(|err| {
|
||||
log::error!("Failed to report packet stat: {:?}", err);
|
||||
});
|
||||
impl From<PacketStatisticsEvent> for ClientStatsEvents {
|
||||
fn from(event: PacketStatisticsEvent) -> ClientStatsEvents {
|
||||
ClientStatsEvents::PacketStatistics(event)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PacketStatisticsControl {
|
||||
// Incoming packet stats events from other tasks
|
||||
stats_rx: PacketStatisticsReceiver,
|
||||
|
||||
/// Statistics tracking for Packet based I/O
|
||||
#[derive(Default)]
|
||||
pub struct PacketStatisticsControl {
|
||||
// Keep track of packet statistics over time
|
||||
stats: PacketStatistics,
|
||||
|
||||
@@ -392,18 +394,28 @@ pub(crate) struct PacketStatisticsControl {
|
||||
}
|
||||
|
||||
impl PacketStatisticsControl {
|
||||
pub(crate) fn new() -> (Self, PacketStatisticsReporter) {
|
||||
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
pub(crate) fn handle_event(&mut self, event: PacketStatisticsEvent) {
|
||||
self.stats.handle(event)
|
||||
}
|
||||
|
||||
(
|
||||
Self {
|
||||
stats_rx,
|
||||
stats: PacketStatistics::default(),
|
||||
history: VecDeque::new(),
|
||||
rates: VecDeque::new(),
|
||||
},
|
||||
PacketStatisticsReporter::new(stats_tx),
|
||||
)
|
||||
pub(crate) fn snapshot(&mut self) {
|
||||
self.update_history();
|
||||
self.update_rates();
|
||||
}
|
||||
|
||||
pub(crate) fn report(&self) -> PacketStatistics {
|
||||
self.stats.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn local_report(&mut self, task_client: &mut nym_task::TaskClient) {
|
||||
let rates = self.report_rates();
|
||||
self.check_for_notable_events();
|
||||
self.report_counters();
|
||||
|
||||
// Report our current bandwidth used to e.g a GUI client
|
||||
if let Some(rates) = rates {
|
||||
task_client.send_status_msg(Box::new(MixnetBandwidthStatisticsEvent::new(rates)));
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current stats to the history, and remove old ones.
|
||||
@@ -456,11 +468,13 @@ impl PacketStatisticsControl {
|
||||
}
|
||||
}
|
||||
|
||||
fn report_rates(&self) {
|
||||
fn report_rates(&self) -> Option<PacketRates> {
|
||||
if let Some((_, rates)) = self.rates.back() {
|
||||
log::debug!("{}", rates.summary());
|
||||
log::debug!("{}", rates.detailed_summary());
|
||||
return Some(rates.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn report_counters(&self) {
|
||||
@@ -498,124 +512,4 @@ impl PacketStatisticsControl {
|
||||
|
||||
// IDEA: if there is a burst of acks, that could indicate tokio task starvation.
|
||||
}
|
||||
|
||||
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
log::debug!("Started PacketStatisticsControl with graceful shutdown support");
|
||||
|
||||
let report_interval = Duration::from_secs(PACKET_REPORT_INTERVAL_SECS);
|
||||
let mut report_interval = tokio::time::interval(report_interval);
|
||||
let snapshot_interval = Duration::from_millis(SNAPSHOT_INTERVAL_MS);
|
||||
let mut snapshot_interval = tokio::time::interval(snapshot_interval);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
|
||||
log::warn!("Metrics server is not supported on wasm32-unknown-unknown");
|
||||
let listener: Option<WasmEmpty> = None;
|
||||
} else if #[cfg(feature = "metrics-server")] {
|
||||
let mut metrics_port = 18000;
|
||||
let listener: Option<TcpListener>;
|
||||
loop {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], metrics_port));
|
||||
match TcpListener::bind(addr).await {
|
||||
Ok(l) => {
|
||||
log::info!("###############################");
|
||||
log::info!("Metrics endpoint is at: {:?}", l.local_addr());
|
||||
log::info!("###############################");
|
||||
listener = Some(l);
|
||||
break;
|
||||
},
|
||||
Err(err) => {
|
||||
log::warn!("Failed to bind metrics server: {:?}", err);
|
||||
metrics_port += 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
log::info!("Metrics server is disabled!");
|
||||
let listener: Option<TcpListener> = None;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
// it seems at some point tokio changed its select precondition evaluation,
|
||||
// and it's no longer checked before the future is evaluated.
|
||||
let accept_future: OptionFuture<_> = listener
|
||||
.as_ref()
|
||||
.map(|l| l.accept())
|
||||
.map(FutureExt::fuse)
|
||||
.into();
|
||||
|
||||
tokio::select! {
|
||||
stats_event = self.stats_rx.recv() => match stats_event {
|
||||
Some(stats_event) => {
|
||||
log::trace!("PacketStatisticsControl: Received stats event");
|
||||
self.stats.handle_event(stats_event);
|
||||
},
|
||||
None => {
|
||||
log::trace!("PacketStatisticsControl: stopping since stats channel was closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
// conditional will disable the branch if we're in wasm32-unknown-unknown
|
||||
// use `_` to calm down clippy when running for wasm
|
||||
_result = accept_future, if !accept_future.is_terminated() => {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
|
||||
if let Some(Ok((stream, _))) = _result {
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = http1::Builder::new()
|
||||
.serve_connection(io, service_fn(serve_metrics))
|
||||
.await
|
||||
{
|
||||
log::warn!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log::warn!("Error accepting connection");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = snapshot_interval.tick() => {
|
||||
self.update_history();
|
||||
self.update_rates();
|
||||
}
|
||||
_ = report_interval.tick() => {
|
||||
self.report_rates();
|
||||
self.check_for_notable_events();
|
||||
self.report_counters();
|
||||
}
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("PacketStatisticsControl: Received shutdown");
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
log::debug!("PacketStatisticsControl: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start_with_shutdown(mut self, task_client: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
self.run_with_shutdown(task_client).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
async fn serve_metrics(
|
||||
_: Request<hyper::body::Incoming>,
|
||||
) -> Result<Response<Full<Bytes>>, Infallible> {
|
||||
use nym_metrics::metrics;
|
||||
|
||||
Ok(Response::new(Full::new(Bytes::from(metrics!()))))
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
||||
struct WasmEmpty;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
||||
impl WasmEmpty {
|
||||
async fn accept(&self) {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error types occurring while processing statistics events and reporting.
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum StatsError {
|
||||
#[error("Failed to (de)serialize stats report : {0}")]
|
||||
ReportJsonSerialization(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
/// Result of a statistics operation.
|
||||
pub type Result<T> = core::result::Result<T, StatsError>;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user