Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c5f264cbf | |||
| 73b74ad4d2 | |||
| 4b4f211cba | |||
| 7a26e2ef57 | |||
| 9782945c92 | |||
| 573a91e015 | |||
| eb06285653 | |||
| 728542181e | |||
| 8f52c2a229 | |||
| 3916bbf632 | |||
| f8ce87a205 | |||
| c9b5ac2abf | |||
| bd5577c0a4 | |||
| 3e9178a664 | |||
| ac822164af | |||
| edc404d6d7 | |||
| e0e5317b44 | |||
| 6711a3bb3c | |||
| 20130fb2a2 | |||
| 59560ac125 | |||
| 893648cad0 | |||
| 27502b9c75 | |||
| 2ab4a445b7 | |||
| 7f5e2f2909 | |||
| d5bc26d2e3 | |||
| aaa965937b | |||
| b3f883e82b | |||
| 9ae4fd04ac | |||
| 4470969bec | |||
| 1a4c3a7709 | |||
| 99b31920d5 | |||
| 62ccb6b4cd | |||
| 365e0134b4 | |||
| 8432c30f6c | |||
| c2764f90b3 | |||
| a6a39d1234 | |||
| 5f35d54fcb | |||
| 096a599673 | |||
| 41da67ad6f | |||
| 273a741cdf | |||
| b5c8b69547 | |||
| 5bd87bdaa8 | |||
| 7d64618701 | |||
| 4151c65251 | |||
| ccb48f92cd | |||
| 8fce478a1f | |||
| 4f712ad4ba | |||
| 562fd44a30 | |||
| 603e897e2d | |||
| 6c122aad10 | |||
| b4ac601a82 | |||
| ce380a6b0d | |||
| 5b02801c04 | |||
| 525372d7ac | |||
| e473a05250 | |||
| a55d604bf5 | |||
| 5075894ff5 | |||
| c392266a4c | |||
| 9b540936db | |||
| 7bf1036b68 | |||
| 1d82ec56d8 | |||
| 3ae5b59141 | |||
| 929b401f95 | |||
| d158deba77 | |||
| 5c77b48708 | |||
| 73d53653db | |||
| 74bedead20 | |||
| 1bec95d2a1 | |||
| 30137e285d | |||
| 5253d5ec51 | |||
| 7ed7329917 | |||
| 4abc0ae0ba | |||
| 47d8fcb21a | |||
| acef5a5652 | |||
| cb919a3af9 | |||
| 8e021a4419 | |||
| e5db7cb915 | |||
| 9bd03af3e9 | |||
| e8a026ef0b | |||
| 86755aa6ba | |||
| 77e0c6425e | |||
| 0373e2b02a | |||
| e8dd347186 | |||
| 5681920092 | |||
| edbf35cb34 | |||
| c46d04d3c4 | |||
| 4ebd1dd7f5 | |||
| 62ab760656 | |||
| 32de7efc32 | |||
| deae210b82 | |||
| 5b2b45a6eb | |||
| bc0f0cfc55 | |||
| 2661e4539e | |||
| 2fe5a5249c | |||
| c5b678d4e9 | |||
| 487f07ef35 | |||
| 6a63be63e9 | |||
| 552c99389e | |||
| 55502d210c | |||
| 66967ca88e | |||
| ba6d02b1db | |||
| ebc957c04d | |||
| 81c3ad74ca | |||
| 88f57e6d87 | |||
| 28a965e698 | |||
| 029c445805 | |||
| 358c95d541 | |||
| 0f7793a881 | |||
| 5478c23563 | |||
| 9f39e93e7b | |||
| 896a3e1be6 | |||
| 800390db85 | |||
| 1eaa13155c | |||
| fad3346096 | |||
| 150f832f8e | |||
| 202336b8a1 | |||
| f0e94f8e5e | |||
| 6cd00b8d10 | |||
| 534187cc8f | |||
| 25b4934f69 | |||
| 5ef7e24893 | |||
| 87ef46bc05 | |||
| f7bc5be8e4 | |||
| b309583886 | |||
| 245185710a | |||
| b7cfe31d72 | |||
| 68ca41a6be | |||
| 5621e7d22e | |||
| a1a5c7772d | |||
| b47deafc14 | |||
| cc6a6d8db2 | |||
| 5b28e24c17 | |||
| f8d68d8ef0 | |||
| a9d86508b5 | |||
| bb7fa587de | |||
| 6585732dfc | |||
| 2065e0fc17 | |||
| 3f7bdad59c | |||
| 6209e78c1e | |||
| 8e5062af96 | |||
| 496e642d7f | |||
| 07e18ec198 | |||
| d5953c28c1 | |||
| 3aa4b66588 | |||
| 005f0ce340 | |||
| 8d1d025fa2 | |||
| 1d53a2f954 | |||
| 966d123608 | |||
| 963d55273f | |||
| 6872d7bf77 | |||
| 6fe93bcda0 | |||
| 78d568e04e | |||
| 8880bdd857 | |||
| cc83ecf7e4 | |||
| 796f5a678a | |||
| 00b60f5493 | |||
| 0dfd1cca44 | |||
| eacefd3697 | |||
| 424c25768c | |||
| e460c1700e | |||
| 9935c99d41 | |||
| 2c4aee63bf | |||
| 0ebc395df9 | |||
| 1de3317e75 | |||
| 91c20af893 | |||
| 1365e2f246 | |||
| cfa1ce46f2 | |||
| 3f4f76859b | |||
| 3f7f4b82de | |||
| 934ba2b027 | |||
| 221e1870e5 | |||
| 9b36bccf0c | |||
| 80d7285497 | |||
| b94f2a483d | |||
| f64cfb4cd1 | |||
| eda223ed3d | |||
| c98d4305fa | |||
| 2eecbca6eb | |||
| a58c80ef08 | |||
| ac9d0db8be | |||
| 7521d98963 | |||
| 1e98131090 | |||
| 46bf65462c | |||
| e3df4c2d68 | |||
| 45c013350f | |||
| 30e2f27c64 | |||
| 6fecc53975 | |||
| e4dbfb1904 | |||
| 3113c1e9a7 | |||
| f822d3db7b | |||
| 9d23766288 | |||
| fd4930b198 | |||
| 5d7be89edb | |||
| 47f5b4ceac | |||
| 790220039b | |||
| 16fdfa4583 | |||
| 1d8a931e0c | |||
| 48d0883b31 | |||
| e271370326 | |||
| cbbeb66b5b | |||
| de020f46a6 | |||
| f24bb5c038 | |||
| 79dfe7eeda | |||
| 0108c6ed19 | |||
| 0e8f60d501 | |||
| 6e30e6178b |
+17
-22
@@ -11,30 +11,25 @@
|
||||
# In each subsection folders are ordered first by depth, then alphabetically.
|
||||
# This should make it easy to add new rules without breaking existing ones.
|
||||
|
||||
# Something weird not covered by anything else
|
||||
* @futurechimp @mmsinclair
|
||||
# contracts
|
||||
/contracts/mixnet @durch @jstuczyn
|
||||
/contracts/vesting @durch @jstuczyn
|
||||
/contracts/service-provider-directory @octol
|
||||
|
||||
# Rust rules:
|
||||
*.rs @durch @futurechimp @jstuczyn @neacsu @octol
|
||||
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
|
||||
# crypto code
|
||||
/common/crypto/ @jstuczyn
|
||||
/common/nymcoconut/ @jstuczyn
|
||||
/common/dkg/ @jstuczyn
|
||||
/common/nymsphinx/ @jstuczyn
|
||||
|
||||
# JS rules:
|
||||
*.js @mmsinclair @fmtabbara
|
||||
*.ts @mmsinclair @fmtabbara
|
||||
*.tsx @mmsinclair @fmtabbara
|
||||
*.jsx @mmsinclair @fmtabbara
|
||||
# rust sdk
|
||||
/sdk/rust/ @octol
|
||||
|
||||
# Something looking like possible documentation rules:
|
||||
*.md @mfahampshire
|
||||
# nym-connect (rust)
|
||||
/nym-connect/desktop/src-tauri/ @octol
|
||||
|
||||
# our docker scripts
|
||||
/docker/ @neacsu
|
||||
# nym-wallet (rust)
|
||||
/nym-wallet/src-tauri/ @octol
|
||||
|
||||
# if there are any changes in the core crypto, I feel like Ania should take a look:
|
||||
/common/crypto/ @aniampio
|
||||
/common/nymsphinx/ @aniampio
|
||||
|
||||
# Explorer and wallet should probably get looked by the product team
|
||||
/explorer/ @nymtech/product
|
||||
/nym-wallet/ @nymtech/product
|
||||
/wallet-web/ @nymtech/product
|
||||
# documentation
|
||||
/documentation @mfahampshire
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: 'Documentation'
|
||||
about: Suggest a fix or enhancement to the documentation or developer portal content
|
||||
title: "[DOCS]"
|
||||
labels: documentation
|
||||
assignees: mfahampshire
|
||||
|
||||
---
|
||||
|
||||
Is your issue either:
|
||||
- [ ] a fix to existing documentation (e.g. fixing a broken link or incorrect command)
|
||||
- [ ] an enhancement (e.g. adding a description for an undocumented feature)
|
||||
|
||||
Please briefly describe your issue:
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: 1.69.0
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: 1.69.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -9,12 +9,14 @@ on:
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/lib/socks5-listener/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
- 'Cargo.toml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
@@ -23,12 +25,14 @@ on:
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/lib/socks5-listener/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
- 'Cargo.toml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
name: Nym Connect - Android APK Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "release/nc-android-v[0-9].[0-9].[0-9]*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build APK
|
||||
runs-on: custom-runner-linux
|
||||
env:
|
||||
ANDROID_HOME: ${{ github.workspace }}/android-sdk
|
||||
NDK_VERSION: 25.1.8937393
|
||||
NDK_HOME: ${{ github.workspace }}/android-sdk/ndk/25.1.8937393
|
||||
SDK_PLATFORM_VERSION: android-33
|
||||
SDK_BUILDTOOLS_VERSION: 33.0.1
|
||||
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
# https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux/#1-system-dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install \
|
||||
build-essential \
|
||||
unzip \
|
||||
curl \
|
||||
wget \
|
||||
libssl-dev \
|
||||
squashfs-tools \
|
||||
librsvg2-dev
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Install Android SDK manager
|
||||
# https://developer.android.com/studio/command-line/sdkmanager
|
||||
run: |
|
||||
curl -sS https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -o cmdline-tools.zip
|
||||
unzip cmdline-tools.zip
|
||||
mkdir -p $ANDROID_HOME/cmdline-tools/latest
|
||||
mv cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest
|
||||
rm -rf cmdline-tools
|
||||
|
||||
- name: Install Android S/NDK
|
||||
run: |
|
||||
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
|
||||
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
|
||||
"platforms;$SDK_PLATFORM_VERSION" \
|
||||
"platform-tools" \
|
||||
"ndk;$NDK_VERSION" \
|
||||
"build-tools;$SDK_BUILDTOOLS_VERSION"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# TODO this step takes a considerable amount of time
|
||||
# We could avoid to compile from source tauri-cli and use instead
|
||||
# pre-compiled binary provided by the node package `@tauri-apps/cli`
|
||||
# But when using the later the build fails for some reason
|
||||
# so keep installing and using tauri-cli
|
||||
- name: Install tauri cli
|
||||
run: cargo install tauri-cli --version "^2.0.0-alpha.2"
|
||||
|
||||
- name: Install rust android targets
|
||||
run: |
|
||||
rustup target add aarch64-linux-android \
|
||||
armv7-linux-androideabi \
|
||||
i686-linux-android \
|
||||
x86_64-linux-android
|
||||
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install yarn
|
||||
run: |
|
||||
npm i -g yarn
|
||||
yarn --version
|
||||
|
||||
- name: Build frontend code
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
yarn build
|
||||
yarn workspace @nym/nym-connect-mobile webpack:prod
|
||||
|
||||
- name: Build APK
|
||||
working-directory: nym-connect/mobile
|
||||
env:
|
||||
# NODE_TAURI_CLI=${{ github.workspace }}/nym-connect/mobile/node_modules/.bin/tauri
|
||||
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
|
||||
WRY_ANDROID_PACKAGE: net.nymtech.nym_connect
|
||||
WRY_ANDROID_LIBRARY: nym_connect
|
||||
# TODO build with release profile (--release), it will requires
|
||||
# to sign the APK. For now build with debug profile to avoid that
|
||||
# TODO build using `yarn tauri`, provide NODE_TAURI_CLI, see TODO notes above
|
||||
run: cargo tauri android build --debug --apk --split-per-abi -t aarch64
|
||||
|
||||
# TODO add the version number to APK name
|
||||
- name: Rename APK artifact
|
||||
run: |
|
||||
mkdir apk/
|
||||
mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk \
|
||||
apk/nym-connect-arm64-debug.apk
|
||||
mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/x86_64/debug/app-x86_64-debug.apk \
|
||||
apk/nym-connect-x86_64-debug.apk
|
||||
|
||||
- name: Upload APK artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: nc-apk-debug
|
||||
path: |
|
||||
apk/nym-connect-arm64-debug.apk
|
||||
apk/nym-connect-x86_64-debug.apk
|
||||
|
||||
# publish:
|
||||
# name: Publish APK
|
||||
# needs: build
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v3
|
||||
# - name: Download binary artifact
|
||||
# uses: actions/download-artifact@v3
|
||||
# with:
|
||||
# name: nc-apk-debug
|
||||
# path: apk
|
||||
# # TODO add a step to upload the APK somewhere
|
||||
# - name: Publish
|
||||
# uses: ???
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"os":"windows10",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
@@ -22,7 +22,7 @@
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"os":"windows10",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
@@ -38,7 +38,7 @@
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"os":"windows10",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
name: Nyms5 Android
|
||||
# unsigned APKs only, supported archs:
|
||||
# - arm64-v8a (arm64)
|
||||
# - x86_64
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- nyms5-android-v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build APK
|
||||
runs-on: custom-runner-linux
|
||||
env:
|
||||
ANDROID_HOME: ${{ github.workspace }}/android-sdk
|
||||
NDK_VERSION: 25.2.9519653
|
||||
NDK_HOME: ${{ github.workspace }}/android-sdk/ndk/25.2.9519653
|
||||
SDK_PLATFORM_VERSION: android-33
|
||||
SDK_BUILDTOOLS_VERSION: 33.0.2
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Install Android SDK manager
|
||||
# https://developer.android.com/studio/command-line/sdkmanager
|
||||
run: |
|
||||
curl -sS https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -o cmdline-tools.zip
|
||||
unzip cmdline-tools.zip
|
||||
mkdir -p $ANDROID_HOME/cmdline-tools/latest
|
||||
mv cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest
|
||||
rm -rf cmdline-tools
|
||||
|
||||
- name: Install Android S/NDK
|
||||
run: |
|
||||
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
|
||||
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
|
||||
"platforms;$SDK_PLATFORM_VERSION" \
|
||||
"platform-tools" \
|
||||
"ndk;$NDK_VERSION" \
|
||||
"build-tools;$SDK_BUILDTOOLS_VERSION"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install rust android targets
|
||||
run: |
|
||||
rustup target add aarch64-linux-android \
|
||||
x86_64-linux-android
|
||||
|
||||
- name: Build lib nym-socks5-listener
|
||||
working-directory: sdk/lib/socks5-listener/
|
||||
env:
|
||||
RELEASE: true
|
||||
# build for arm64 and x86_64
|
||||
run: ./build-android.sh aarch64 x86_64
|
||||
|
||||
- name: Build APKs (unsigned)
|
||||
working-directory: nym-connect/native/android
|
||||
env:
|
||||
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
|
||||
# build for arm64 and x86_64
|
||||
run: ./gradlew :app:assembleArch64Release
|
||||
|
||||
- name: Prepare APKs
|
||||
run: |
|
||||
mkdir apk
|
||||
mv nym-connect/native/android/app/build/outputs/apk/arch64/release/app-arch64-release-unsigned.apk \
|
||||
apk/nyms5-arch64-release.apk
|
||||
|
||||
- name: Upload APKs
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: nyms5-apk-arch64-release
|
||||
path: |
|
||||
apk/nyms5-arch64-release.apk
|
||||
|
||||
gh-release:
|
||||
name: Publish APK (GH release)
|
||||
needs: build
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Download binary artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: nyms5-apk-arch64-release
|
||||
path: apk
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: apk/nyms5-arch64-release.apk
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
name: Upload nyxd to CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-nyxd:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare build output directory
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/nyxd
|
||||
run: |
|
||||
rm -rf ci-builds || true
|
||||
mkdir -p $OUTPUT_DIR
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools git
|
||||
continue-on-error: true
|
||||
|
||||
- name: Update env variables to include go
|
||||
run: |
|
||||
sudo rm -rf /usr/local/go
|
||||
curl https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf -
|
||||
cat <<'EOF' >>$HOME/.profile
|
||||
export GOROOT=/usr/local/go
|
||||
export GOPATH=$HOME/go
|
||||
export GO111MODULE=on
|
||||
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
|
||||
EOF
|
||||
source $HOME/.profile
|
||||
|
||||
- name: Verify Go is installed
|
||||
run: go version
|
||||
|
||||
- name: Clone nyxd repo
|
||||
run: |
|
||||
git clone https://github.com/tommyv1987/nyxd
|
||||
cd nyxd
|
||||
git checkout release/v0.30.2
|
||||
|
||||
- name: Run nyxd
|
||||
run: |
|
||||
pwd
|
||||
cd nyxd && make build
|
||||
sleep 10
|
||||
ls /home/runner/work/nym/nym/nyxd/build
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/nyxd
|
||||
run: |
|
||||
cp /home/runner/work/nym/nym/nyxd/build/nyxd $OUTPUT_DIR
|
||||
WASMVM_SO=$(ldd /home/runner/work/nym/nym/nyxd/build/nyxd | grep "libwasm*" | awk '{ print $3 }')
|
||||
ls $WASMVM_SO
|
||||
sleep 3
|
||||
cp $(echo $WASMVM_SO) $OUTPUT_DIR
|
||||
|
||||
- name: Deploy nyxd to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-avzr"
|
||||
SOURCE: "ci-builds/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
@@ -8,6 +8,7 @@
|
||||
.idea
|
||||
target
|
||||
.env
|
||||
.env.dev
|
||||
/.vscode/settings.json
|
||||
validator/.vscode
|
||||
sample-configs/validator-config.toml
|
||||
@@ -42,3 +43,4 @@ envs/qwerty.env
|
||||
.parcel-cache
|
||||
**/.DS_Store
|
||||
cpu-cycles/libcpucycles/build
|
||||
foxyfox.env
|
||||
+62
-7
@@ -4,14 +4,69 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- nym-network-statistics properly handles signals ([#3209])
|
||||
- add socks5 support for Rust SDK ([#3226], [#3255])
|
||||
- add coconut bandwidth credential support for Rust SDK ([#3273])
|
||||
## [v1.1.19] (2023-05-16)
|
||||
|
||||
[#3209]: https://github.com/nymtech/nym/issues/3209
|
||||
[#3226]: https://github.com/nymtech/nym/pull/3226
|
||||
[#3255]: https://github.com/nymtech/nym/pull/3255
|
||||
[#3273]: https://github.com/nymtech/nym/pull/3273
|
||||
- nym-name-service endpoint in nym-api ([#3403])
|
||||
- Implement key storage for WASM client using IndexedDB (for browser) ([#3329])
|
||||
- Initial version of nym-name-service contract providing name aliases for nym-addresses ([#3274])
|
||||
- Update Cargo.lock ([#3410])
|
||||
|
||||
[#3403]: https://github.com/nymtech/nym/issues/3403
|
||||
[#3329]: https://github.com/nymtech/nym/issues/3329
|
||||
[#3274]: https://github.com/nymtech/nym/issues/3274
|
||||
[#3410]: https://github.com/nymtech/nym/pull/3410
|
||||
|
||||
## [v1.1.18] (2023-05-09)
|
||||
|
||||
- Implement heartbeat messages between socks5 proxy and network requester ([#3215])
|
||||
|
||||
[#3215]: https://github.com/nymtech/nym/issues/3215
|
||||
|
||||
## [v1.1.17] (2023-05-02)
|
||||
|
||||
- Add service-provider-directory-contract support to nym-cli ([#3334])
|
||||
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312])
|
||||
- Add service-provider-directory support to validator-client ([#3296])
|
||||
- Allow topology injection in our WASM client ('test my node' feature) ([#3270])
|
||||
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
|
||||
- Cache service provider contract in nym-api ([#3241])
|
||||
- Feature/1 1 17 docs ([#3370])
|
||||
- adding a test for SP endpoint ([#3367])
|
||||
- Feature/store cipher ([#3350])
|
||||
|
||||
[#3334]: https://github.com/nymtech/nym/issues/3334
|
||||
[#3312]: https://github.com/nymtech/nym/issues/3312
|
||||
[#3296]: https://github.com/nymtech/nym/issues/3296
|
||||
[#3270]: https://github.com/nymtech/nym/issues/3270
|
||||
[#3242]: https://github.com/nymtech/nym/issues/3242
|
||||
[#3241]: https://github.com/nymtech/nym/issues/3241
|
||||
[#3370]: https://github.com/nymtech/nym/pull/3370
|
||||
[#3367]: https://github.com/nymtech/nym/pull/3367
|
||||
[#3350]: https://github.com/nymtech/nym/pull/3350
|
||||
|
||||
## [v1.1.16] (2023-04-25)
|
||||
|
||||
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
|
||||
- Poisson process gets stuck at too slow rate. Rework to more aggressively up-regulate ([#3309])
|
||||
- decrease the logging level of warnings associated with clients dropping packets due to gateway being overloaded (I'd say reduce it to debug/trace) - there are few sources of those, e.g. in real and cover traffic streams ([#3299])
|
||||
- Make the buffer size in `AvailableReader` depend on packet sizes the client is using + introduce read timeouts ([#3213])
|
||||
- Rust SDK - Support coconut, credential storage etc ([#2755])
|
||||
- version bump for next release ([#3349])
|
||||
- added coconut credential generation example ([#3339])
|
||||
- update mix-node setup docs with node description ([#3325])
|
||||
- exposed missing gateway commands in nym-cli ([#3324])
|
||||
- make sure to clear inner 'ack_map' in 'GatewaysReader' ([#3300])
|
||||
|
||||
[#3320]: https://github.com/nymtech/nym/issues/3320
|
||||
[#3309]: https://github.com/nymtech/nym/issues/3309
|
||||
[#3299]: https://github.com/nymtech/nym/issues/3299
|
||||
[#3213]: https://github.com/nymtech/nym/issues/3213
|
||||
[#2755]: https://github.com/nymtech/nym/issues/2755
|
||||
[#3349]: https://github.com/nymtech/nym/pull/3349
|
||||
[#3339]: https://github.com/nymtech/nym/pull/3339
|
||||
[#3325]: https://github.com/nymtech/nym/pull/3325
|
||||
[#3324]: https://github.com/nymtech/nym/pull/3324
|
||||
[#3300]: https://github.com/nymtech/nym/pull/3300
|
||||
|
||||
## [v1.1.15] (2023-04-18)
|
||||
|
||||
|
||||
Generated
+1041
-565
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,7 @@ members = [
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
"common/cosmwasm-smart-contracts/multisig-contract",
|
||||
"common/cosmwasm-smart-contracts/name-service",
|
||||
"common/cosmwasm-smart-contracts/service-provider-directory",
|
||||
"common/cosmwasm-smart-contracts/vesting-contract",
|
||||
"common/credential-storage",
|
||||
@@ -48,6 +49,7 @@ members = [
|
||||
"common/ledger",
|
||||
"common/mixnode-common",
|
||||
"common/network-defaults",
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nymsphinx",
|
||||
@@ -59,12 +61,14 @@ members = [
|
||||
"common/nymsphinx/forwarding",
|
||||
"common/nymsphinx/framing",
|
||||
"common/nymsphinx/params",
|
||||
"common/nymsphinx/routing",
|
||||
"common/nymsphinx/types",
|
||||
"common/pemstore",
|
||||
"common/socks5-client-core",
|
||||
"common/socks5/proxy-helpers",
|
||||
"common/socks5/requests",
|
||||
"common/statistics",
|
||||
"common/store-cipher",
|
||||
"common/task",
|
||||
"common/topology",
|
||||
"common/types",
|
||||
@@ -74,6 +78,7 @@ members = [
|
||||
"gateway/gateway-requests",
|
||||
"integrations/bity",
|
||||
"mixnode",
|
||||
"sdk/lib/socks5-listener",
|
||||
"sdk/rust/nym-sdk",
|
||||
"service-providers/common",
|
||||
"service-providers/network-requester",
|
||||
@@ -108,6 +113,7 @@ license = "Apache-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
async-trait = "0.1.64"
|
||||
anyhow = "1.0.71"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
cfg-if = "1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
@@ -121,6 +127,7 @@ cw3 = { version = "=0.13.4" }
|
||||
cw3-fixed-multisig = { version = "=0.13.4" }
|
||||
cw4 = { version = "=0.13.4" }
|
||||
dotenvy = "0.15.6"
|
||||
generic-array = "0.14.7"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
once_cell = "1.7.2"
|
||||
@@ -131,3 +138,4 @@ tap = "1.0.1"
|
||||
thiserror = "1.0.38"
|
||||
tokio = "1.24.1"
|
||||
url = "2.2"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
@@ -13,6 +13,10 @@ happy: fmt clippy-happy test
|
||||
# on all workspaces.
|
||||
build-release: build-release-main wasm
|
||||
|
||||
# Deprecated
|
||||
# For backwards compatibility
|
||||
clippy-all: clippy
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Define targets for a given workspace
|
||||
# $(1): name
|
||||
@@ -52,11 +56,11 @@ fmt-$(1):
|
||||
cargo fmt --manifest-path $(2)/Cargo.toml --all
|
||||
|
||||
clippy-happy: clippy-happy-$(1)
|
||||
clippy-all: clippy-$(1) clippy-examples-$(1)
|
||||
clippy: clippy-$(1) clippy-examples-$(1)
|
||||
check: check-$(1)
|
||||
cargo-test: test-$(1)
|
||||
cargo-test-expensive: test-expensive-$(1)
|
||||
build: build-$(1) build-$(1)-examples
|
||||
build: build-$(1) build-examples-$(1)
|
||||
build-release-all: build-release-$(1)
|
||||
fmt: fmt-$(1)
|
||||
|
||||
@@ -95,6 +99,7 @@ CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
|
||||
VESTING_CONTRACT=$(CONTRACTS_OUT_DIR)/vesting_contract.wasm
|
||||
MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
|
||||
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
|
||||
|
||||
wasm: wasm-build wasm-opt
|
||||
|
||||
@@ -105,6 +110,7 @@ wasm-opt:
|
||||
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
|
||||
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
|
||||
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
|
||||
wasm-opt --disable-sign-ext -Os $(NAME_SERVICE_CONTRACT) -o $(NAME_SERVICE_CONTRACT)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Misc
|
||||
|
||||
@@ -32,7 +32,7 @@ For Typescript components, please see [ts-packages](./ts-packages).
|
||||
|
||||
### Developer chat
|
||||
|
||||
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
|
||||
You can chat with us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes place in the **#dev** channel. Node operators should be in the **#node-operators** channel.
|
||||
|
||||
### Rewards
|
||||
|
||||
@@ -46,7 +46,7 @@ Node, node operator and delegator rewards are determined according to the princi
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitiveness gets for a Sybil attacker.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
|
||||
@@ -70,7 +70,7 @@ Operator of node `i` is credited with the following amount:
|
||||
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
Delegate with stake `s` recieves:
|
||||
Delegate with stake `s` receives:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
@@ -12,7 +12,9 @@ serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
|
||||
|
||||
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-client-core = { path = "../../common/client-core" }
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod commands;
|
||||
@@ -9,13 +9,14 @@ use commands::*;
|
||||
use error::Result;
|
||||
use log::*;
|
||||
use nym_bin_common::completions::fig_generate;
|
||||
use nym_config::{CRED_DB_FILE_NAME, DATA_DIR};
|
||||
use nym_config::DEFAULT_DATA_DIR;
|
||||
use nym_network_defaults::{setup_env, NymNetworkDetails};
|
||||
use std::process::exit;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use nym_bin_common::logging::setup_logging;
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use nym_validator_client::nyxd::traits::DkgQueryClient;
|
||||
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
|
||||
use nym_validator_client::Config;
|
||||
@@ -71,10 +72,11 @@ async fn main() -> Result<()> {
|
||||
|
||||
match args.command {
|
||||
Command::Run(r) => {
|
||||
let db_path = r
|
||||
.client_home_directory
|
||||
.join(DATA_DIR)
|
||||
.join(CRED_DB_FILE_NAME);
|
||||
// we assume the structure of <home-dir>/data
|
||||
let data_dir = r.client_home_directory.join(DEFAULT_DATA_DIR);
|
||||
let paths = CommonClientPaths::new_default(data_dir);
|
||||
let db_path = paths.credentials_database;
|
||||
|
||||
let shared_storage =
|
||||
nym_credential_storage::initialise_persistent_storage(db_path).await;
|
||||
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.15"
|
||||
version = "1.1.19"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,23 +1,57 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::template::config_template;
|
||||
use nym_client_core::config::ClientCoreConfigTrait;
|
||||
use crate::client::config::persistence::ClientPaths;
|
||||
use crate::client::config::template::CONFIG_TEMPLATE;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
|
||||
use nym_config::{NymConfig, OptionalSet};
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
|
||||
OptionalSet, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use nym_client_core::config::Config as BaseConfig;
|
||||
pub use nym_client_core::config::MISSING_VALUE;
|
||||
pub use nym_client_core::config::Config as BaseClientConfig;
|
||||
pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig};
|
||||
|
||||
pub mod old_config_v1_1_13;
|
||||
pub mod old_config_v1_1_19;
|
||||
mod persistence;
|
||||
mod template;
|
||||
|
||||
const DEFAULT_CLIENTS_DIR: &str = "clients";
|
||||
|
||||
/// Derive default path to clients's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/mixnodes/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_CLIENTS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to client's config file.
|
||||
/// It should get resolved to `$HOME/.nym/clients/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
/// Derive default path to client's data directory where files, such as keys, are stored.
|
||||
/// It should get resolved to `$HOME/.nym/clients/<id>/data`
|
||||
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_CLIENTS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_DATA_DIR)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum SocketType {
|
||||
@@ -26,74 +60,57 @@ pub enum SocketType {
|
||||
}
|
||||
|
||||
impl SocketType {
|
||||
pub fn from_string<S: Into<String>>(val: S) -> Self {
|
||||
let mut upper = val.into();
|
||||
upper.make_ascii_uppercase();
|
||||
match upper.as_ref() {
|
||||
"WEBSOCKET" | "WS" => SocketType::WebSocket,
|
||||
_ => SocketType::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_websocket(&self) -> bool {
|
||||
matches!(self, SocketType::WebSocket)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Config {
|
||||
#[serde(flatten)]
|
||||
base: BaseConfig<Config>,
|
||||
pub base: BaseClientConfig,
|
||||
|
||||
socket: Socket,
|
||||
pub socket: Socket,
|
||||
|
||||
// pub paths: CommonClientPathfinder,
|
||||
pub storage_paths: ClientPaths,
|
||||
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl NymConfig for Config {
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
config_template()
|
||||
}
|
||||
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("clients")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.base.get_nym_root_directory()
|
||||
}
|
||||
|
||||
fn config_directory(&self) -> PathBuf {
|
||||
self.root_directory()
|
||||
.join(self.base.get_id())
|
||||
.join("config")
|
||||
}
|
||||
|
||||
fn data_directory(&self) -> PathBuf {
|
||||
self.root_directory().join(self.base.get_id()).join("data")
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCoreConfigTrait for Config {
|
||||
fn get_gateway_endpoint(&self) -> &nym_client_core::config::GatewayEndpointConfig {
|
||||
self.base.get_gateway_endpoint()
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||
pub fn new<S: AsRef<str>>(id: S) -> Self {
|
||||
Config {
|
||||
base: BaseConfig::new(id),
|
||||
base: BaseClientConfig::new(id.as_ref()),
|
||||
storage_paths: ClientPaths::new_default(default_data_directory(id.as_ref())),
|
||||
logging: Default::default(),
|
||||
socket: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn default_location(&self) -> PathBuf {
|
||||
default_config_filepath(&self.base.client.id)
|
||||
}
|
||||
|
||||
pub fn save_to_default_location(&self) -> io::Result<()> {
|
||||
let config_save_location: PathBuf = self.default_location();
|
||||
save_formatted_config_to_file(self, config_save_location)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> bool {
|
||||
// no other sections have explicit requirements (yet)
|
||||
self.base.validate()
|
||||
@@ -123,39 +140,10 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
// getters
|
||||
pub fn get_config_file_save_location(&self) -> PathBuf {
|
||||
self.config_directory().join(Self::config_file_name())
|
||||
}
|
||||
|
||||
pub fn get_base(&self) -> &BaseConfig<Self> {
|
||||
&self.base
|
||||
}
|
||||
|
||||
pub fn get_base_mut(&mut self) -> &mut BaseConfig<Self> {
|
||||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn get_debug_settings(&self) -> &DebugConfig {
|
||||
self.get_base().get_debug_config()
|
||||
}
|
||||
|
||||
pub fn get_socket_type(&self) -> SocketType {
|
||||
self.socket.socket_type
|
||||
}
|
||||
|
||||
pub fn get_listening_ip(&self) -> IpAddr {
|
||||
self.socket.host
|
||||
}
|
||||
|
||||
pub fn get_listening_port(&self) -> u16 {
|
||||
self.socket.listening_port
|
||||
}
|
||||
|
||||
// poor man's 'builder' method
|
||||
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
{
|
||||
self.base = f(self.base, val);
|
||||
self
|
||||
@@ -165,7 +153,7 @@ impl Config {
|
||||
// (plz, lets refactor it)
|
||||
pub fn with_optional_ext<F, T>(mut self, f: F, val: Option<T>) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
{
|
||||
self.base = self.base.with_optional(f, val);
|
||||
self
|
||||
@@ -173,7 +161,7 @@ impl Config {
|
||||
|
||||
pub fn with_optional_env_ext<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
@@ -189,7 +177,7 @@ impl Config {
|
||||
parser: G,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
G: Fn(&str) -> T,
|
||||
{
|
||||
self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
|
||||
@@ -197,19 +185,21 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
// define_optional_set_inner!(Config, base, BaseClientConfig);
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Socket {
|
||||
socket_type: SocketType,
|
||||
host: IpAddr,
|
||||
listening_port: u16,
|
||||
pub socket_type: SocketType,
|
||||
pub host: IpAddr,
|
||||
pub listening_port: u16,
|
||||
}
|
||||
|
||||
impl Default for Socket {
|
||||
fn default() -> Self {
|
||||
Socket {
|
||||
socket_type: SocketType::WebSocket,
|
||||
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
host: IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,33 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, Socket};
|
||||
use crate::client::config::old_config_v1_1_19::{ConfigV1_1_19, SocketV1_1_19};
|
||||
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
|
||||
use nym_config::NymConfig;
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldConfigV1_1_13 {
|
||||
#[serde(flatten)]
|
||||
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
|
||||
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
|
||||
|
||||
socket: Socket,
|
||||
pub socket: SocketV1_1_19,
|
||||
}
|
||||
|
||||
impl NymConfig for OldConfigV1_1_13 {
|
||||
fn template() -> &'static str {
|
||||
// not intended to be used
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for OldConfigV1_1_13 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("clients")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.base.client.nym_root_directory.clone()
|
||||
}
|
||||
|
||||
fn config_directory(&self) -> PathBuf {
|
||||
self.root_directory()
|
||||
.join(&self.base.client.id)
|
||||
.join("config")
|
||||
}
|
||||
|
||||
fn data_directory(&self) -> PathBuf {
|
||||
self.root_directory()
|
||||
.join(&self.base.client.id)
|
||||
.join("data")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OldConfigV1_1_13> for Config {
|
||||
impl From<OldConfigV1_1_13> for ConfigV1_1_19 {
|
||||
fn from(value: OldConfigV1_1_13) -> Self {
|
||||
Config {
|
||||
ConfigV1_1_19 {
|
||||
base: value.base.into(),
|
||||
socket: value.socket,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::persistence::ClientPaths;
|
||||
use crate::client::config::{Config, Socket, SocketType};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use nym_client_core::config::old_config_v1_1_19::ConfigV1_1_19 as BaseConfigV1_1_19;
|
||||
use nym_client_core::config::{Client, Config as BaseConfig};
|
||||
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum SocketTypeV1_1_19 {
|
||||
WebSocket,
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<SocketTypeV1_1_19> for SocketType {
|
||||
fn from(value: SocketTypeV1_1_19) -> Self {
|
||||
match value {
|
||||
SocketTypeV1_1_19::WebSocket => SocketType::WebSocket,
|
||||
SocketTypeV1_1_19::None => SocketType::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_19 {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseConfigV1_1_19<ConfigV1_1_19>,
|
||||
|
||||
pub socket: SocketV1_1_19,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_19> for Config {
|
||||
fn from(value: ConfigV1_1_19) -> Self {
|
||||
Config {
|
||||
base: BaseConfig {
|
||||
client: Client {
|
||||
version: value.base.client.version,
|
||||
id: value.base.client.id,
|
||||
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
|
||||
nyxd_urls: value.base.client.nyxd_urls,
|
||||
nym_api_urls: value.base.client.nym_api_urls,
|
||||
gateway_endpoint: value.base.client.gateway_endpoint.into(),
|
||||
},
|
||||
debug: value.base.debug.into(),
|
||||
},
|
||||
socket: value.socket.into(),
|
||||
storage_paths: ClientPaths {
|
||||
common_paths: CommonClientPaths {
|
||||
keys: ClientKeysPaths {
|
||||
private_identity_key_file: value.base.client.private_identity_key_file,
|
||||
public_identity_key_file: value.base.client.public_identity_key_file,
|
||||
private_encryption_key_file: value.base.client.private_encryption_key_file,
|
||||
public_encryption_key_file: value.base.client.public_encryption_key_file,
|
||||
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
|
||||
ack_key_file: value.base.client.ack_key_file,
|
||||
},
|
||||
credentials_database: value.base.client.database_path,
|
||||
reply_surb_database: value.base.client.reply_surb_database_path,
|
||||
},
|
||||
},
|
||||
logging: LoggingSettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_19 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("clients")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct SocketV1_1_19 {
|
||||
socket_type: SocketTypeV1_1_19,
|
||||
host: IpAddr,
|
||||
listening_port: u16,
|
||||
}
|
||||
|
||||
impl From<SocketV1_1_19> for Socket {
|
||||
fn from(value: SocketV1_1_19) -> Self {
|
||||
Socket {
|
||||
socket_type: value.socket_type.into(),
|
||||
host: value.host,
|
||||
listening_port: value.listening_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SocketV1_1_19 {
|
||||
fn default() -> Self {
|
||||
SocketV1_1_19 {
|
||||
socket_type: SocketTypeV1_1_19::WebSocket,
|
||||
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct ClientPaths {
|
||||
#[serde(flatten)]
|
||||
pub common_paths: CommonClientPaths,
|
||||
}
|
||||
|
||||
impl ClientPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
ClientPaths {
|
||||
common_paths: CommonClientPaths::new_default(base_data_directory),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) fn config_template() -> &'static str {
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs.
|
||||
r#"
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs.
|
||||
pub(crate) const CONFIG_TEMPLATE: &str = r#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
@@ -37,39 +36,35 @@ nym_api_urls = [
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
[storage_paths]
|
||||
|
||||
# Path to file containing private identity key.
|
||||
private_identity_key_file = '{{ client.private_identity_key_file }}'
|
||||
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
|
||||
|
||||
# Path to file containing public identity key.
|
||||
public_identity_key_file = '{{ client.public_identity_key_file }}'
|
||||
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
|
||||
|
||||
# Path to file containing private encryption key.
|
||||
private_encryption_key_file = '{{ client.private_encryption_key_file }}'
|
||||
keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
|
||||
|
||||
# Path to file containing public encryption key.
|
||||
public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
|
||||
# Path to the database containing bandwidth credentials
|
||||
database_path = '{{ client.database_path }}'
|
||||
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database_path = '{{ client.reply_surb_database_path }}'
|
||||
|
||||
##### additional client config options #####
|
||||
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
# communication with particular gateway.
|
||||
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
|
||||
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
|
||||
|
||||
# Path to file containing key used for encrypting and decrypting the content of an
|
||||
# acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
ack_key_file = '{{ client.ack_key_file }}'
|
||||
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
|
||||
|
||||
##### advanced configuration options #####
|
||||
# Path to the database containing bandwidth credentials
|
||||
credentials_database = '{{ storage_paths.credentials_database }}'
|
||||
|
||||
# Absolute path to the home Nym Clients directory.
|
||||
nym_root_directory = '{{ client.nym_root_directory }}'
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
|
||||
|
||||
# DEPRECATED
|
||||
[client.gateway_endpoint]
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
|
||||
@@ -120,5 +115,5 @@ average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
|
||||
[debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
"#
|
||||
}
|
||||
|
||||
"#;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
@@ -7,84 +7,53 @@ use crate::websocket;
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core::client::base_client::non_wasm_helpers::create_bandwidth_controller;
|
||||
use nym_client_core::client::base_client::storage::OnDiskPersistent;
|
||||
use nym_client_core::client::base_client::{
|
||||
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
|
||||
};
|
||||
use nym_client_core::client::inbound_messages::InputMessage;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::client::received_buffer::{
|
||||
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
|
||||
};
|
||||
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskManager;
|
||||
use nym_validator_client::nyxd::QueryNyxdClient;
|
||||
use nym_validator_client::Client;
|
||||
use std::error::Error;
|
||||
use tokio::sync::watch::error::SendError;
|
||||
|
||||
pub use nym_client_core::client::key_manager::KeyManager;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
pub use nym_sphinx::addressing::clients::Recipient;
|
||||
pub use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_validator_client::Client;
|
||||
|
||||
pub mod config;
|
||||
|
||||
type NativeClientBuilder<'a> = BaseClientBuilder<'a, Client<QueryNyxdClient>, OnDiskPersistent>;
|
||||
|
||||
pub struct SocketClient {
|
||||
/// Client configuration options, including, among other things, packet sending rates,
|
||||
/// key filepaths, etc.
|
||||
config: Config,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
}
|
||||
|
||||
impl SocketClient {
|
||||
pub fn new(config: Config) -> Self {
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
|
||||
|
||||
SocketClient {
|
||||
config,
|
||||
key_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_keys(config: Config, key_manager: KeyManager) -> Self {
|
||||
SocketClient {
|
||||
config,
|
||||
key_manager,
|
||||
}
|
||||
SocketClient { config }
|
||||
}
|
||||
|
||||
async fn create_bandwidth_controller(
|
||||
config: &Config,
|
||||
) -> BandwidthController<Client<QueryNyxdClient>, PersistentStorage> {
|
||||
let details = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let mut client_config =
|
||||
nym_validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
let nyxd_url = config
|
||||
.get_base()
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nyxd validator endpoint provided");
|
||||
let api_url = config
|
||||
.get_base()
|
||||
.get_nym_api_endpoints()
|
||||
.pop()
|
||||
.expect("No validator api endpoint provided");
|
||||
// overwrite env configuration with config URLs
|
||||
client_config = client_config.with_urls(nyxd_url, api_url);
|
||||
let client = nym_validator_client::Client::new_query(client_config)
|
||||
.expect("Could not construct query client");
|
||||
BandwidthController::new(
|
||||
nym_credential_storage::initialise_persistent_storage(
|
||||
config.get_base().get_database_path(),
|
||||
)
|
||||
.await,
|
||||
client,
|
||||
let storage = nym_credential_storage::initialise_persistent_storage(
|
||||
&config.storage_paths.common_paths.credentials_database,
|
||||
)
|
||||
.await;
|
||||
|
||||
create_bandwidth_controller(&config.base, storage)
|
||||
}
|
||||
|
||||
fn start_websocket_listener(
|
||||
@@ -94,6 +63,7 @@ impl SocketClient {
|
||||
client_state: ClientState,
|
||||
self_address: &Recipient,
|
||||
shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
|
||||
@@ -119,9 +89,10 @@ impl SocketClient {
|
||||
self_address,
|
||||
shared_lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
Some(packet_type),
|
||||
);
|
||||
|
||||
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
|
||||
websocket::Listener::new(config.socket.host, config.socket.listening_port)
|
||||
.start(websocket_handler, shutdown);
|
||||
}
|
||||
|
||||
@@ -134,31 +105,42 @@ impl SocketClient {
|
||||
res
|
||||
}
|
||||
|
||||
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
|
||||
if !self.config.get_socket_type().is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
fn key_store(&self) -> OnDiskKeys {
|
||||
OnDiskKeys::new(self.config.storage_paths.common_paths.keys.clone())
|
||||
}
|
||||
|
||||
// TODO: see if this could also be shared with socks5 client / nym-sdk maybe
|
||||
async fn create_base_client_builder(&self) -> Result<NativeClientBuilder, ClientError> {
|
||||
// don't create bandwidth controller if credentials are disabled
|
||||
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
|
||||
let bandwidth_controller = if self.config.base.client.disabled_credentials_mode {
|
||||
None
|
||||
} else {
|
||||
Some(Self::create_bandwidth_controller(&self.config).await)
|
||||
};
|
||||
|
||||
let base_builder = BaseClientBuilder::new_from_base_config(
|
||||
self.config.get_base(),
|
||||
self.key_manager,
|
||||
let base_client = BaseClientBuilder::new_from_base_config(
|
||||
&self.config.base,
|
||||
self.key_store(),
|
||||
bandwidth_controller,
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
Some(self.config.get_base().get_reply_surb_database_path()),
|
||||
self.config.get_debug_settings(),
|
||||
&self.config.storage_paths.common_paths.reply_surb_database,
|
||||
&self.config.base.debug.reply_surbs,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let self_address = base_builder.as_mix_recipient();
|
||||
Ok(base_client)
|
||||
}
|
||||
|
||||
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
|
||||
if !self.config.socket.socket_type.is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
|
||||
let base_builder = self.create_base_client_builder().await?;
|
||||
let packet_type = self.config.base.debug.traffic.packet_type;
|
||||
let mut started_client = base_builder.start_base().await?;
|
||||
let self_address = started_client.address;
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
let client_state = started_client.client_state;
|
||||
@@ -170,40 +152,24 @@ impl SocketClient {
|
||||
client_state,
|
||||
&self_address,
|
||||
started_client.task_manager.subscribe(),
|
||||
packet_type,
|
||||
);
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self_address);
|
||||
info!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(started_client.task_manager)
|
||||
}
|
||||
|
||||
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
|
||||
if self.config.get_socket_type().is_websocket() {
|
||||
if self.config.socket.socket_type.is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
|
||||
// don't create bandwidth controller if credentials are disabled
|
||||
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
|
||||
None
|
||||
} else {
|
||||
Some(Self::create_bandwidth_controller(&self.config).await)
|
||||
};
|
||||
|
||||
let base_client = BaseClientBuilder::new_from_base_config(
|
||||
self.config.get_base(),
|
||||
self.key_manager,
|
||||
bandwidth_controller,
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
Some(self.config.get_base().get_reply_surb_database_path()),
|
||||
self.config.get_debug_settings(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let address = base_client.as_mix_recipient();
|
||||
|
||||
let mut started_client = base_client.start_base().await?;
|
||||
let base_builder = self.create_base_client_builder().await?;
|
||||
let packet_type = self.config.base.debug.traffic.packet_type;
|
||||
let mut started_client = base_builder.start_base().await?;
|
||||
let address = started_client.address;
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
|
||||
@@ -224,6 +190,7 @@ impl SocketClient {
|
||||
reconstructed_receiver,
|
||||
address,
|
||||
shutdown_notifier: started_client.task_manager,
|
||||
packet_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -237,6 +204,7 @@ pub struct DirectClient {
|
||||
|
||||
// we need to keep reference to this guy otherwise things will start dropping
|
||||
shutdown_notifier: TaskManager,
|
||||
packet_type: PacketType,
|
||||
}
|
||||
|
||||
impl DirectClient {
|
||||
@@ -257,7 +225,7 @@ impl DirectClient {
|
||||
/// well enough in local tests)
|
||||
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane);
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
@@ -276,7 +244,13 @@ impl DirectClient {
|
||||
reply_surbs: u32,
|
||||
) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
|
||||
let input_msg = InputMessage::new_anonymous(
|
||||
recipient,
|
||||
message,
|
||||
reply_surbs,
|
||||
lane,
|
||||
Some(self.packet_type),
|
||||
);
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
@@ -290,7 +264,8 @@ impl DirectClient {
|
||||
/// well enough in local tests)
|
||||
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
|
||||
let input_msg =
|
||||
InputMessage::new_reply(recipient_tag, message, lane, Some(self.packet_type));
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::client::config::{
|
||||
default_config_directory, default_config_filepath, default_data_directory,
|
||||
};
|
||||
use crate::commands::try_upgrade_config;
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
@@ -9,13 +12,13 @@ use crate::{
|
||||
};
|
||||
use clap::Args;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_config::NymConfig;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use std::{fs, io};
|
||||
use tap::TapFallible;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -97,15 +100,15 @@ impl From<Init> for OverrideConfig {
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: nym_client_core::init::InitResults,
|
||||
client_listening_port: String,
|
||||
client_listening_port: u16,
|
||||
client_address: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient) -> Self {
|
||||
Self {
|
||||
client_core: nym_client_core::init::InitResults::new(config.get_base(), address),
|
||||
client_listening_port: config.get_listening_port().to_string(),
|
||||
client_core: nym_client_core::init::InitResults::new(&config.base, address),
|
||||
client_listening_port: config.socket.listening_port,
|
||||
client_address: address.to_string(),
|
||||
}
|
||||
}
|
||||
@@ -119,18 +122,26 @@ impl Display for InitResults {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_paths(id: &str) -> io::Result<()> {
|
||||
fs::create_dir_all(default_data_directory(id))?;
|
||||
fs::create_dir_all(default_config_directory(id))
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
eprintln!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
|
||||
let already_init = Config::default_config_file_path(id).exists();
|
||||
if already_init {
|
||||
let already_init = if default_config_filepath(id).exists() {
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
try_upgrade_config(id)?;
|
||||
eprintln!("Client \"{id}\" was already initialised before");
|
||||
}
|
||||
true
|
||||
} else {
|
||||
init_paths(id)?;
|
||||
false
|
||||
};
|
||||
|
||||
// Usually you only register with the gateway on the first init, however you can force
|
||||
// re-registering if wanted.
|
||||
@@ -152,39 +163,37 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
|
||||
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<_>(
|
||||
&key_store,
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
&config.base,
|
||||
args.latency_based_selection,
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
config.get_base_mut().set_gateway_endpoint(gateway);
|
||||
config.base.set_gateway_endpoint(gateway);
|
||||
|
||||
config.save_to_file(None).tap_err(|_| {
|
||||
let config_save_location = config.default_location();
|
||||
config.save_to_default_location().tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
eprintln!(
|
||||
"Saved configuration file to {}",
|
||||
config_save_location.display()
|
||||
);
|
||||
|
||||
print_saved_config(&config);
|
||||
let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys(
|
||||
&config.storage_paths.common_paths.keys,
|
||||
&config.base.client.gateway_endpoint,
|
||||
)?;
|
||||
|
||||
eprintln!("Client configuration completed.\n");
|
||||
|
||||
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{}", args.output.format(&init_results));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config) {
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
eprintln!("Saved configuration file to {config_save_location:?}");
|
||||
eprintln!("Using gateway: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner());
|
||||
log::debug!(
|
||||
"Gateway listener: {}",
|
||||
config.get_base().get_gateway_listener()
|
||||
);
|
||||
eprintln!("Client configuration completed.\n");
|
||||
}
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
|
||||
use crate::client::config::{BaseConfig, Config};
|
||||
use crate::client::config::old_config_v1_1_19::ConfigV1_1_19;
|
||||
use crate::client::config::{BaseClientConfig, Config};
|
||||
use crate::error::ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use nym_bin_common::build_information::BinaryBuildInformation;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_config::{NymConfig, OptionalSet};
|
||||
use nym_config::OptionalSet;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
@@ -82,40 +84,92 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
|
||||
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
config
|
||||
.with_optional(Config::with_disabled_socket, args.disable_socket)
|
||||
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
|
||||
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_base(
|
||||
BaseClientConfig::with_high_default_traffic_volume,
|
||||
args.fastmode,
|
||||
)
|
||||
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_optional(Config::with_port, args.port)
|
||||
.with_optional(Config::with_host, args.host)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nym_apis,
|
||||
BaseClientConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
nym_network_defaults::var_names::NYM_API,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nyxd,
|
||||
BaseClientConfig::with_custom_nyxd,
|
||||
args.nyxd_urls,
|
||||
nym_network_defaults::var_names::NYXD,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional_ext(
|
||||
BaseConfig::with_disabled_credentials,
|
||||
BaseClientConfig::with_disabled_credentials,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
|
||||
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
|
||||
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
|
||||
let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.13 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_19 = old_config.into();
|
||||
let updated: Config = updated_step1.into();
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_19_config(id: &str) -> Result<bool, ClientError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.19 (which is incompatible with the current one, i.e. +1.1.20)
|
||||
let Ok(old_config) = ConfigV1_1_19::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.19 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated.save_to_file(None)
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
|
||||
let upgraded = try_upgrade_v1_1_13_config(id)?;
|
||||
if !upgraded {
|
||||
try_upgrade_v1_1_19_config(id)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_load_current_config(id: &str) -> Result<Config, ClientError> {
|
||||
try_upgrade_config(id)?;
|
||||
|
||||
let config = match Config::read_from_default_path(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})");
|
||||
return Err(ClientError::FailedToLoadConfig(id.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
if !config.validate() {
|
||||
return Err(ClientError::ConfigValidationFailure);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::{
|
||||
client::{config::Config, SocketClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
@@ -13,8 +10,9 @@ use crate::{
|
||||
use clap::Args;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Run {
|
||||
@@ -82,7 +80,7 @@ impl From<Run> for OverrideConfig {
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = cfg.get_base().get_version();
|
||||
let config_version = &cfg.base.client.version;
|
||||
if binary_version == config_version {
|
||||
true
|
||||
} else {
|
||||
@@ -98,30 +96,10 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let id = &args.id;
|
||||
eprintln!("Starting client {}...", args.id);
|
||||
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
|
||||
let mut config = match Config::load_from_file(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
|
||||
return Err(Box::new(ClientError::FailedToLoadConfig(id.to_string())));
|
||||
}
|
||||
};
|
||||
|
||||
if !config.validate() {
|
||||
return Err(Box::new(ClientError::ConfigValidationFailure));
|
||||
}
|
||||
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
if config.get_base_mut().set_empty_fields_to_defaults() {
|
||||
warn!("some of the core config options were left unset. the default values are going to get used instead.");
|
||||
}
|
||||
let mut config = try_load_current_config(&args.id)?;
|
||||
config = override_config(config, OverrideConfig::from(args.clone()));
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
|
||||
@@ -1,42 +1,14 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, MISSING_VALUE};
|
||||
|
||||
use nym_bin_common::version_checker::Version;
|
||||
use nym_config::NymConfig;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::try_load_current_config;
|
||||
use clap::Args;
|
||||
use std::fmt::Display;
|
||||
use nym_bin_common::version_checker::Version;
|
||||
use std::process;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
|
||||
print_failed_upgrade(from_version, to_version);
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
println!("\n==================\nTrying to upgrade client from {from} to {to} ...");
|
||||
}
|
||||
|
||||
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
eprintln!("Upgrade from {from} to {to} failed!\n==================\n");
|
||||
}
|
||||
|
||||
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
println!("Upgrade from {from} to {to} was successful!\n==================\n");
|
||||
}
|
||||
|
||||
fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! {
|
||||
eprintln!(
|
||||
"Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!"
|
||||
);
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! {
|
||||
eprintln!("Cannot perform upgrade from {config_version} to {current_version}. Please let the developers know about this issue if you expected it to work!");
|
||||
fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! {
|
||||
eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet");
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
@@ -48,7 +20,7 @@ pub(crate) struct Upgrade {
|
||||
}
|
||||
|
||||
fn parse_config_version(config: &Config) -> Version {
|
||||
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| {
|
||||
let version = Version::parse(&config.base.client.version).unwrap_or_else(|err| {
|
||||
eprintln!("failed to parse client version! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
@@ -77,53 +49,14 @@ fn parse_package_version() -> Version {
|
||||
version
|
||||
}
|
||||
|
||||
fn minor_0_12_upgrade(
|
||||
mut config: Config,
|
||||
_matches: &Upgrade,
|
||||
config_version: &Version,
|
||||
package_version: &Version,
|
||||
) -> Config {
|
||||
let to_version = if package_version.major == 0 && package_version.minor == 12 {
|
||||
package_version.clone()
|
||||
} else {
|
||||
Version::new(0, 12, 0)
|
||||
};
|
||||
|
||||
print_start_upgrade(config_version, &to_version);
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_version(to_version.to_string().as_ref());
|
||||
|
||||
config.save_to_file(None).unwrap_or_else(|err| {
|
||||
eprintln!("failed to overwrite config file! - {err}");
|
||||
print_failed_upgrade(config_version, &to_version);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
print_successful_upgrade(config_version, to_version);
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
|
||||
loop {
|
||||
let config_version = parse_config_version(&config);
|
||||
|
||||
if &config_version == package_version {
|
||||
println!("You're using the most recent version!");
|
||||
return;
|
||||
}
|
||||
|
||||
config = match config_version.major {
|
||||
0 => match config_version.minor {
|
||||
9 | 10 => outdated_upgrade(&config_version, package_version),
|
||||
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
},
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
}
|
||||
fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) {
|
||||
let config_version = parse_config_version(&config);
|
||||
if &config_version == package_version {
|
||||
println!("You're using the most recent version!");
|
||||
return;
|
||||
}
|
||||
|
||||
unimplemented_upgrade(package_version, &config_version)
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &Upgrade) {
|
||||
@@ -131,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) {
|
||||
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
|
||||
let existing_config = try_load_current_config(id).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
if existing_config.get_base().get_version() == MISSING_VALUE {
|
||||
if existing_config.base.client.version.is_empty() {
|
||||
eprintln!("the existing configuration file does not seem to contain version number.");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// here be upgrade path to 0.9.X and beyond based on version number from config
|
||||
do_upgrade(existing_config, args, &package_version)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use nym_client_core::client::{
|
||||
use nym_client_websocket_requests::{requests::ClientRequest, responses::ServerResponse};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
@@ -41,6 +42,7 @@ pub(crate) struct HandlerBuilder {
|
||||
self_full_address: Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
}
|
||||
|
||||
impl HandlerBuilder {
|
||||
@@ -51,6 +53,7 @@ impl HandlerBuilder {
|
||||
self_full_address: &Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
) -> Self {
|
||||
Self {
|
||||
msg_input,
|
||||
@@ -59,6 +62,7 @@ impl HandlerBuilder {
|
||||
self_full_address: *self_full_address,
|
||||
lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +77,7 @@ impl HandlerBuilder {
|
||||
received_response_type: Default::default(),
|
||||
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
||||
reply_controller_sender: self.reply_controller_sender.clone(),
|
||||
packet_type: self.packet_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +91,7 @@ pub(crate) struct Handler {
|
||||
received_response_type: ReceivedResponseType,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
@@ -160,7 +166,7 @@ impl Handler {
|
||||
});
|
||||
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane);
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
@@ -191,7 +197,8 @@ impl Handler {
|
||||
TransmissionLane::ConnectionId(id)
|
||||
});
|
||||
|
||||
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
|
||||
let input_msg =
|
||||
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
@@ -218,7 +225,7 @@ impl Handler {
|
||||
TransmissionLane::ConnectionId(id)
|
||||
});
|
||||
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.15"
|
||||
version = "1.1.19"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::commands::try_upgrade_config;
|
||||
use crate::config::{
|
||||
default_config_directory, default_config_filepath, default_data_directory, Config,
|
||||
};
|
||||
use crate::{
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
use clap::Args;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_config::NymConfig;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_socks5_client_core::config::Config;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use std::{fs, io};
|
||||
use tap::TapFallible;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -91,6 +93,7 @@ impl From<Init> for OverrideConfig {
|
||||
no_cover: init_config.no_cover,
|
||||
nyxd_urls: init_config.nyxd_urls,
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
outfox: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,15 +102,15 @@ impl From<Init> for OverrideConfig {
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: nym_client_core::init::InitResults,
|
||||
socks5_listening_port: String,
|
||||
socks5_listening_port: u16,
|
||||
client_address: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient) -> Self {
|
||||
Self {
|
||||
client_core: nym_client_core::init::InitResults::new(config.get_base(), address),
|
||||
socks5_listening_port: config.get_socks5().get_listening_port().to_string(),
|
||||
client_core: nym_client_core::init::InitResults::new(&config.core.base, address),
|
||||
socks5_listening_port: config.core.socks5.listening_port,
|
||||
client_address: address.to_string(),
|
||||
}
|
||||
}
|
||||
@@ -121,19 +124,27 @@ impl Display for InitResults {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_paths(id: &str) -> io::Result<()> {
|
||||
fs::create_dir_all(default_data_directory(id))?;
|
||||
fs::create_dir_all(default_config_directory(id))
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
eprintln!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
let provider_address = &args.provider;
|
||||
|
||||
let already_init = Config::default_config_file_path(id).exists();
|
||||
if already_init {
|
||||
let already_init = if default_config_filepath(id).exists() {
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
try_upgrade_config(id)?;
|
||||
eprintln!("SOCKS5 client \"{id}\" was already initialised before");
|
||||
}
|
||||
true
|
||||
} else {
|
||||
init_paths(id)?;
|
||||
false
|
||||
};
|
||||
|
||||
// Usually you only register with the gateway on the first init, however you can force
|
||||
// re-registering if wanted.
|
||||
@@ -158,41 +169,36 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
|
||||
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<_>(
|
||||
&key_store,
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
&config.core.base,
|
||||
args.latency_based_selection,
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
config.get_base_mut().set_gateway_endpoint(gateway);
|
||||
config.core.base.set_gateway_endpoint(gateway);
|
||||
|
||||
// TODO: ask the service provider we specified for its interface version and set it in the config
|
||||
|
||||
config.save_to_file(None).tap_err(|_| {
|
||||
let config_save_location = config.default_location();
|
||||
config.save_to_default_location().tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
eprintln!(
|
||||
"Saved configuration file to {}",
|
||||
config_save_location.display()
|
||||
);
|
||||
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys(
|
||||
&config.storage_paths.common_paths.keys,
|
||||
&config.core.base.client.gateway_endpoint,
|
||||
)?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{}", args.output.format(&init_results));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config) {
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
eprintln!("Saved configuration file to {:?}", config_save_location);
|
||||
eprintln!("Using gateway: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner());
|
||||
log::debug!(
|
||||
"Gateway listener: {}",
|
||||
config.get_base().get_gateway_listener()
|
||||
);
|
||||
eprintln!("Client configuration completed.\n");
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
|
||||
use crate::config::old_config_v1_1_19::ConfigV1_1_19;
|
||||
use crate::config::{BaseClientConfig, Config};
|
||||
use crate::error::Socks5ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use nym_bin_common::build_information::BinaryBuildInformation;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_config::{NymConfig, OptionalSet};
|
||||
use nym_socks5_client_core::config::old_config_v1_1_13::OldConfigV1_1_13;
|
||||
use nym_socks5_client_core::config::{BaseConfig, Config};
|
||||
use nym_config::OptionalSet;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod init;
|
||||
@@ -64,6 +67,7 @@ pub(crate) struct OverrideConfig {
|
||||
no_cover: bool,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
outfox: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
@@ -80,41 +84,99 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
let packet_type = if args.outfox {
|
||||
PacketType::Outfox
|
||||
} else {
|
||||
PacketType::Mix
|
||||
};
|
||||
config
|
||||
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
|
||||
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_base(
|
||||
BaseClientConfig::with_high_default_traffic_volume,
|
||||
args.fastmode,
|
||||
)
|
||||
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_base(BaseClientConfig::with_packet_type, packet_type)
|
||||
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
|
||||
.with_optional(Config::with_port, args.port)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nym_apis,
|
||||
.with_optional_base_custom_env(
|
||||
BaseClientConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
nym_network_defaults::var_names::NYM_API,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nyxd,
|
||||
.with_optional_base_custom_env(
|
||||
BaseClientConfig::with_custom_nyxd,
|
||||
args.nyxd_urls,
|
||||
nym_network_defaults::var_names::NYXD,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional_ext(
|
||||
BaseConfig::with_disabled_credentials,
|
||||
.with_optional_base(
|
||||
BaseClientConfig::with_disabled_credentials,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
|
||||
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
|
||||
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
|
||||
let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.13 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_19 = old_config.into();
|
||||
let updated: Config = updated_step1.into();
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_19_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.19 (which is incompatible with the current one, i.e. +1.1.20)
|
||||
let Ok(old_config) = ConfigV1_1_19::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.19 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated.save_to_file(None)
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
|
||||
let upgraded = try_upgrade_v1_1_13_config(id)?;
|
||||
if !upgraded {
|
||||
try_upgrade_v1_1_19_config(id)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_load_current_config(id: &str) -> Result<Config, Socks5ClientError> {
|
||||
try_upgrade_config(id)?;
|
||||
|
||||
let config = match Config::read_from_default_path(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})");
|
||||
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
if !config.validate() {
|
||||
return Err(Socks5ClientError::ConfigValidationFailure);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::config::Config;
|
||||
use crate::{
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
@@ -9,9 +10,9 @@ use crate::{
|
||||
use clap::Args;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_config::NymConfig;
|
||||
use nym_client_core::client::base_client::storage::OnDiskPersistent;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_socks5_client_core::{config::Config, NymClient};
|
||||
use nym_socks5_client_core::NymClient;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -20,10 +21,6 @@ pub(crate) struct Run {
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
/// Custom path to the nym-mixnet-client configuration file
|
||||
#[clap(long)]
|
||||
config: Option<String>,
|
||||
|
||||
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
/// While this is going to hide its actual address information, it will make the actual communication
|
||||
/// slower and consume nearly double the bandwidth as it will require sending reply SURBs.
|
||||
@@ -67,6 +64,9 @@ pub(crate) struct Run {
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
#[clap(long, hide = true, action)]
|
||||
outfox: bool,
|
||||
}
|
||||
|
||||
impl From<Run> for OverrideConfig {
|
||||
@@ -79,6 +79,7 @@ impl From<Run> for OverrideConfig {
|
||||
no_cover: run_config.no_cover,
|
||||
nyxd_urls: run_config.nyxd_urls,
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
outfox: run_config.outfox,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,13 +88,12 @@ impl From<Run> for OverrideConfig {
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = cfg.get_base().get_version();
|
||||
let config_version = &cfg.core.base.client.version;
|
||||
if binary_version == config_version {
|
||||
true
|
||||
} else {
|
||||
warn!(
|
||||
"The mixnode binary has different version than what is specified in config file! {} and {}",
|
||||
binary_version, config_version
|
||||
"The socks5-client binary has different version than what is specified in config file! {binary_version} and {config_version}",
|
||||
);
|
||||
if is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
@@ -106,37 +106,18 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let id = &args.id;
|
||||
eprintln!("Starting client {}...", args.id);
|
||||
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
|
||||
let mut config = match Config::load_from_file(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
|
||||
return Err(Box::new(Socks5ClientError::FailedToLoadConfig(
|
||||
id.to_string(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if !config.validate() {
|
||||
return Err(Box::new(Socks5ClientError::ConfigValidationFailure));
|
||||
}
|
||||
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
if config.get_base_mut().set_empty_fields_to_defaults() {
|
||||
warn!("some of the core config options were left unset. the default values are going to get used instead.");
|
||||
}
|
||||
let mut config = try_load_current_config(&args.id)?;
|
||||
config = override_config(config, OverrideConfig::from(args.clone()));
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await
|
||||
let storage =
|
||||
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
|
||||
.await?;
|
||||
NymClient::new(config.core, storage).run_forever().await
|
||||
}
|
||||
|
||||
@@ -1,50 +1,14 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_bin_common::version_checker::Version;
|
||||
use nym_config::NymConfig;
|
||||
use nym_socks5_client_core::config::{Config, MISSING_VALUE};
|
||||
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::config::Config;
|
||||
use clap::Args;
|
||||
use std::{fmt::Display, process};
|
||||
use nym_bin_common::version_checker::Version;
|
||||
use std::process;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
|
||||
print_failed_upgrade(from_version, to_version);
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
println!(
|
||||
"\n==================\nTrying to upgrade client from {} to {} ...",
|
||||
from, to
|
||||
);
|
||||
}
|
||||
|
||||
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
eprintln!(
|
||||
"Upgrade from {} to {} failed!\n==================\n",
|
||||
from, to
|
||||
);
|
||||
}
|
||||
|
||||
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
println!(
|
||||
"Upgrade from {} to {} was successful!\n==================\n",
|
||||
from, to
|
||||
);
|
||||
}
|
||||
|
||||
fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! {
|
||||
eprintln!(
|
||||
"Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!",
|
||||
config_version, package_version
|
||||
);
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! {
|
||||
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version);
|
||||
fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! {
|
||||
eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet");
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
@@ -56,15 +20,14 @@ pub(crate) struct Upgrade {
|
||||
}
|
||||
|
||||
fn parse_config_version(config: &Config) -> Version {
|
||||
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| {
|
||||
let version = Version::parse(&config.core.base.client.version).unwrap_or_else(|err| {
|
||||
eprintln!("failed to parse client version! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
if version.is_prerelease() || !version.build.is_empty() {
|
||||
eprintln!(
|
||||
"Trying to upgrade from a non-released version {}. This is not supported!",
|
||||
version
|
||||
"Trying to upgrade from a non-released version {version}. This is not supported!"
|
||||
);
|
||||
process::exit(1)
|
||||
}
|
||||
@@ -79,63 +42,21 @@ fn parse_package_version() -> Version {
|
||||
// however, we are not using them ourselves at the moment and hence it should be fine.
|
||||
// if we change our mind, we could easily tweak this code
|
||||
if version.is_prerelease() || !version.build.is_empty() {
|
||||
eprintln!(
|
||||
"Trying to upgrade to a non-released version {}. This is not supported!",
|
||||
version
|
||||
);
|
||||
eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!");
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
version
|
||||
}
|
||||
|
||||
fn minor_0_12_upgrade(
|
||||
mut config: Config,
|
||||
_args: &Upgrade,
|
||||
config_version: &Version,
|
||||
package_version: &Version,
|
||||
) -> Config {
|
||||
let to_version = if package_version.major == 0 && package_version.minor == 12 {
|
||||
package_version.clone()
|
||||
} else {
|
||||
Version::new(0, 12, 0)
|
||||
};
|
||||
|
||||
print_start_upgrade(config_version, &to_version);
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_version(to_version.to_string().as_ref());
|
||||
|
||||
config.save_to_file(None).unwrap_or_else(|err| {
|
||||
eprintln!("failed to overwrite config file! - {err}");
|
||||
print_failed_upgrade(config_version, &to_version);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
print_successful_upgrade(config_version, to_version);
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
|
||||
loop {
|
||||
let config_version = parse_config_version(&config);
|
||||
|
||||
if &config_version == package_version {
|
||||
println!("You're using the most recent version!");
|
||||
return;
|
||||
}
|
||||
|
||||
config = match config_version.major {
|
||||
0 => match config_version.minor {
|
||||
9 | 10 => outdated_upgrade(&config_version, package_version),
|
||||
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
},
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
}
|
||||
fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) {
|
||||
let config_version = parse_config_version(&config);
|
||||
if &config_version == package_version {
|
||||
println!("You're using the most recent version!");
|
||||
return;
|
||||
}
|
||||
|
||||
unimplemented_upgrade(package_version, &config_version)
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &Upgrade) {
|
||||
@@ -143,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) {
|
||||
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
|
||||
let existing_config = try_load_current_config(id).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
if existing_config.get_base().get_version() == MISSING_VALUE {
|
||||
if existing_config.core.base.client.version.is_empty() {
|
||||
eprintln!("the existing configuration file does not seem to contain version number.");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// here be upgrade path to 0.9.X and beyond based on version number from config
|
||||
do_upgrade(existing_config, args, &package_version)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::SocksClientPaths;
|
||||
use crate::config::template::CONFIG_TEMPLATE;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
|
||||
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use nym_client_core::config::Config as BaseClientConfig;
|
||||
pub use nym_socks5_client_core::config::Config as CoreConfig;
|
||||
|
||||
pub mod old_config_v1_1_13;
|
||||
pub mod old_config_v1_1_19;
|
||||
mod persistence;
|
||||
mod template;
|
||||
|
||||
const DEFAULT_SOCKS5_CLIENTS_DIR: &str = "socks5-clients";
|
||||
|
||||
/// Derive default path to clients's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_SOCKS5_CLIENTS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to client's config file.
|
||||
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
/// Derive default path to client's data directory where files, such as keys, are stored.
|
||||
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/data`
|
||||
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_SOCKS5_CLIENTS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_DATA_DIR)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
pub core: CoreConfig,
|
||||
|
||||
pub storage_paths: SocksClientPaths,
|
||||
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: AsRef<str>>(id: S, provider_mix_address: S) -> Self {
|
||||
Config {
|
||||
core: CoreConfig::new(id.as_ref(), provider_mix_address.as_ref()),
|
||||
storage_paths: SocksClientPaths::new_default(default_data_directory(id.as_ref())),
|
||||
logging: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn default_location(&self) -> PathBuf {
|
||||
default_config_filepath(&self.core.base.client.id)
|
||||
}
|
||||
|
||||
pub fn save_to_default_location(&self) -> io::Result<()> {
|
||||
let config_save_location: PathBuf = self.default_location();
|
||||
save_formatted_config_to_file(self, config_save_location)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> bool {
|
||||
// no other sections have explicit requirements (yet)
|
||||
self.core.validate()
|
||||
}
|
||||
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.core.socks5.listening_port = port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self {
|
||||
self.core.socks5.send_anonymously = anonymous_replies;
|
||||
self
|
||||
}
|
||||
|
||||
// poor man's 'builder' method
|
||||
|
||||
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
{
|
||||
self.core = self.core.with_base(f, val);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optional_base<F, T>(mut self, f: F, val: Option<T>) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
{
|
||||
self.core = self.core.with_optional_base(f, val);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn with_optional_base_env<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
self.core = self.core.with_optional_base_env(f, val, env_var);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optional_base_custom_env<F, T, G>(
|
||||
mut self,
|
||||
f: F,
|
||||
val: Option<T>,
|
||||
env_var: &str,
|
||||
parser: G,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
G: Fn(&str) -> T,
|
||||
{
|
||||
self.core = self
|
||||
.core
|
||||
.with_optional_base_custom_env(f, val, env_var, parser);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::old_config_v1_1_19::{ConfigV1_1_19, Socks5V1_1_19};
|
||||
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_config::must_get_home;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldConfigV1_1_13 {
|
||||
#[serde(flatten)]
|
||||
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
|
||||
|
||||
pub socks5: Socks5V1_1_19,
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for OldConfigV1_1_13 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let base_dir = must_get_home();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let base_dir = PathBuf::from("/tmp");
|
||||
|
||||
base_dir.join(".nym").join("socks5-clients")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OldConfigV1_1_13> for ConfigV1_1_19 {
|
||||
fn from(value: OldConfigV1_1_13) -> Self {
|
||||
ConfigV1_1_19 {
|
||||
base: value.base.into(),
|
||||
socks5: value.socks5,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::SocksClientPaths;
|
||||
use crate::config::{BaseClientConfig, Config, CoreConfig};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use nym_client_core::config::old_config_v1_1_19::ConfigV1_1_19 as BaseConfigV1_1_19;
|
||||
use nym_client_core::config::Client;
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_config::must_get_home;
|
||||
use nym_socks5_client_core::config::{
|
||||
ProviderInterfaceVersion, Socks5, Socks5Debug, Socks5ProtocolVersion,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
|
||||
const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_19 {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseConfigV1_1_19<ConfigV1_1_19>,
|
||||
|
||||
pub socks5: Socks5V1_1_19,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_19> for Config {
|
||||
fn from(value: ConfigV1_1_19) -> Self {
|
||||
Config {
|
||||
core: CoreConfig {
|
||||
base: BaseClientConfig {
|
||||
client: Client {
|
||||
version: value.base.client.version,
|
||||
id: value.base.client.id,
|
||||
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
|
||||
nyxd_urls: value.base.client.nyxd_urls,
|
||||
nym_api_urls: value.base.client.nym_api_urls,
|
||||
gateway_endpoint: value.base.client.gateway_endpoint.into(),
|
||||
},
|
||||
debug: value.base.debug.into(),
|
||||
},
|
||||
socks5: value.socks5.into(),
|
||||
},
|
||||
storage_paths: SocksClientPaths {
|
||||
common_paths: CommonClientPaths {
|
||||
keys: ClientKeysPaths {
|
||||
private_identity_key_file: value.base.client.private_identity_key_file,
|
||||
public_identity_key_file: value.base.client.public_identity_key_file,
|
||||
private_encryption_key_file: value.base.client.private_encryption_key_file,
|
||||
public_encryption_key_file: value.base.client.public_encryption_key_file,
|
||||
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
|
||||
ack_key_file: value.base.client.ack_key_file,
|
||||
},
|
||||
credentials_database: value.base.client.database_path,
|
||||
reply_surb_database: value.base.client.reply_surb_database_path,
|
||||
},
|
||||
},
|
||||
logging: LoggingSettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_19 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let base_dir = must_get_home();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let base_dir = PathBuf::from("/tmp");
|
||||
|
||||
base_dir.join(".nym").join("socks5-clients")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Socks5V1_1_19 {
|
||||
pub listening_port: u16,
|
||||
|
||||
pub provider_mix_address: String,
|
||||
|
||||
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
|
||||
pub provider_interface_version: ProviderInterfaceVersion,
|
||||
|
||||
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
|
||||
pub socks5_protocol_version: Socks5ProtocolVersion,
|
||||
|
||||
#[serde(default)]
|
||||
pub send_anonymously: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub socks5_debug: Socks5DebugV1_1_19,
|
||||
}
|
||||
|
||||
impl From<Socks5V1_1_19> for Socks5 {
|
||||
fn from(value: Socks5V1_1_19) -> Self {
|
||||
Socks5 {
|
||||
listening_port: value.listening_port,
|
||||
provider_mix_address: value.provider_mix_address,
|
||||
provider_interface_version: value.provider_interface_version,
|
||||
socks5_protocol_version: value.socks5_protocol_version,
|
||||
send_anonymously: value.send_anonymously,
|
||||
socks5_debug: value.socks5_debug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Socks5DebugV1_1_19 {
|
||||
connection_start_surbs: u32,
|
||||
per_request_surbs: u32,
|
||||
}
|
||||
|
||||
impl From<Socks5DebugV1_1_19> for Socks5Debug {
|
||||
fn from(value: Socks5DebugV1_1_19) -> Self {
|
||||
Socks5Debug {
|
||||
connection_start_surbs: value.connection_start_surbs,
|
||||
per_request_surbs: value.per_request_surbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Socks5DebugV1_1_19 {
|
||||
fn default() -> Self {
|
||||
Socks5DebugV1_1_19 {
|
||||
connection_start_surbs: DEFAULT_CONNECTION_START_SURBS,
|
||||
per_request_surbs: DEFAULT_PER_REQUEST_SURBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct SocksClientPaths {
|
||||
#[serde(flatten)]
|
||||
pub common_paths: CommonClientPaths,
|
||||
}
|
||||
|
||||
impl SocksClientPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
SocksClientPaths {
|
||||
common_paths: CommonClientPaths::new_default(base_data_directory),
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-48
@@ -1,102 +1,97 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) fn config_template() -> &'static str {
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs.
|
||||
r#"
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs.
|
||||
pub(crate) const CONFIG_TEMPLATE: &str = r#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
##### main base client config options #####
|
||||
|
||||
[client]
|
||||
[core.client]
|
||||
# Version of the client for which this configuration was created.
|
||||
version = '{{ client.version }}'
|
||||
version = '{{ core.client.version }}'
|
||||
|
||||
# Human readable ID of this particular client.
|
||||
id = '{{ client.id }}'
|
||||
id = '{{ core.client.id }}'
|
||||
|
||||
# Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
# to claim bandwidth without presenting bandwidth credentials.
|
||||
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
|
||||
disabled_credentials_mode = {{ core.client.disabled_credentials_mode }}
|
||||
|
||||
# Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
nyxd_urls = [
|
||||
{{#each client.nyxd_urls }}
|
||||
{{#each core.client.nyxd_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
# Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
nym_api_urls = [
|
||||
{{#each client.nym_api_urls }}
|
||||
{{#each core.client.nym_api_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
[storage_paths]
|
||||
|
||||
# Path to file containing private identity key.
|
||||
private_identity_key_file = '{{ client.private_identity_key_file }}'
|
||||
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
|
||||
|
||||
# Path to file containing public identity key.
|
||||
public_identity_key_file = '{{ client.public_identity_key_file }}'
|
||||
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
|
||||
|
||||
# Path to file containing private encryption key.
|
||||
private_encryption_key_file = '{{ client.private_encryption_key_file }}'
|
||||
keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
|
||||
|
||||
# Path to file containing public encryption key.
|
||||
public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
|
||||
# Path to the database containing bandwidth credentials
|
||||
database_path = '{{ client.database_path }}'
|
||||
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database_path = '{{ client.reply_surb_database_path }}'
|
||||
|
||||
##### additional client config options #####
|
||||
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
# communication with particular gateway.
|
||||
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
|
||||
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
|
||||
|
||||
# Path to file containing key used for encrypting and decrypting the content of an
|
||||
# acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
ack_key_file = '{{ client.ack_key_file }}'
|
||||
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
|
||||
|
||||
##### advanced configuration options #####
|
||||
# Path to the database containing bandwidth credentials
|
||||
credentials_database = '{{ storage_paths.credentials_database }}'
|
||||
|
||||
# Absolute path to the home Nym Clients directory.
|
||||
nym_root_directory = '{{ client.nym_root_directory }}'
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
|
||||
|
||||
[client.gateway_endpoint]
|
||||
# DEPRECATED
|
||||
[core.client.gateway_endpoint]
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
|
||||
gateway_id = '{{ core.client.gateway_endpoint.gateway_id }}'
|
||||
|
||||
# Address of the gateway owner to which the client should send messages.
|
||||
gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}'
|
||||
gateway_owner = '{{ core.client.gateway_endpoint.gateway_owner }}'
|
||||
|
||||
# Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}'
|
||||
gateway_listener = '{{ core.client.gateway_endpoint.gateway_listener }}'
|
||||
|
||||
|
||||
##### socket config options #####
|
||||
|
||||
[socks5]
|
||||
[core.socks5]
|
||||
|
||||
# The mix address of the provider to which all requests are going to be sent.
|
||||
provider_mix_address = '{{ socks5.provider_mix_address }}'
|
||||
provider_mix_address = '{{ core.socks5.provider_mix_address }}'
|
||||
|
||||
# The port on which the client will be listening for incoming requests
|
||||
listening_port = {{ socks5.listening_port }}
|
||||
listening_port = {{ core.socks5.listening_port }}
|
||||
|
||||
# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
# While this is going to hide its actual address information, it will make the actual communication
|
||||
# slower and consume nearly double the bandwidth as it will require sending reply SURBs.
|
||||
#
|
||||
# Note that some service providers might not support this.
|
||||
send_anonymously = {{ socks5.send_anonymously }}
|
||||
send_anonymously = {{ core.socks5.send_anonymously }}
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
@@ -109,20 +104,19 @@ send_anonymously = {{ socks5.send_anonymously }}
|
||||
# The following options should not be modified unless you know EXACTLY what you are doing
|
||||
# as if set incorrectly, they may impact your anonymity.
|
||||
|
||||
# [socks5.socks5_debug]
|
||||
# [core.socks5.socks5_debug]
|
||||
|
||||
|
||||
[debug]
|
||||
[core.debug]
|
||||
|
||||
[debug.traffic]
|
||||
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
|
||||
[core.debug.traffic]
|
||||
average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}'
|
||||
|
||||
[debug.acknowledgements]
|
||||
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
|
||||
[core.debug.acknowledgements]
|
||||
average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
|
||||
|
||||
[debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
[core.debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
"#
|
||||
}
|
||||
"#;
|
||||
@@ -8,6 +8,7 @@ use nym_bin_common::logging::{maybe_print_banner, setup_logging};
|
||||
use nym_network_defaults::setup_env;
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
pub mod error;
|
||||
|
||||
#[tokio::main]
|
||||
|
||||
@@ -34,7 +34,14 @@ import {
|
||||
StakeSaturationResponse,
|
||||
UnbondedMixnodeResponse,
|
||||
VestingAccountInfo,
|
||||
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
|
||||
ContractState,
|
||||
VestingAccountsCoinPaged,
|
||||
VestingAccountsPaged,
|
||||
DelegationTimes,
|
||||
Delegations,
|
||||
Period,
|
||||
VestingAccountNode,
|
||||
DelegationBlock,
|
||||
} from '@nymproject/types';
|
||||
import QueryClient from './query-client';
|
||||
import SigningClient, { ISigningClient } from './signing-client';
|
||||
@@ -207,7 +214,7 @@ export default class ValidatorClient implements INymClient {
|
||||
let mixNodes: UnbondedMixnodeResponse[] = [];
|
||||
const limit = 50;
|
||||
let startAfter;
|
||||
for (; ;) {
|
||||
for (;;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
|
||||
this.mixnetContract,
|
||||
@@ -230,7 +237,7 @@ export default class ValidatorClient implements INymClient {
|
||||
let mixNodes: MixNodeBond[] = [];
|
||||
const limit = 50;
|
||||
let startAfter;
|
||||
for (; ;) {
|
||||
for (;;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds(
|
||||
this.mixnetContract,
|
||||
@@ -252,7 +259,7 @@ export default class ValidatorClient implements INymClient {
|
||||
let mixNodes: MixNodeDetails[] = [];
|
||||
const limit = 50;
|
||||
let startAfter;
|
||||
for (; ;) {
|
||||
for (;;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
|
||||
this.mixnetContract,
|
||||
@@ -284,7 +291,7 @@ export default class ValidatorClient implements INymClient {
|
||||
let delegations: Delegation[] = [];
|
||||
const limit = 250;
|
||||
let startAfter;
|
||||
for (; ;) {
|
||||
for (;;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
|
||||
this.mixnetContract,
|
||||
@@ -307,7 +314,7 @@ export default class ValidatorClient implements INymClient {
|
||||
let delegations: Delegation[] = [];
|
||||
const limit = 250;
|
||||
let startAfter;
|
||||
for (; ;) {
|
||||
for (;;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
|
||||
this.mixnetContract,
|
||||
@@ -330,7 +337,7 @@ export default class ValidatorClient implements INymClient {
|
||||
let delegations: Delegation[] = [];
|
||||
const limit = 250;
|
||||
let startAfter;
|
||||
for (; ;) {
|
||||
for (;;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged(
|
||||
this.mixnetContract,
|
||||
@@ -518,11 +525,9 @@ export default class ValidatorClient implements INymClient {
|
||||
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
|
||||
}
|
||||
|
||||
|
||||
// VESTING
|
||||
// TODO - MOVE TO A DIFFERENT FILE
|
||||
|
||||
|
||||
public async getVestingAccountsPaged(): Promise<VestingAccountsPaged> {
|
||||
return this.client.getVestingAccountsPaged(this.vestingContract);
|
||||
}
|
||||
@@ -608,7 +613,7 @@ export default class ValidatorClient implements INymClient {
|
||||
}
|
||||
|
||||
public async getDelegation(address: string, mix_id: number): Promise<DelegationBlock> {
|
||||
return this.client.getDelegation(this.vestingContract, address, mix_id );
|
||||
return this.client.getDelegation(this.vestingContract, address, mix_id);
|
||||
}
|
||||
|
||||
public async getTotalDelegationAmount(address: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
|
||||
@@ -618,4 +623,10 @@ export default class ValidatorClient implements INymClient {
|
||||
public async getCurrentVestingPeriod(address: string): Promise<Period> {
|
||||
return this.client.getCurrentVestingPeriod(this.vestingContract, address);
|
||||
}
|
||||
|
||||
// SIMULATE
|
||||
|
||||
public async simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
|
||||
return (this.client as SigningClient).simulateSend(signingAddress, from, to, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,18 @@ import {
|
||||
RewardingParams,
|
||||
UnbondedMixnodeResponse,
|
||||
VestingAccountInfo,
|
||||
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
|
||||
ContractState,
|
||||
VestingAccountsCoinPaged,
|
||||
VestingAccountsPaged,
|
||||
DelegationTimes,
|
||||
Delegations,
|
||||
Period,
|
||||
VestingAccountNode,
|
||||
DelegationBlock,
|
||||
} from '@nymproject/types';
|
||||
import NymApiQuerier from './nym-api-querier';
|
||||
import { makeBankMsgSend } from './utils';
|
||||
import { ISimulateClient } from './types/simulate';
|
||||
|
||||
// methods exposed by `SigningCosmWasmClient`
|
||||
export interface ICosmWasmSigning {
|
||||
@@ -148,7 +157,7 @@ export interface INymSigning {
|
||||
clientAddress: string;
|
||||
}
|
||||
|
||||
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
|
||||
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning, ISimulateClient {
|
||||
bondMixNode(
|
||||
mixnetContractAddress: string,
|
||||
mixNode: MixNode,
|
||||
@@ -515,7 +524,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
|
||||
getVestingAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsPaged> {
|
||||
return this.nyxdQuerier.getVestingAccountsPaged(vestingContractAddress);
|
||||
};
|
||||
}
|
||||
|
||||
getVestingAmountsAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsCoinPaged> {
|
||||
return this.nyxdQuerier.getVestingAmountsAccountsPaged(vestingContractAddress);
|
||||
@@ -569,7 +578,10 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
return this.nyxdQuerier.getEndTime(vestingContractAddress, vestingAccountAddress);
|
||||
}
|
||||
|
||||
getOriginalVestingDetails(vestingContractAddress: string, vestingAccountAddress: string): Promise<OriginalVestingResponse> {
|
||||
getOriginalVestingDetails(
|
||||
vestingContractAddress: string,
|
||||
vestingAccountAddress: string,
|
||||
): Promise<OriginalVestingResponse> {
|
||||
return this.nyxdQuerier.getOriginalVestingDetails(vestingContractAddress, vestingAccountAddress);
|
||||
}
|
||||
|
||||
@@ -589,7 +601,11 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
return this.nyxdQuerier.getGateway(vestingContractAddress, address);
|
||||
}
|
||||
|
||||
getDelegationTimes(vestingContractAddress: string, mix_id: number, delegatorAddress: string): Promise<DelegationTimes> {
|
||||
getDelegationTimes(
|
||||
vestingContractAddress: string,
|
||||
mix_id: number,
|
||||
delegatorAddress: string,
|
||||
): Promise<DelegationTimes> {
|
||||
return this.nyxdQuerier.getDelegationTimes(vestingContractAddress, mix_id, delegatorAddress);
|
||||
}
|
||||
|
||||
@@ -597,15 +613,38 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
return this.nyxdQuerier.getAllDelegations(vestingContractAddress);
|
||||
}
|
||||
|
||||
getDelegation(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number): Promise<DelegationBlock> {
|
||||
getDelegation(
|
||||
vestingContractAddress: string,
|
||||
vestingAccountAddress: string,
|
||||
mix_id: number,
|
||||
): Promise<DelegationBlock> {
|
||||
return this.nyxdQuerier.getDelegation(vestingContractAddress, vestingAccountAddress, mix_id);
|
||||
}
|
||||
|
||||
getTotalDelegationAmount(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
|
||||
return this.nyxdQuerier.getTotalDelegationAmount(vestingContractAddress, vestingAccountAddress, mix_id, block_timestamp_sec);
|
||||
getTotalDelegationAmount(
|
||||
vestingContractAddress: string,
|
||||
vestingAccountAddress: string,
|
||||
mix_id: number,
|
||||
block_timestamp_sec: number,
|
||||
): Promise<Coin> {
|
||||
return this.nyxdQuerier.getTotalDelegationAmount(
|
||||
vestingContractAddress,
|
||||
vestingAccountAddress,
|
||||
mix_id,
|
||||
block_timestamp_sec,
|
||||
);
|
||||
}
|
||||
|
||||
getCurrentVestingPeriod(vestingContractAddress: string, address: string): Promise<Period> {
|
||||
return this.nyxdQuerier.getCurrentVestingPeriod(vestingContractAddress, address);
|
||||
}
|
||||
|
||||
// simulation
|
||||
|
||||
// TODO consider adding multipling factor
|
||||
|
||||
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
|
||||
const sendMsg = makeBankMsgSend(from, to, amount);
|
||||
return this.simulate(signingAddress, [sendMsg], 'simulate send tx');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import expect from 'expect';
|
||||
import ValidatorClient from '../..';
|
||||
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
// TODO: implement for QA with .env for mnemonics
|
||||
describe('Simualtions', () => {
|
||||
let client: ValidatorClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
client = await ValidatorClient.connect(
|
||||
process.env.mnemonic || '',
|
||||
process.env.rpcAddress || '',
|
||||
process.env.validatorAddress || '',
|
||||
process.env.prefix || '',
|
||||
process.env.mixnetContractAddress || '',
|
||||
process.env.vestingContractAddress || '',
|
||||
process.env.denom || '',
|
||||
);
|
||||
});
|
||||
|
||||
it('can simulate sending tokens', async () => {
|
||||
const res = await client.simulateSend(client.address, client.address, client.address, [
|
||||
{ amount: '400000', denom: 'unym' },
|
||||
]);
|
||||
|
||||
expect(typeof res).toBe('number');
|
||||
}).timeout(10000);
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Coin } from '@cosmjs/proto-signing';
|
||||
|
||||
export interface ISimulateClient {
|
||||
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]): Promise<number>;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
@@ -1,6 +1,5 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
|
||||
Generated
+5255
File diff suppressed because it is too large
Load Diff
@@ -17,29 +17,39 @@ default = ["console_error_panic_hook"]
|
||||
offline-test = []
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.68"
|
||||
bs58 = "0.4.0"
|
||||
futures = "0.3"
|
||||
js-sys = "0.3"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
serde-wasm-bindgen = "0.4"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
tokio = { version = "1.24.1", features = ["sync"] }
|
||||
url = "2.2"
|
||||
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
thiserror = "1.0.40"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"}
|
||||
|
||||
# internal
|
||||
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
|
||||
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-crypto = { path = "../../common/crypto" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
wasm-utils = { path = "../../common/wasm-utils" }
|
||||
nym-task = { path = "../../common/task" }
|
||||
wasm-utils = { path = "../../common/wasm-utils", features = ["storage"], default-features = false }
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
@@ -57,7 +67,7 @@ wee_alloc = { version = "0.4", optional = true }
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = true
|
||||
wasm-opt = false
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -0,0 +1,5 @@
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
import('./index.js')
|
||||
.catch(e => console.error('Error importing `index.js`:', e));
|
||||
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nym WebAssembly Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
<label>Sender: </label><input disabled="true" size="85" type="text" id="sender" value="">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>Recipient: </label><input size="85" type="text" id="recipient" value="">
|
||||
</p>
|
||||
<p>
|
||||
<label>Message: </label><input type="text" id="message" value="Hello mixnet!">
|
||||
</p>
|
||||
<p>
|
||||
<button id="send-button">Send</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<label>Mixnode Identity: </label>
|
||||
<input type="text" size = "60" id="mixnode_identity" value="...">
|
||||
<button id="magic-button">✨ Magic Test Button ✨</button>
|
||||
</div>
|
||||
|
||||
|
||||
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
|
||||
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
|
||||
messages show in green.</p>
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
<span id="output"></span>
|
||||
</p>
|
||||
<script src="./bootstrap.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
class WebWorkerClient {
|
||||
worker = null;
|
||||
|
||||
constructor() {
|
||||
this.worker = new Worker('./worker.js');
|
||||
|
||||
this.worker.onmessage = (ev) => {
|
||||
if (ev.data && ev.data.kind) {
|
||||
switch (ev.data.kind) {
|
||||
case 'Ready':
|
||||
const {selfAddress} = ev.data.args;
|
||||
displaySenderAddress(selfAddress);
|
||||
break;
|
||||
case 'ReceiveMessage':
|
||||
const {message, senderTag, isTestPacket } = ev.data.args;
|
||||
displayReceived(message, senderTag, isTestPacket);
|
||||
break;
|
||||
case 'DisableMagicTestButton':
|
||||
const magicButton = document.querySelector('#magic-button');
|
||||
magicButton.setAttribute('disabled', "true")
|
||||
break;
|
||||
case 'DisplayTesterResults':
|
||||
const {score, sentPackets, receivedPackets, receivedAcks, duplicatePackets, duplicateAcks} = ev.data.args;
|
||||
const resultText = `Test score: ${score}. Sent ${sentPackets} packets. Received ${receivedPackets} packets and ${receivedAcks} acks back. We also got ${duplicatePackets} duplicate packets and ${duplicateAcks} duplicate acks.`
|
||||
displayReceivedRawString(resultText)
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sendMessage = (message, recipient) => {
|
||||
if (!this.worker) {
|
||||
console.error('Could not send message because worker does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
this.worker.postMessage({
|
||||
kind: 'SendMessage',
|
||||
args: {
|
||||
message, recipient,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
sendTestPacket = (mixnodeIdentity) => {
|
||||
if (!this.worker) {
|
||||
console.error('Could not send message because worker does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
this.worker.postMessage({
|
||||
kind: 'TestPacket',
|
||||
args: {
|
||||
mixnodeIdentity,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let client = null;
|
||||
|
||||
async function main() {
|
||||
client = new WebWorkerClient();
|
||||
|
||||
const sendButton = document.querySelector('#send-button');
|
||||
sendButton.onclick = function () {
|
||||
sendMessageTo();
|
||||
};
|
||||
|
||||
const magicButton = document.querySelector('#magic-button');
|
||||
magicButton.onclick = function () {
|
||||
sendTestPacket();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Sphinx packet and send it to the mixnet through the gateway node.
|
||||
*
|
||||
* Message and recipient are taken from the values in the user interface.
|
||||
*
|
||||
*/
|
||||
async function sendMessageTo() {
|
||||
const message = document.getElementById('message').value;
|
||||
const recipient = document.getElementById('recipient').value;
|
||||
|
||||
await client.sendMessage(message, recipient);
|
||||
displaySend(message);
|
||||
}
|
||||
|
||||
async function sendTestPacket() {
|
||||
const mixnodeIdentity = document.getElementById('mixnode_identity').value;
|
||||
|
||||
await client.sendTestPacket(mixnodeIdentity)
|
||||
displaySend(`sending test packets to: ${mixnodeIdentity}...`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display messages that have been sent up the websocket. Colours them blue.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displaySend(message) {
|
||||
let timestamp = new Date().toISOString().substr(11, 12);
|
||||
|
||||
let sendDiv = document.createElement('div');
|
||||
let paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: blue');
|
||||
let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
|
||||
sendDiv.appendChild(paragraph);
|
||||
document.getElementById('output').appendChild(sendDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display received text messages in the browser. Colour them green.
|
||||
*
|
||||
* @param {Uint8Array} raw
|
||||
*/
|
||||
function displayReceived(raw, sender_tag, isTestPacket) {
|
||||
let content = new TextDecoder().decode(raw);
|
||||
if (sender_tag !== undefined) {
|
||||
console.log("this message also contained some surbs from", sender_tag)
|
||||
}
|
||||
|
||||
if (isTestPacket) {
|
||||
const decoded = JSON.parse(content)
|
||||
content = `Received packet ${decoded.msg_id} / ${decoded.total_msgs} for node ${decoded.encoded_node_identity} (test: ${decoded.test_id})`
|
||||
}
|
||||
|
||||
displayReceivedRawString(content)
|
||||
}
|
||||
|
||||
|
||||
function displayReceivedRawString(raw) {
|
||||
let timestamp = new Date().toISOString().substr(11, 12);
|
||||
let receivedDiv = document.createElement('div');
|
||||
let paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: green');
|
||||
let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + raw);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
receivedDiv.appendChild(paragraph);
|
||||
document.getElementById('output').appendChild(receivedDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the nymClient's sender address in the user interface
|
||||
*
|
||||
* @param {String} address
|
||||
*/
|
||||
function displaySenderAddress(address) {
|
||||
document.getElementById('sender').value = address;
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "create-wasm-app",
|
||||
"version": "0.1.0",
|
||||
"description": "create an app to consume rust-generated wasm packages",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"create-wasm-app": ".bin/create-wasm-app.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"start": "webpack-dev-server --port 8001"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
|
||||
},
|
||||
"keywords": [
|
||||
"webassembly",
|
||||
"wasm",
|
||||
"rust",
|
||||
"webpack"
|
||||
],
|
||||
"author": "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nymtech/nym/issues"
|
||||
},
|
||||
"homepage": "https://nymtech.net/docs",
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^10.2.4",
|
||||
"hello-wasm-pack": "^0.1.0",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/nym-client-wasm": "file:../pkg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
},
|
||||
entry: {
|
||||
bootstrap: './bootstrap.js',
|
||||
worker: './worker.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
// mode: 'development',
|
||||
mode: 'production',
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'index.html',
|
||||
{
|
||||
from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)',
|
||||
to: '[name][ext]',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
experiments: { syncWebAssembly: true },
|
||||
};
|
||||
@@ -0,0 +1,344 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
importScripts('nym_client_wasm.js');
|
||||
|
||||
console.log('Initializing worker');
|
||||
|
||||
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
|
||||
const {
|
||||
NymNodeTester,
|
||||
WasmGateway,
|
||||
WasmMixNode,
|
||||
WasmNymTopology,
|
||||
default_debug,
|
||||
NymClientBuilder,
|
||||
NymClient,
|
||||
set_panic_hook,
|
||||
Config,
|
||||
GatewayEndpointConfig,
|
||||
ClientStorage,
|
||||
current_network_topology,
|
||||
make_key,
|
||||
make_key2
|
||||
} = wasm_bindgen;
|
||||
|
||||
let client = null;
|
||||
let tester = null;
|
||||
|
||||
function dummyTopology() {
|
||||
const l1Mixnode = new WasmMixNode(
|
||||
1,
|
||||
'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47',
|
||||
'178.79.143.65',
|
||||
1789,
|
||||
'4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz',
|
||||
'8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG',
|
||||
1,
|
||||
'1.10.0',
|
||||
);
|
||||
const l2Mixnode = new WasmMixNode(
|
||||
2,
|
||||
'n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70',
|
||||
'109.74.197.180',
|
||||
1789,
|
||||
'7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK',
|
||||
'GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN',
|
||||
2,
|
||||
'1.10.0',
|
||||
);
|
||||
const l3Mixnode = new WasmMixNode(
|
||||
3,
|
||||
'n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77',
|
||||
'176.58.101.80',
|
||||
1789,
|
||||
'FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R',
|
||||
'DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD',
|
||||
3,
|
||||
'1.10.0',
|
||||
);
|
||||
|
||||
const gateway = new WasmGateway(
|
||||
'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts',
|
||||
'85.159.212.96',
|
||||
1789,
|
||||
9000,
|
||||
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
|
||||
'BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL',
|
||||
'1.10.1',
|
||||
);
|
||||
|
||||
const mixnodes = new Map();
|
||||
mixnodes.set(1, [l1Mixnode]);
|
||||
mixnodes.set(2, [l2Mixnode]);
|
||||
mixnodes.set(3, [l3Mixnode]);
|
||||
|
||||
|
||||
const gateways = [gateway];
|
||||
|
||||
return new WasmNymTopology(mixnodes, gateways)
|
||||
}
|
||||
|
||||
function printAndDisplayTestResult(result) {
|
||||
result.log_details();
|
||||
|
||||
self.postMessage({
|
||||
kind: 'DisplayTesterResults',
|
||||
args: {
|
||||
score: result.score(),
|
||||
sentPackets: result.sent_packets,
|
||||
receivedPackets: result.received_packets,
|
||||
receivedAcks: result.received_acks,
|
||||
duplicatePackets: result.duplicate_packets,
|
||||
duplicateAcks: result.duplicate_acks,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function testWithTester() {
|
||||
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
|
||||
// A) construct with hardcoded topology
|
||||
const topology = dummyTopology()
|
||||
const nodeTester = await new NymNodeTester(topology, preferredGateway);
|
||||
|
||||
// B) first get topology directly from nym-api
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const topology = await current_network_topology(validator)
|
||||
// const nodeTester = await new NymNodeTester(topology, preferredGateway);
|
||||
//
|
||||
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const nodeTester = await NymNodeTester.new_with_api(validator, preferredGateway)
|
||||
|
||||
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
|
||||
// const topology = dummyTopology()
|
||||
// const nodeTester = await new NymNodeTester(topology);
|
||||
|
||||
self.onmessage = async event => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case 'TestPacket': {
|
||||
const {mixnodeIdentity} = event.data.args;
|
||||
console.log("starting node test...");
|
||||
|
||||
let result = await nodeTester.test_node(mixnodeIdentity);
|
||||
printAndDisplayTestResult(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function testWithNymClient() {
|
||||
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
const topology = dummyTopology()
|
||||
|
||||
let received = 0
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
received += 1;
|
||||
self.postMessage({
|
||||
kind: 'ReceiveMessage',
|
||||
args: {
|
||||
message,
|
||||
senderTag: undefined,
|
||||
isTestPacket: true,
|
||||
},
|
||||
});
|
||||
|
||||
// it's really up to the user to create proper callback here...
|
||||
console.log(`received ${received} packets so far`)
|
||||
};
|
||||
|
||||
console.log('Instantiating WASM client...');
|
||||
|
||||
let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, preferredGateway)
|
||||
console.log('Web worker creating WASM client...');
|
||||
let local_client = await clientBuilder.start_client();
|
||||
console.log('WASM client running!');
|
||||
|
||||
const selfAddress = local_client.self_address();
|
||||
|
||||
// set the global (I guess we don't have to anymore?)
|
||||
client = local_client;
|
||||
|
||||
console.log(`Client address is ${selfAddress}`);
|
||||
self.postMessage({
|
||||
kind: 'Ready',
|
||||
args: {
|
||||
selfAddress,
|
||||
},
|
||||
});
|
||||
|
||||
// Set callback to handle messages passed to the worker.
|
||||
self.onmessage = async event => {
|
||||
console.log(event)
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case 'SendMessage': {
|
||||
const {message, recipient} = event.data.args;
|
||||
let uint8Array = new TextEncoder().encode(message);
|
||||
await client.send_regular_message(uint8Array, recipient);
|
||||
break;
|
||||
}
|
||||
case 'TestPacket': {
|
||||
const {mixnodeIdentity} = event.data.args;
|
||||
const req = await client.try_construct_test_packet_request(mixnodeIdentity);
|
||||
await client.change_hardcoded_topology(req.injectable_topology());
|
||||
await client.try_send_test_packets(req);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function normalNymClientUsage() {
|
||||
self.postMessage({kind: 'DisableMagicTestButton'});
|
||||
|
||||
// only really useful if you want to adjust some settings like traffic rate
|
||||
// (if not needed you can just pass a null)
|
||||
const debug = default_debug();
|
||||
|
||||
debug.disable_main_poisson_packet_distribution = true;
|
||||
debug.disable_loop_cover_traffic_stream = true;
|
||||
debug.use_extended_packet_size = false;
|
||||
// debug.average_packet_delay_ms = BigInt(10);
|
||||
// debug.average_ack_delay_ms = BigInt(10);
|
||||
// debug.ack_wait_addition_ms = BigInt(3000);
|
||||
// debug.ack_wait_multiplier = 10;
|
||||
|
||||
debug.topology_refresh_rate_ms = BigInt(60000)
|
||||
|
||||
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
|
||||
const config = new Config('my-awesome-wasm-client', validator, debug);
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
console.log(message);
|
||||
self.postMessage({
|
||||
kind: 'ReceiveMessage',
|
||||
args: {
|
||||
message,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
console.log('Instantiating WASM client...');
|
||||
|
||||
let localClient = await new NymClient(config, onMessageHandler)
|
||||
console.log('WASM client running!');
|
||||
|
||||
const selfAddress = localClient.self_address();
|
||||
|
||||
// set the global (I guess we don't have to anymore?)
|
||||
client = localClient;
|
||||
|
||||
console.log(`Client address is ${selfAddress}`);
|
||||
self.postMessage({
|
||||
kind: 'Ready',
|
||||
args: {
|
||||
selfAddress,
|
||||
},
|
||||
});
|
||||
|
||||
// Set callback to handle messages passed to the worker.
|
||||
self.onmessage = async event => {
|
||||
console.log(event)
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case 'SendMessage': {
|
||||
const {message, recipient} = event.data.args;
|
||||
let uint8Array = new TextEncoder().encode(message);
|
||||
await client.send_regular_message(uint8Array, recipient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function messWithStorage() {
|
||||
self.onmessage = async event => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case 'TestPacket': {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("button clicked...", mixnodeIdentity);
|
||||
|
||||
let id1 = "one";
|
||||
let id2 = "two";
|
||||
|
||||
console.log("making store1 NO-ENC");
|
||||
let _storage1 = await ClientStorage.new_unencrypted(id1);
|
||||
|
||||
console.log("making store2 ENC")
|
||||
let _storage2 = await new ClientStorage(id2, "my-secret-password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store1 WITH PASSWORD")
|
||||
// let _storage1_alt = await new ClientStorage(id1, "password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 WITHOUT PASSWORD")
|
||||
// let _storage2_alt = await ClientStorage.new_unencrypted(id2);
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 with WRONG PASSWORD")
|
||||
// let _storage2_bad = await new ClientStorage(id2, "bad-password")
|
||||
|
||||
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
//
|
||||
// console.log("store1: ", await storage1.store("FOOMP"));
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// load WASM package
|
||||
await wasm_bindgen('nym_client_wasm_bg.wasm');
|
||||
console.log('Loaded WASM');
|
||||
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
|
||||
// run test on simplified and dedicated tester:
|
||||
// await testWithTester()
|
||||
|
||||
// hook-up the whole client for testing
|
||||
// await testWithNymClient()
|
||||
|
||||
// 'Normal' client setup (to send 'normal' messages)
|
||||
await normalNymClientUsage()
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
|
||||
@@ -7,58 +7,49 @@
|
||||
#![allow(clippy::drop_copy)]
|
||||
|
||||
use nym_client_core::config::{
|
||||
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic,
|
||||
DebugConfig as ConfigDebug, GatewayConnection as ConfigGatewayConnection,
|
||||
GatewayEndpointConfig, ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology,
|
||||
Traffic as ConfigTraffic,
|
||||
Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig,
|
||||
CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug,
|
||||
GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs,
|
||||
Topology as ConfigTopology, Traffic as ConfigTraffic,
|
||||
};
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
pub(crate) id: String,
|
||||
|
||||
pub(crate) nym_api_url: Url,
|
||||
|
||||
pub(crate) disabled_credentials_mode: bool,
|
||||
|
||||
/// Information regarding how the client should send data to gateway.
|
||||
pub(crate) gateway_endpoint: GatewayEndpointConfig,
|
||||
|
||||
pub(crate) debug: ConfigDebug,
|
||||
pub(crate) base: BaseClientConfig,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Config {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
id: String,
|
||||
validator_server: String,
|
||||
gateway_endpoint: GatewayEndpointConfig,
|
||||
debug: Option<Debug>,
|
||||
) -> Self {
|
||||
pub fn new(id: String, validator_server: String, debug: Option<DebugWasm>) -> Self {
|
||||
Config {
|
||||
id,
|
||||
nym_api_url: validator_server
|
||||
.parse()
|
||||
.expect("provided url was malformed"),
|
||||
disabled_credentials_mode: true,
|
||||
gateway_endpoint,
|
||||
debug: debug.map(Into::into).unwrap_or_default(),
|
||||
base: BaseClientConfig::new(id)
|
||||
.with_custom_nyxd(vec![validator_server
|
||||
.parse()
|
||||
.expect("provided url was malformed")])
|
||||
.with_debug_config(debug.map(Into::into).unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_tester_config<S: Into<String>>(id: S) -> Self {
|
||||
Config {
|
||||
base: BaseClientConfig::new(id)
|
||||
.with_disabled_credentials(true)
|
||||
.with_disabled_cover_traffic(true)
|
||||
.with_disabled_topology_refresh(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Traffic {
|
||||
pub struct TrafficWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
@@ -77,14 +68,23 @@ pub struct Traffic {
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
|
||||
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
|
||||
pub use_outfox: bool,
|
||||
}
|
||||
|
||||
impl From<Traffic> for ConfigTraffic {
|
||||
fn from(traffic: Traffic) -> Self {
|
||||
impl From<TrafficWasm> for ConfigTraffic {
|
||||
fn from(traffic: TrafficWasm) -> Self {
|
||||
let use_extended_packet_size = traffic
|
||||
.use_extended_packet_size
|
||||
.then(|| PacketSize::ExtendedPacket32);
|
||||
|
||||
let packet_type = if traffic.use_outfox {
|
||||
PacketType::Outfox
|
||||
} else {
|
||||
PacketType::Mix
|
||||
};
|
||||
|
||||
ConfigTraffic {
|
||||
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms),
|
||||
message_sending_average_delay: Duration::from_millis(
|
||||
@@ -94,26 +94,28 @@ impl From<Traffic> for ConfigTraffic {
|
||||
.disable_main_poisson_packet_distribution,
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
secondary_packet_size: use_extended_packet_size,
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigTraffic> for Traffic {
|
||||
impl From<ConfigTraffic> for TrafficWasm {
|
||||
fn from(traffic: ConfigTraffic) -> Self {
|
||||
Traffic {
|
||||
TrafficWasm {
|
||||
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64,
|
||||
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
|
||||
as u64,
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
|
||||
use_outfox: traffic.packet_type == PacketType::Outfox,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CoverTraffic {
|
||||
pub struct CoverTrafficWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
pub loop_cover_traffic_average_delay_ms: u64,
|
||||
@@ -127,8 +129,8 @@ pub struct CoverTraffic {
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
}
|
||||
|
||||
impl From<CoverTraffic> for ConfigCoverTraffic {
|
||||
fn from(cover_traffic: CoverTraffic) -> Self {
|
||||
impl From<CoverTrafficWasm> for ConfigCoverTraffic {
|
||||
fn from(cover_traffic: CoverTrafficWasm) -> Self {
|
||||
ConfigCoverTraffic {
|
||||
loop_cover_traffic_average_delay: Duration::from_millis(
|
||||
cover_traffic.loop_cover_traffic_average_delay_ms,
|
||||
@@ -139,9 +141,9 @@ impl From<CoverTraffic> for ConfigCoverTraffic {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigCoverTraffic> for CoverTraffic {
|
||||
impl From<ConfigCoverTraffic> for CoverTrafficWasm {
|
||||
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
|
||||
CoverTraffic {
|
||||
CoverTrafficWasm {
|
||||
loop_cover_traffic_average_delay_ms: cover_traffic
|
||||
.loop_cover_traffic_average_delay
|
||||
.as_millis() as u64,
|
||||
@@ -153,14 +155,14 @@ impl From<ConfigCoverTraffic> for CoverTraffic {
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct GatewayConnection {
|
||||
pub struct GatewayConnectionWasm {
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
pub gateway_response_timeout_ms: u64,
|
||||
}
|
||||
|
||||
impl From<GatewayConnection> for ConfigGatewayConnection {
|
||||
fn from(gateway_connection: GatewayConnection) -> Self {
|
||||
impl From<GatewayConnectionWasm> for ConfigGatewayConnection {
|
||||
fn from(gateway_connection: GatewayConnectionWasm) -> Self {
|
||||
ConfigGatewayConnection {
|
||||
gateway_response_timeout: Duration::from_millis(
|
||||
gateway_connection.gateway_response_timeout_ms,
|
||||
@@ -169,9 +171,9 @@ impl From<GatewayConnection> for ConfigGatewayConnection {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigGatewayConnection> for GatewayConnection {
|
||||
impl From<ConfigGatewayConnection> for GatewayConnectionWasm {
|
||||
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
|
||||
GatewayConnection {
|
||||
GatewayConnectionWasm {
|
||||
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
|
||||
as u64,
|
||||
}
|
||||
@@ -180,7 +182,7 @@ impl From<ConfigGatewayConnection> for GatewayConnection {
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Acknowledgements {
|
||||
pub struct AcknowledgementsWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
@@ -198,8 +200,8 @@ pub struct Acknowledgements {
|
||||
pub ack_wait_addition_ms: u64,
|
||||
}
|
||||
|
||||
impl From<Acknowledgements> for ConfigAcknowledgements {
|
||||
fn from(acknowledgements: Acknowledgements) -> Self {
|
||||
impl From<AcknowledgementsWasm> for ConfigAcknowledgements {
|
||||
fn from(acknowledgements: AcknowledgementsWasm) -> Self {
|
||||
ConfigAcknowledgements {
|
||||
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms),
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
@@ -208,9 +210,9 @@ impl From<Acknowledgements> for ConfigAcknowledgements {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigAcknowledgements> for Acknowledgements {
|
||||
impl From<ConfigAcknowledgements> for AcknowledgementsWasm {
|
||||
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
|
||||
Acknowledgements {
|
||||
AcknowledgementsWasm {
|
||||
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64,
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64,
|
||||
@@ -220,7 +222,7 @@ impl From<ConfigAcknowledgements> for Acknowledgements {
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Topology {
|
||||
pub struct TopologyWasm {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
pub topology_refresh_rate_ms: u64,
|
||||
@@ -229,31 +231,38 @@ pub struct Topology {
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
pub topology_resolution_timeout_ms: u64,
|
||||
|
||||
/// Specifies whether the client should not refresh the network topology after obtaining
|
||||
/// the first valid instance.
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
pub disable_refreshing: bool,
|
||||
}
|
||||
|
||||
impl From<Topology> for ConfigTopology {
|
||||
fn from(topology: Topology) -> Self {
|
||||
impl From<TopologyWasm> for ConfigTopology {
|
||||
fn from(topology: TopologyWasm) -> Self {
|
||||
ConfigTopology {
|
||||
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms),
|
||||
topology_resolution_timeout: Duration::from_millis(
|
||||
topology.topology_resolution_timeout_ms,
|
||||
),
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigTopology> for Topology {
|
||||
impl From<ConfigTopology> for TopologyWasm {
|
||||
fn from(topology: ConfigTopology) -> Self {
|
||||
Topology {
|
||||
TopologyWasm {
|
||||
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
|
||||
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ReplySurbs {
|
||||
pub struct ReplySurbsWasm {
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
@@ -287,8 +296,8 @@ pub struct ReplySurbs {
|
||||
pub maximum_reply_key_age_ms: u64,
|
||||
}
|
||||
|
||||
impl From<ReplySurbs> for ConfigReplySurbs {
|
||||
fn from(reply_surbs: ReplySurbs) -> Self {
|
||||
impl From<ReplySurbsWasm> for ConfigReplySurbs {
|
||||
fn from(reply_surbs: ReplySurbsWasm) -> Self {
|
||||
ConfigReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
@@ -308,9 +317,9 @@ impl From<ReplySurbs> for ConfigReplySurbs {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigReplySurbs> for ReplySurbs {
|
||||
impl From<ConfigReplySurbs> for ReplySurbsWasm {
|
||||
fn from(reply_surbs: ConfigReplySurbs) -> Self {
|
||||
ReplySurbs {
|
||||
ReplySurbsWasm {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
|
||||
@@ -332,28 +341,28 @@ impl From<ConfigReplySurbs> for ReplySurbs {
|
||||
// just a helper structure to more easily pass through the JS boundary
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Debug {
|
||||
pub struct DebugWasm {
|
||||
/// Defines all configuration options related to traffic streams.
|
||||
pub traffic: Traffic,
|
||||
pub traffic: TrafficWasm,
|
||||
|
||||
/// Defines all configuration options related to cover traffic stream(s).
|
||||
pub cover_traffic: CoverTraffic,
|
||||
pub cover_traffic: CoverTrafficWasm,
|
||||
|
||||
/// Defines all configuration options related to the gateway connection.
|
||||
pub gateway_connection: GatewayConnection,
|
||||
pub gateway_connection: GatewayConnectionWasm,
|
||||
|
||||
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
|
||||
pub acknowledgements: Acknowledgements,
|
||||
pub acknowledgements: AcknowledgementsWasm,
|
||||
|
||||
/// Defines all configuration options related topology, such as refresh rates or timeouts.
|
||||
pub topology: Topology,
|
||||
pub topology: TopologyWasm,
|
||||
|
||||
/// Defines all configuration options related to reply SURBs.
|
||||
pub reply_surbs: ReplySurbs,
|
||||
pub reply_surbs: ReplySurbsWasm,
|
||||
}
|
||||
|
||||
impl From<Debug> for ConfigDebug {
|
||||
fn from(debug: Debug) -> Self {
|
||||
impl From<DebugWasm> for ConfigDebug {
|
||||
fn from(debug: DebugWasm) -> Self {
|
||||
ConfigDebug {
|
||||
traffic: debug.traffic.into(),
|
||||
cover_traffic: debug.cover_traffic.into(),
|
||||
@@ -365,9 +374,9 @@ impl From<Debug> for ConfigDebug {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigDebug> for Debug {
|
||||
impl From<ConfigDebug> for DebugWasm {
|
||||
fn from(debug: ConfigDebug) -> Self {
|
||||
Debug {
|
||||
DebugWasm {
|
||||
traffic: debug.traffic.into(),
|
||||
cover_traffic: debug.cover_traffic.into(),
|
||||
gateway_connection: debug.gateway_connection.into(),
|
||||
@@ -379,6 +388,6 @@ impl From<ConfigDebug> for Debug {
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn default_debug() -> Debug {
|
||||
pub fn default_debug() -> DebugWasm {
|
||||
ConfigDebug::default().into()
|
||||
}
|
||||
|
||||
@@ -1,16 +1,42 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::WasmClientError;
|
||||
use crate::tester::helpers::WasmTestMessageExt;
|
||||
use crate::tester::{NodeTestMessage, DEFAULT_TEST_PACKETS};
|
||||
use crate::topology::WasmNymTopology;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::client::base_client::ClientInput;
|
||||
use nym_client_core::client::base_client::{ClientInput, ClientState};
|
||||
use nym_client_core::client::inbound_messages::InputMessage;
|
||||
use nym_topology::{MixLayer, NymTopology};
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::{console_log, simple_js_error};
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymClientTestRequest {
|
||||
// serialized NodeTestMessage
|
||||
pub(crate) test_msgs: Vec<Vec<u8>>,
|
||||
|
||||
// specially constructed network topology that only contains the target
|
||||
// node on the tested layer
|
||||
pub(crate) testable_topology: NymTopology,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymClientTestRequest {
|
||||
pub fn injectable_topology(&self) -> WasmNymTopology {
|
||||
self.testable_topology.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
// defining helper trait as we could directly call the method on the wrapper
|
||||
pub(crate) trait InputSender {
|
||||
fn send_message(&self, message: InputMessage) -> Promise;
|
||||
|
||||
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise;
|
||||
}
|
||||
|
||||
impl InputSender for Arc<ClientInput> {
|
||||
@@ -19,12 +45,114 @@ impl InputSender for Arc<ClientInput> {
|
||||
future_to_promise(async move {
|
||||
match this.input_sender.send(message).await {
|
||||
Ok(_) => Ok(JsValue::null()),
|
||||
Err(_) => {
|
||||
let js_error =
|
||||
js_sys::Error::new("InputMessageReceiver has stopped receiving!");
|
||||
Err(JsValue::from(js_error))
|
||||
}
|
||||
Err(_) => Err(simple_js_error(
|
||||
"InputMessageReceiver has stopped receiving!",
|
||||
)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise {
|
||||
let this = Arc::clone(self);
|
||||
future_to_promise(async move {
|
||||
for message in messages {
|
||||
if this.input_sender.send(message).await.is_err() {
|
||||
return Err(simple_js_error(
|
||||
"InputMessageReceiver has stopped receiving!",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(JsValue::null())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait WasmTopologyExt {
|
||||
/// Changes the current network topology to the provided value.
|
||||
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise;
|
||||
|
||||
/// Returns the current network topology.
|
||||
fn current_topology(&self) -> Promise;
|
||||
|
||||
/// Checks whether the provided node exists in the known network topology and if so, returns its layer.
|
||||
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise;
|
||||
|
||||
/// Creates a `NymClientTestRequest` with a variant of `this` topology where the target node is the only one on its layer.
|
||||
fn mix_test_request(
|
||||
&self,
|
||||
test_id: u32,
|
||||
mixnode_identity: String,
|
||||
num_test_packets: Option<u32>,
|
||||
) -> Promise;
|
||||
}
|
||||
|
||||
impl WasmTopologyExt for Arc<ClientState> {
|
||||
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
|
||||
let this = Arc::clone(self);
|
||||
future_to_promise(async move {
|
||||
let nym_topology: NymTopology = topology.into();
|
||||
console_log!("changing topology to {nym_topology:?}");
|
||||
this.topology_accessor
|
||||
.manually_change_topology(nym_topology)
|
||||
.await;
|
||||
Ok(JsValue::null())
|
||||
})
|
||||
}
|
||||
|
||||
fn current_topology(&self) -> Promise {
|
||||
let this = Arc::clone(self);
|
||||
future_to_promise(async move {
|
||||
match this.topology_accessor.current_topology().await {
|
||||
Some(topology) => Ok(JsValue::from(WasmNymTopology::from(topology))),
|
||||
None => Err(WasmClientError::UnavailableNetworkTopology.into()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks whether the target mixnode exists in the known network topology and returns its layer.
|
||||
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise {
|
||||
let this = Arc::clone(self);
|
||||
future_to_promise(async move {
|
||||
let Some(current_topology) = this.topology_accessor.current_topology().await else {
|
||||
return Err(WasmClientError::UnavailableNetworkTopology.into())
|
||||
};
|
||||
|
||||
match current_topology.find_mix_by_identity(&mixnode_identity) {
|
||||
None => Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into()),
|
||||
Some(node) => Ok(JsValue::from(MixLayer::from(node.layer))),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn mix_test_request(
|
||||
&self,
|
||||
test_id: u32,
|
||||
mixnode_identity: String,
|
||||
num_test_packets: Option<u32>,
|
||||
) -> Promise {
|
||||
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
|
||||
|
||||
let this = Arc::clone(self);
|
||||
future_to_promise(async move {
|
||||
let Some(current_topology) = this.topology_accessor.current_topology().await else {
|
||||
return Err(WasmClientError::UnavailableNetworkTopology.into())
|
||||
};
|
||||
|
||||
let Some(mix) = current_topology.find_mix_by_identity(&mixnode_identity) else {
|
||||
return Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into());
|
||||
};
|
||||
|
||||
let ext = WasmTestMessageExt::new(test_id);
|
||||
let test_msgs = NodeTestMessage::mix_plaintexts(mix, num_test_packets, ext)
|
||||
.map_err(WasmClientError::from)?;
|
||||
|
||||
let mut updated = current_topology.clone();
|
||||
updated.set_mixes_in_layer(mix.layer.into(), vec![mix.to_owned()]);
|
||||
|
||||
Ok(JsValue::from(NymClientTestRequest {
|
||||
test_msgs,
|
||||
testable_topology: updated,
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use self::config::Config;
|
||||
use crate::client::helpers::InputSender;
|
||||
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt};
|
||||
use crate::client::response_pusher::ResponsePusher;
|
||||
use crate::constants::NODE_TESTER_CLIENT_ID;
|
||||
use crate::error::WasmClientError;
|
||||
use crate::helpers::{
|
||||
choose_gateway, gateway_from_topology, parse_recipient, parse_sender_tag,
|
||||
setup_reply_surb_storage_backend,
|
||||
};
|
||||
use crate::storage::traits::FullWasmClientStorage;
|
||||
use crate::storage::ClientStorage;
|
||||
use crate::topology::WasmNymTopology;
|
||||
use js_sys::Promise;
|
||||
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core::client::base_client::{
|
||||
BaseClientBuilder, ClientInput, ClientOutput, CredentialsToggle,
|
||||
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle,
|
||||
};
|
||||
use nym_client_core::client::inbound_messages::InputMessage;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskManager;
|
||||
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::{console_error, console_log};
|
||||
use wasm_utils::{check_promise_result, console_log, PromisableResult};
|
||||
|
||||
pub mod config;
|
||||
mod helpers;
|
||||
@@ -31,19 +43,26 @@ mod response_pusher;
|
||||
pub struct NymClient {
|
||||
self_address: String,
|
||||
client_input: Arc<ClientInput>,
|
||||
client_state: Arc<ClientState>,
|
||||
|
||||
// keep track of the "old" topology for the purposes of node tester
|
||||
// so that it could be restored after the check is done
|
||||
_full_topology: Option<NymTopology>,
|
||||
|
||||
// even though we don't use graceful shutdowns, other components rely on existence of this struct
|
||||
// and if it's dropped, everything will start going offline
|
||||
_task_manager: TaskManager,
|
||||
|
||||
packet_type: PacketType,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymClientBuilder {
|
||||
config: Config,
|
||||
custom_topology: Option<NymTopology>,
|
||||
preferred_gateway: Option<IdentityKey>,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
|
||||
storage_passphrase: Option<String>,
|
||||
reply_surb_storage_backend: browser_backend::Backend,
|
||||
|
||||
on_message: js_sys::Function,
|
||||
@@ -57,121 +76,214 @@ pub struct NymClientBuilder {
|
||||
#[wasm_bindgen]
|
||||
impl NymClientBuilder {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(config: Config, on_message: js_sys::Function) -> Self {
|
||||
//, key_manager: Option<KeyManager>) {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
on_message: js_sys::Function,
|
||||
preferred_gateway: Option<IdentityKey>,
|
||||
storage_passphrase: Option<String>,
|
||||
) -> Self {
|
||||
NymClientBuilder {
|
||||
reply_surb_storage_backend: Self::setup_reply_surb_storage_backend(&config),
|
||||
reply_surb_storage_backend: setup_reply_surb_storage_backend(
|
||||
config.base.debug.reply_surbs,
|
||||
),
|
||||
config,
|
||||
key_manager: Self::setup_key_manager(),
|
||||
custom_topology: None,
|
||||
storage_passphrase,
|
||||
on_message,
|
||||
bandwidth_controller: None,
|
||||
disabled_credentials: true,
|
||||
preferred_gateway,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
|
||||
// a prior shared keypair between the client and the gateway
|
||||
// no cover traffic
|
||||
// no poisson delay
|
||||
// hardcoded topology
|
||||
// NOTE: you most likely want to use `[NymNodeTester]` instead.
|
||||
pub fn new_tester(
|
||||
topology: WasmNymTopology,
|
||||
on_message: js_sys::Function,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> Self {
|
||||
if let Some(gateway_id) = &gateway {
|
||||
if !topology.ensure_contains_gateway_id(gateway_id) {
|
||||
panic!("the specified topology does not contain the gateway used by the client")
|
||||
}
|
||||
}
|
||||
|
||||
// perhaps this should be public?
|
||||
fn setup_key_manager() -> KeyManager {
|
||||
let mut rng = OsRng;
|
||||
// for time being generate new keys each time...
|
||||
console_log!("generated new set of keys");
|
||||
KeyManager::new(&mut rng)
|
||||
}
|
||||
let full_config = Config::new_tester_config(NODE_TESTER_CLIENT_ID);
|
||||
|
||||
// don't get too excited about the name, under the hood it's just a big fat placeholder
|
||||
// with no persistence
|
||||
fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
|
||||
browser_backend::Backend::new(
|
||||
config
|
||||
.debug
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
config
|
||||
.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
)
|
||||
NymClientBuilder {
|
||||
reply_surb_storage_backend: setup_reply_surb_storage_backend(
|
||||
full_config.base.debug.reply_surbs,
|
||||
),
|
||||
config: full_config,
|
||||
custom_topology: Some(topology.into()),
|
||||
on_message,
|
||||
bandwidth_controller: None,
|
||||
disabled_credentials: true,
|
||||
storage_passphrase: None,
|
||||
preferred_gateway: gateway,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_reconstructed_pusher(client_output: ClientOutput, on_message: js_sys::Function) {
|
||||
ResponsePusher::new(client_output, on_message).start()
|
||||
}
|
||||
|
||||
pub async fn start_client(self) -> Promise {
|
||||
future_to_promise(async move {
|
||||
console_log!("Starting the wasm client");
|
||||
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider + Send + Sync>> {
|
||||
if let Some(hardcoded_topology) = self.custom_topology.take() {
|
||||
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let disabled_credentials = if self.disabled_credentials {
|
||||
CredentialsToggle::Disabled
|
||||
} else {
|
||||
CredentialsToggle::Enabled
|
||||
};
|
||||
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
|
||||
console_log!("Starting the wasm client");
|
||||
|
||||
let base_builder = BaseClientBuilder::new(
|
||||
&self.config.gateway_endpoint,
|
||||
&self.config.debug,
|
||||
self.key_manager,
|
||||
self.bandwidth_controller,
|
||||
self.reply_surb_storage_backend,
|
||||
disabled_credentials,
|
||||
vec![self.config.nym_api_url.clone()],
|
||||
);
|
||||
let disabled_credentials = if self.disabled_credentials {
|
||||
CredentialsToggle::Disabled
|
||||
} else {
|
||||
CredentialsToggle::Enabled
|
||||
};
|
||||
|
||||
let self_address = base_builder.as_mix_recipient().to_string();
|
||||
let mut started_client = match base_builder.start_base().await {
|
||||
Ok(base_client) => base_client,
|
||||
Err(err) => {
|
||||
let error_msg = format!("failed to start the base client components - {err}");
|
||||
console_error!("{}", error_msg);
|
||||
let js_error = js_sys::Error::new(&error_msg);
|
||||
return Err(JsValue::from(js_error));
|
||||
}
|
||||
};
|
||||
let nym_api_endpoints = self.config.base.client.nym_api_urls.clone();
|
||||
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
// TODO: this will have to be re-used for surbs. but this is a problem for another PR.
|
||||
let client_store =
|
||||
ClientStorage::new_async(&self.config.base.client.id, self.storage_passphrase.take())
|
||||
.await?;
|
||||
|
||||
Self::start_reconstructed_pusher(client_output, self.on_message);
|
||||
// if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way
|
||||
let gateway_endpoint = if let Some(topology) = &self.custom_topology {
|
||||
gateway_from_topology(
|
||||
&mut thread_rng(),
|
||||
self.preferred_gateway.as_deref(),
|
||||
topology,
|
||||
&client_store,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
choose_gateway(
|
||||
&client_store,
|
||||
self.preferred_gateway.clone(),
|
||||
&nym_api_endpoints,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
Ok(JsValue::from(NymClient {
|
||||
self_address,
|
||||
client_input: Arc::new(client_input),
|
||||
_task_manager: started_client.task_manager,
|
||||
}))
|
||||
let maybe_topology_provider = self.topology_provider();
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = BaseClientBuilder::new(
|
||||
&gateway_endpoint,
|
||||
&self.config.base.debug,
|
||||
client_store,
|
||||
self.bandwidth_controller,
|
||||
self.reply_surb_storage_backend,
|
||||
disabled_credentials,
|
||||
nym_api_endpoints,
|
||||
);
|
||||
if let Some(topology_provider) = maybe_topology_provider {
|
||||
base_builder = base_builder.with_topology_provider(topology_provider);
|
||||
}
|
||||
|
||||
let mut started_client = base_builder.start_base().await?;
|
||||
let self_address = started_client.address.to_string();
|
||||
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
|
||||
Self::start_reconstructed_pusher(client_output, self.on_message);
|
||||
|
||||
Ok(NymClient {
|
||||
self_address,
|
||||
client_input: Arc::new(client_input),
|
||||
client_state: Arc::new(started_client.client_state),
|
||||
_full_topology: None,
|
||||
_task_manager: started_client.task_manager,
|
||||
packet_type: self.config.base.debug.traffic.packet_type,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_client(self) -> Promise {
|
||||
future_to_promise(async move { self.start_client_async().await.into_promise_result() })
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymClient {
|
||||
async fn _new(
|
||||
config: Config,
|
||||
on_message: js_sys::Function,
|
||||
preferred_gateway: Option<IdentityKey>,
|
||||
storage_passphrase: Option<String>,
|
||||
) -> Result<NymClient, WasmClientError> {
|
||||
NymClientBuilder::new(config, on_message, preferred_gateway, storage_passphrase)
|
||||
.start_client_async()
|
||||
.await
|
||||
}
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
config: Config,
|
||||
on_message: js_sys::Function,
|
||||
preferred_gateway: Option<IdentityKey>,
|
||||
storage_passphrase: Option<String>,
|
||||
) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::_new(config, on_message, preferred_gateway, storage_passphrase)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn self_address(&self) -> String {
|
||||
self.self_address.clone()
|
||||
}
|
||||
|
||||
fn parse_recipient(recipient: &str) -> Result<Recipient, JsValue> {
|
||||
match Recipient::try_from_base58_string(recipient) {
|
||||
Ok(recipient) => Ok(recipient),
|
||||
Err(err) => {
|
||||
let error_msg = format!("{recipient} is not a valid Nym network recipient - {err}");
|
||||
console_error!("{}", error_msg);
|
||||
let js_error = js_sys::Error::new(&error_msg);
|
||||
Err(JsValue::from(js_error))
|
||||
}
|
||||
}
|
||||
pub fn try_construct_test_packet_request(
|
||||
&self,
|
||||
mixnode_identity: String,
|
||||
num_test_packets: Option<u32>,
|
||||
) -> Promise {
|
||||
// TODO: improve the source of rng (i.e. don't make it ephemeral...)
|
||||
let mut ephemeral_rng = OsRng;
|
||||
let test_id = ephemeral_rng.next_u32();
|
||||
self.client_state
|
||||
.mix_test_request(test_id, mixnode_identity, num_test_packets)
|
||||
}
|
||||
|
||||
fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, JsValue> {
|
||||
match AnonymousSenderTag::try_from_base58_string(tag) {
|
||||
Ok(tag) => Ok(tag),
|
||||
Err(err) => {
|
||||
let error_msg = format!("{tag} is not a valid Nym AnonymousSenderTag - {err}");
|
||||
console_error!("{}", error_msg);
|
||||
let js_error = js_sys::Error::new(&error_msg);
|
||||
Err(JsValue::from(js_error))
|
||||
}
|
||||
}
|
||||
pub fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
|
||||
self.client_state.change_hardcoded_topology(topology)
|
||||
}
|
||||
|
||||
pub fn current_network_topology(&self) -> Promise {
|
||||
self.client_state.current_topology()
|
||||
}
|
||||
|
||||
/// Sends a test packet through the current network topology.
|
||||
/// It's the responsibility of the caller to ensure the correct topology has been injected and
|
||||
/// correct onmessage handlers have been setup.
|
||||
pub fn try_send_test_packets(&mut self, request: NymClientTestRequest) -> Promise {
|
||||
// TOOD: use the premade packets instead
|
||||
console_log!(
|
||||
"Attempting to send {} test packets",
|
||||
request.test_msgs.len()
|
||||
);
|
||||
|
||||
// our address MUST BE valid
|
||||
let recipient = parse_recipient(&self.self_address()).unwrap();
|
||||
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msgs = request
|
||||
.test_msgs
|
||||
.into_iter()
|
||||
.map(|p| InputMessage::new_regular(recipient, p, lane, None))
|
||||
.collect();
|
||||
|
||||
self.client_input.send_messages(input_msgs)
|
||||
}
|
||||
|
||||
/// The simplest message variant where no additional information is attached.
|
||||
@@ -184,13 +296,11 @@ impl NymClient {
|
||||
message.len() as f64 / 1024.0
|
||||
);
|
||||
|
||||
let recipient = match Self::parse_recipient(&recipient) {
|
||||
Ok(recipient) => recipient,
|
||||
Err(err) => return Promise::reject(&err),
|
||||
};
|
||||
let recipient = check_promise_result!(parse_recipient(&recipient));
|
||||
|
||||
let lane = TransmissionLane::General;
|
||||
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane);
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
|
||||
self.client_input.send_message(input_msg)
|
||||
}
|
||||
|
||||
@@ -213,13 +323,17 @@ impl NymClient {
|
||||
message.len() as f64 / 1024.0
|
||||
);
|
||||
|
||||
let recipient = match Self::parse_recipient(&recipient) {
|
||||
Ok(recipient) => recipient,
|
||||
Err(err) => return Promise::reject(&err),
|
||||
};
|
||||
let recipient = check_promise_result!(parse_recipient(&recipient));
|
||||
|
||||
let lane = TransmissionLane::General;
|
||||
|
||||
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
|
||||
let input_msg = InputMessage::new_anonymous(
|
||||
recipient,
|
||||
message,
|
||||
reply_surbs,
|
||||
lane,
|
||||
Some(self.packet_type),
|
||||
);
|
||||
self.client_input.send_message(input_msg)
|
||||
}
|
||||
|
||||
@@ -233,13 +347,11 @@ impl NymClient {
|
||||
message.len() as f64 / 1024.0
|
||||
);
|
||||
|
||||
let sender_tag = match Self::parse_sender_tag(&recipient_tag) {
|
||||
Ok(recipient) => recipient,
|
||||
Err(err) => return Promise::reject(&err),
|
||||
};
|
||||
let sender_tag = check_promise_result!(parse_sender_tag(&recipient_tag));
|
||||
|
||||
let lane = TransmissionLane::General;
|
||||
|
||||
let input_msg = InputMessage::new_reply(sender_tag, message, lane);
|
||||
let input_msg = InputMessage::new_reply(sender_tag, message, lane, Some(self.packet_type));
|
||||
self.client_input.send_message(input_msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
|
||||
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::errors::ClientStorageError;
|
||||
use crate::topology::WasmTopologyError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_node_tester_utils::error::NetworkTestingError;
|
||||
use nym_sphinx::addressing::clients::RecipientFormattingError;
|
||||
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
|
||||
use nym_topology::NymTopologyError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::simple_js_error;
|
||||
|
||||
// might as well start using well-defined error enum...
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WasmClientError {
|
||||
#[error(
|
||||
"A node test is already in progress. Wait for it to finish before starting another one."
|
||||
)]
|
||||
TestInProgress,
|
||||
|
||||
#[error("experienced an issue with internal client components: {source}")]
|
||||
BaseClientError {
|
||||
#[from]
|
||||
source: ClientCoreError,
|
||||
},
|
||||
|
||||
#[error("The provided gateway identity is invalid: {source}")]
|
||||
InvalidGatewayIdentity { source: Ed25519RecoveryError },
|
||||
|
||||
#[error("Gateway communication failure: {source}")]
|
||||
GatewayClientError {
|
||||
#[from]
|
||||
source: GatewayClientError,
|
||||
},
|
||||
|
||||
#[error("failed to query nym api: {source}")]
|
||||
NymApiError {
|
||||
#[from]
|
||||
source: ValidatorClientError,
|
||||
},
|
||||
|
||||
#[error("The provided wasm topology was invalid: {source}")]
|
||||
WasmTopologyError {
|
||||
#[from]
|
||||
source: WasmTopologyError,
|
||||
},
|
||||
|
||||
#[error("The provided nym topology was invalid: {source}")]
|
||||
TopologyError {
|
||||
#[from]
|
||||
source: NymTopologyError,
|
||||
},
|
||||
|
||||
#[error("failed to test the node: {source}")]
|
||||
NodeTestingFailure {
|
||||
#[from]
|
||||
source: NetworkTestingError,
|
||||
},
|
||||
|
||||
#[error("{raw} is not a valid url: {source}")]
|
||||
MalformedUrl {
|
||||
raw: String,
|
||||
source: url::ParseError,
|
||||
},
|
||||
|
||||
#[error("Network topology is currently unavailable")]
|
||||
UnavailableNetworkTopology,
|
||||
|
||||
#[error("Mixnode {mixnode_identity} is not present in the current network topology")]
|
||||
NonExistentMixnode { mixnode_identity: String },
|
||||
|
||||
#[error("Gateway {gateway_identity} is not present in the current network topology")]
|
||||
NonExistentGateway { gateway_identity: String },
|
||||
|
||||
#[error("{raw} is not a valid Nym network recipient: {source}")]
|
||||
MalformedRecipient {
|
||||
raw: String,
|
||||
source: RecipientFormattingError,
|
||||
},
|
||||
|
||||
#[error("{raw} is not a valid Nym AnonymousSenderTag: {source}")]
|
||||
MalformedSenderTag {
|
||||
raw: String,
|
||||
source: InvalidAnonymousSenderTagRepresentation,
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: ClientStorageError,
|
||||
},
|
||||
|
||||
#[error("this client has already registered with a gateway: {gateway_config:?}")]
|
||||
AlreadyRegistered {
|
||||
gateway_config: GatewayEndpointConfig,
|
||||
},
|
||||
}
|
||||
|
||||
impl WasmClientError {
|
||||
pub fn into_rejected_promise(self) -> Promise {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmClientError> for JsValue {
|
||||
fn from(value: WasmClientError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmClientError> for Promise {
|
||||
fn from(value: WasmClientError) -> Self {
|
||||
Promise::reject(&value.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::WasmClientError;
|
||||
use crate::storage::ClientStorage;
|
||||
use crate::topology::WasmNymTopology;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_client_core::config;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::client::{IdentityKey, IdentityKeyRef};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::{console_log, PromisableResult};
|
||||
|
||||
// don't get too excited about the name, under the hood it's just a big fat placeholder
|
||||
// with no disk_persistence
|
||||
pub(crate) fn setup_reply_surb_storage_backend(
|
||||
config: config::ReplySurbs,
|
||||
) -> browser_backend::Backend {
|
||||
browser_backend::Backend::new(
|
||||
config.minimum_reply_surb_storage_threshold,
|
||||
config.maximum_reply_surb_storage_threshold,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_recipient(recipient: &str) -> Result<Recipient, WasmClientError> {
|
||||
Recipient::try_from_base58_string(recipient).map_err(|source| {
|
||||
WasmClientError::MalformedRecipient {
|
||||
raw: recipient.to_string(),
|
||||
source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmClientError> {
|
||||
AnonymousSenderTag::try_from_base58_string(tag).map_err(|source| {
|
||||
WasmClientError::MalformedSenderTag {
|
||||
raw: tag.to_string(),
|
||||
source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn current_network_topology_async(
|
||||
nym_api_url: String,
|
||||
) -> Result<WasmNymTopology, WasmClientError> {
|
||||
let url: Url = match nym_api_url.parse() {
|
||||
Ok(url) => url,
|
||||
Err(source) => {
|
||||
return Err(WasmClientError::MalformedUrl {
|
||||
raw: nym_api_url,
|
||||
source,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let api_client = NymApiClient::new(url);
|
||||
let mixnodes = api_client.get_cached_active_mixnodes().await?;
|
||||
let gateways = api_client.get_cached_gateways().await?;
|
||||
|
||||
Ok(NymTopology::from_detailed(mixnodes, gateways).into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn current_network_topology(nym_api_url: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
current_network_topology_async(nym_api_url)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn choose_gateway(
|
||||
client_store: &ClientStorage,
|
||||
chosen_gateway: Option<IdentityKey>,
|
||||
nym_apis: &[Url],
|
||||
) -> Result<GatewayEndpointConfig, WasmClientError> {
|
||||
let existing_gateway_config = client_store.read_gateway_config().await?;
|
||||
|
||||
console_log!("loaded: {:?}", existing_gateway_config);
|
||||
|
||||
if let Some(existing) = existing_gateway_config {
|
||||
if let Some(provided) = &chosen_gateway {
|
||||
if provided != &existing.gateway_id {
|
||||
return Err(WasmClientError::AlreadyRegistered {
|
||||
gateway_config: existing,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Ok(existing);
|
||||
};
|
||||
|
||||
// if NOTHING is specified nor available, choose gateway randomly.
|
||||
let setup = GatewaySetup::new(None, chosen_gateway, None);
|
||||
let config = setup.try_get_gateway_details(nym_apis).await?;
|
||||
|
||||
// perform registration + persist the new gateway info
|
||||
// TODO: this is actually quite bad. we shouldn't be persisting gateway info here since we did not have persisted
|
||||
// the shared key yet. this will only happen when we start the base client itself.
|
||||
// but unfortunately, we can't do much more until we do a bit more refactoring.
|
||||
client_store.store_gateway_config(&config).await?;
|
||||
|
||||
console_log!("stored: {:?}", config);
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub(crate) async fn gateway_from_topology<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
explicit_gateway: Option<IdentityKeyRef<'_>>,
|
||||
topology: &NymTopology,
|
||||
client_store: &ClientStorage,
|
||||
) -> Result<GatewayEndpointConfig, WasmClientError> {
|
||||
let existing_gateway_config = client_store.read_gateway_config().await?;
|
||||
console_log!("loaded: {:?}", existing_gateway_config);
|
||||
|
||||
let new_gateway: GatewayEndpointConfig = if let Some(provided) = explicit_gateway {
|
||||
if let Some(existing) = existing_gateway_config {
|
||||
// we have stored gateway info and explicitly provided identity key
|
||||
//
|
||||
// check if they match, otherwise return an error
|
||||
return if provided != existing.gateway_id {
|
||||
Err(WasmClientError::AlreadyRegistered {
|
||||
gateway_config: existing,
|
||||
})
|
||||
} else {
|
||||
Ok(existing)
|
||||
};
|
||||
} else {
|
||||
// we have explicitly provided identity key and didn't have any prior stored data
|
||||
//
|
||||
// try to grab details from the topology
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(provided)
|
||||
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
|
||||
if let Some(gateway) = topology.get_gateway(&gateway_identity) {
|
||||
gateway.clone().into()
|
||||
} else {
|
||||
return Err(WasmClientError::NonExistentGateway {
|
||||
gateway_identity: gateway_identity.to_base58_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if let Some(existing) = existing_gateway_config {
|
||||
// we have stored data and didn't provide anything separately - use what's stored!
|
||||
return Ok(existing);
|
||||
} else {
|
||||
// we don't have anything stored nor we have provided anything
|
||||
//
|
||||
// just grab random gateway from our topology
|
||||
topology.random_gateway(rng)?.clone().into()
|
||||
};
|
||||
|
||||
console_log!("storing: {:?}", new_gateway);
|
||||
client_store.store_gateway_config(&new_gateway).await?;
|
||||
Ok(new_gateway)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
@@ -8,10 +8,23 @@ mod client;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod encoded_payload_helper;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod error;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod gateway_selector;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod storage;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod tester;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod topology;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod validation;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod helpers;
|
||||
|
||||
mod constants;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::simple_js_error;
|
||||
use wasm_utils::storage::error::StorageError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientStorageError {
|
||||
#[error("failed to use the storage: {source}")]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error("{typ} cryptographic key is not available in storage")]
|
||||
CryptoKeyNotInStorage { typ: String },
|
||||
}
|
||||
|
||||
impl From<ClientStorageError> for JsValue {
|
||||
fn from(value: ClientStorageError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::errors::ClientStorageError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
|
||||
use wasm_utils::PromisableResult;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub(crate) mod errors;
|
||||
pub(crate) mod traits;
|
||||
|
||||
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
|
||||
const STORAGE_VERSION: u32 = 1;
|
||||
|
||||
// v1 tables
|
||||
mod v1 {
|
||||
// stores
|
||||
pub const KEYS_STORE: &str = "keys";
|
||||
pub const CORE_STORE: &str = "core";
|
||||
|
||||
// keys
|
||||
// TODO: to replace with FULL config
|
||||
pub const GATEWAY_CONFIG: &str = "gateway_config";
|
||||
|
||||
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
|
||||
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
|
||||
|
||||
// TODO: for those we could actually use the subtle crypto storage
|
||||
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
|
||||
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClientStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) name: String,
|
||||
pub(crate) inner: Arc<WasmStorage>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ClientStorage {
|
||||
fn db_name(client_id: &str) -> String {
|
||||
format!("{STORAGE_NAME_PREFIX}-{client_id}")
|
||||
}
|
||||
|
||||
pub(crate) async fn new_async(
|
||||
client_id: &str,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<Self, ClientStorageError> {
|
||||
let name = Self::db_name(client_id);
|
||||
|
||||
// make sure the password is zeroized when no longer used, especially if we error out.
|
||||
// special care must be taken on JS side to ensure it's correctly used there.
|
||||
let passphrase = Zeroizing::new(passphrase);
|
||||
|
||||
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let old_version = evt.old_version() as u32;
|
||||
|
||||
if old_version < 1 {
|
||||
// migrating to version 1
|
||||
let db = evt.db();
|
||||
|
||||
db.create_object_store(v1::KEYS_STORE)?;
|
||||
db.create_object_store(v1::CORE_STORE)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let inner = WasmStorage::new(
|
||||
&name,
|
||||
STORAGE_VERSION,
|
||||
migrate_fn,
|
||||
passphrase.as_ref().map(|p| p.as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(ClientStorage {
|
||||
inner: Arc::new(inner),
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(client_id: String, passphrase: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, Some(passphrase))
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_unencrypted(client_id: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, None)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn read_gateway_config(
|
||||
&self,
|
||||
) -> Result<Option<GatewayEndpointConfig>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_CONFIG))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_identity_keypair(
|
||||
&self,
|
||||
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<Option<encryption::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, ClientStorageError> {
|
||||
self.may_read_identity_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<encryption::KeyPair, ClientStorageError> {
|
||||
self.may_read_encryption_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_ack_key(&self) -> Result<AckKey, ClientStorageError> {
|
||||
self.may_read_ack_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_ACK_KEY.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, ClientStorageError> {
|
||||
self.may_read_gateway_shared_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn store_identity_keypair(
|
||||
&self,
|
||||
keypair: &identity::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_encryption_keypair(
|
||||
&self,
|
||||
keypair: &encryption::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_ack_key(&self, key: &AckKey) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_ACK_KEY),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn store_gateway_config(
|
||||
&self,
|
||||
gateway_endpoint: &GatewayEndpointConfig,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::CORE_STORE,
|
||||
JsValue::from_str(v1::GATEWAY_CONFIG),
|
||||
gateway_endpoint,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::errors::ClientStorageError;
|
||||
use crate::storage::ClientStorage;
|
||||
use async_trait::async_trait;
|
||||
use nym_client_core::client::base_client::storage::MixnetClientStorage;
|
||||
use nym_client_core::client::key_manager::persistence::KeyStore;
|
||||
use nym_client_core::client::key_manager::KeyManager;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use wasm_utils::console_log;
|
||||
|
||||
// temporary until other variants are properly implemented (probably it should get changed into `ClientStorage`
|
||||
// implementing all traits and everything getting combined
|
||||
pub struct FullWasmClientStorage {
|
||||
key_store: ClientStorage,
|
||||
reply_storage: browser_backend::Backend,
|
||||
credential_storage: EphemeralCredentialStorage,
|
||||
}
|
||||
|
||||
impl MixnetClientStorage for FullWasmClientStorage {
|
||||
type KeyStore = ClientStorage;
|
||||
type ReplyStore = browser_backend::Backend;
|
||||
type CredentialStore = EphemeralCredentialStorage;
|
||||
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
|
||||
(self.key_store, self.reply_storage, self.credential_storage)
|
||||
}
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore {
|
||||
&self.key_store
|
||||
}
|
||||
|
||||
fn reply_store(&self) -> &Self::ReplyStore {
|
||||
&self.reply_storage
|
||||
}
|
||||
|
||||
fn credential_store(&self) -> &Self::CredentialStore {
|
||||
&self.credential_storage
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl KeyStore for ClientStorage {
|
||||
type StorageError = ClientStorageError;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
|
||||
console_log!("attempting to load cryptographic keys...");
|
||||
|
||||
// all keys implement `ZeroizeOnDrop`, so if we return an Error, whatever was already loaded will be cleared
|
||||
let identity_keypair = self.must_read_identity_keypair().await?;
|
||||
let encryption_keypair = self.must_read_encryption_keypair().await?;
|
||||
let ack_keypair = self.must_read_ack_key().await?;
|
||||
let gateway_shared_key = self.must_read_gateway_shared_key().await?;
|
||||
|
||||
Ok(KeyManager::from_keys(
|
||||
identity_keypair,
|
||||
encryption_keypair,
|
||||
gateway_shared_key,
|
||||
ack_keypair,
|
||||
))
|
||||
}
|
||||
|
||||
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
|
||||
console_log!("attempting to store cryptographic keys...");
|
||||
|
||||
self.store_identity_keypair(&keys.identity_keypair())
|
||||
.await?;
|
||||
self.store_encryption_keypair(&keys.encryption_keypair())
|
||||
.await?;
|
||||
self.store_ack_key(&keys.ack_key()).await?;
|
||||
self.store_gateway_shared_key(&keys.gateway_shared_key())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tester::helpers::{NodeTestResult, WasmTestMessageExt};
|
||||
use futures::StreamExt;
|
||||
use nym_node_tester_utils::processor::Received;
|
||||
use nym_node_tester_utils::receiver::ReceivedReceiver;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::MutexGuard as AsyncMutexGuard;
|
||||
use wasm_utils::{console_error, console_log, console_warn};
|
||||
|
||||
pub(crate) struct EphemeralTestReceiver<'a> {
|
||||
sent_packets: u32,
|
||||
expected_acks: HashSet<FragmentIdentifier>,
|
||||
|
||||
received_valid_messages: HashSet<u32>,
|
||||
received_valid_acks: HashSet<FragmentIdentifier>,
|
||||
duplicate_packets: u32,
|
||||
duplicate_acks: u32,
|
||||
|
||||
timeout_duration: Duration,
|
||||
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
|
||||
}
|
||||
|
||||
impl<'a> EphemeralTestReceiver<'a> {
|
||||
pub(crate) fn finish(self) -> NodeTestResult {
|
||||
NodeTestResult {
|
||||
sent_packets: self.sent_packets,
|
||||
received_packets: self.received_valid_messages.len() as u32,
|
||||
received_acks: self.received_valid_acks.len() as u32,
|
||||
duplicate_packets: self.duplicate_packets,
|
||||
duplicate_acks: self.duplicate_acks,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
sent_packets: u32,
|
||||
expected_acks: HashSet<FragmentIdentifier>,
|
||||
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
EphemeralTestReceiver {
|
||||
sent_packets,
|
||||
expected_acks,
|
||||
received_valid_messages: Default::default(),
|
||||
received_valid_acks: Default::default(),
|
||||
duplicate_packets: 0,
|
||||
duplicate_acks: 0,
|
||||
timeout_duration: timeout,
|
||||
receiver_permit,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_next_received_packet(&mut self, packet: Option<Received<WasmTestMessageExt>>) -> bool {
|
||||
let Some(received_packet) = packet else {
|
||||
// can't do anything more...
|
||||
console_error!("packet receiver has stopped processing results!");
|
||||
return true
|
||||
};
|
||||
match received_packet {
|
||||
Received::Message(msg) => {
|
||||
if !self.received_valid_messages.insert(msg.msg_id) {
|
||||
self.duplicate_packets += 1;
|
||||
}
|
||||
}
|
||||
Received::Ack(frag_id) => {
|
||||
if self.expected_acks.contains(&frag_id) {
|
||||
if !self.received_valid_acks.insert(frag_id) {
|
||||
self.duplicate_acks += 1
|
||||
}
|
||||
} else {
|
||||
console_warn!("received an ack that was not part of the test! (id: {frag_id})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.received_all() {
|
||||
console_log!("already received all the packets! finishing the test...");
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn received_all(&self) -> bool {
|
||||
self.received_valid_acks.len() == self.received_valid_messages.len()
|
||||
&& self.received_valid_acks.len() == self.sent_packets as usize
|
||||
}
|
||||
|
||||
pub(crate) async fn perform_test(mut self) -> NodeTestResult {
|
||||
let mut timeout_fut = wasm_timer::Delay::new(self.timeout_duration);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut timeout_fut => {
|
||||
console_warn!("reached test timeout before receiving all packets.");
|
||||
break
|
||||
}
|
||||
received_packet = self.receiver_permit.next() => {
|
||||
let is_done = self.on_next_received_packet(received_packet);
|
||||
if is_done {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
|
||||
use nym_node_tester_utils::processor::Received;
|
||||
use nym_node_tester_utils::receiver::ReceivedReceiver;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver<WasmTestMessageExt>>>);
|
||||
|
||||
impl ReceivedReceiverWrapper {
|
||||
pub(super) fn new(inner: ReceivedReceiver<WasmTestMessageExt>) -> Self {
|
||||
ReceivedReceiverWrapper(Arc::new(AsyncMutex::new(inner)))
|
||||
}
|
||||
|
||||
pub(super) async fn clear_received_channel(&self) {
|
||||
let mut lost_msgs = 0;
|
||||
let mut lost_acks = 0;
|
||||
let mut permit = self.0.lock().await;
|
||||
while let Ok(Some(received)) = permit.try_next() {
|
||||
match received {
|
||||
Received::Message(_) => lost_msgs += 1,
|
||||
Received::Ack(_) => lost_acks += 1,
|
||||
}
|
||||
}
|
||||
if lost_msgs > 0 || lost_acks > 0 {
|
||||
console_warn!("while preparing for the test run, we cleared {lost_msgs} messages and {lost_acks} acks that were received in the meantime.")
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver<WasmTestMessageExt>> {
|
||||
self.0.lock().await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct WasmTestMessageExt {
|
||||
pub test_id: u32,
|
||||
}
|
||||
|
||||
impl WasmTestMessageExt {
|
||||
pub fn new(test_id: u32) -> Self {
|
||||
WasmTestMessageExt { test_id }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe put it in the tester utils
|
||||
#[wasm_bindgen]
|
||||
pub struct NodeTestResult {
|
||||
pub sent_packets: u32,
|
||||
pub received_packets: u32,
|
||||
pub received_acks: u32,
|
||||
|
||||
pub duplicate_packets: u32,
|
||||
pub duplicate_acks: u32,
|
||||
}
|
||||
|
||||
impl Display for NodeTestResult {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Test results: ")?;
|
||||
writeln!(f, "Total score: {:.2}%", self.score())?;
|
||||
writeln!(f, "Sent packets: {}", self.sent_packets)?;
|
||||
writeln!(f, "Received (valid) packets: {}", self.received_packets)?;
|
||||
writeln!(f, "Received (valid) acks: {}", self.received_acks)?;
|
||||
writeln!(f, "Received duplicate packets: {}", self.duplicate_packets)?;
|
||||
write!(f, "Received duplicate acks: {}", self.duplicate_acks)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NodeTestResult {
|
||||
pub fn log_details(&self) {
|
||||
console_log!("{}", self)
|
||||
}
|
||||
|
||||
pub fn score(&self) -> f32 {
|
||||
let expected = self.sent_packets * 2;
|
||||
let actual = (self.received_packets + self.received_acks)
|
||||
.saturating_sub(self.duplicate_packets + self.duplicate_acks);
|
||||
|
||||
actual as f32 / expected as f32 * 100.
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TestMarker {
|
||||
value: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl TestMarker {
|
||||
pub fn new(value: Arc<AtomicBool>) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestMarker {
|
||||
// make sure to clear the test flag when the marker is dropped
|
||||
fn drop(&mut self) {
|
||||
self.value.store(false, Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::constants::NODE_TESTER_ID;
|
||||
use crate::error::WasmClientError;
|
||||
use crate::helpers::{current_network_topology_async, gateway_from_topology};
|
||||
use crate::storage::ClientStorage;
|
||||
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
|
||||
use crate::tester::helpers::{
|
||||
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
|
||||
};
|
||||
use crate::topology::WasmNymTopology;
|
||||
use futures::channel::mpsc;
|
||||
use js_sys::Promise;
|
||||
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core::client::key_manager::ManagedKeys;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
|
||||
use nym_node_tester_utils::{NodeTester, TestMessage};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_task::TaskManager;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Mutex as SyncMutex};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::{check_promise_result, console_log, PromisableResult};
|
||||
|
||||
mod ephemeral_receiver;
|
||||
pub(crate) mod helpers;
|
||||
|
||||
pub type NodeTestMessage = TestMessage<WasmTestMessageExt>;
|
||||
type LockedGatewayClient =
|
||||
Arc<AsyncMutex<GatewayClient<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>>;
|
||||
|
||||
pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymNodeTester {
|
||||
test_in_progress: Arc<AtomicBool>,
|
||||
|
||||
// we need to increment the nonce between tests to distinguish the packets
|
||||
// but we can't make the tester mutable because of wasm...
|
||||
// so we're using the atomics
|
||||
current_test_nonce: AtomicU32,
|
||||
|
||||
// blame all those mutexes on being unable to have an async method with internal mutability...
|
||||
tester: Arc<SyncMutex<NodeTester<OsRng>>>,
|
||||
gateway_client: LockedGatewayClient,
|
||||
|
||||
// we have to put it behind the lock due to wasm limitations and borrowing...
|
||||
// the mutex acquisition should be instant as there aren't going to be any threads attempting
|
||||
// to get simultaneous access
|
||||
processed_receiver: ReceivedReceiverWrapper,
|
||||
|
||||
// even though we don't use graceful shutdowns, other components rely on existence of this struct
|
||||
// and if it's dropped, everything will start going offline
|
||||
_task_manager: TaskManager,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymNodeTesterBuilder {
|
||||
gateway: Option<IdentityKey>,
|
||||
|
||||
base_topology: NymTopology,
|
||||
|
||||
// unimplemented
|
||||
bandwidth_controller:
|
||||
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
|
||||
}
|
||||
|
||||
fn address(keys: &ManagedKeys, gateway_identity: NodeIdentity) -> Recipient {
|
||||
Recipient::new(
|
||||
*keys.identity_public_key(),
|
||||
*keys.encryption_public_key(),
|
||||
gateway_identity,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymNodeTesterBuilder {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
base_topology: WasmNymTopology,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> NymNodeTesterBuilder {
|
||||
NymNodeTesterBuilder {
|
||||
gateway,
|
||||
base_topology: base_topology.into(),
|
||||
bandwidth_controller: None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn _new_with_api(
|
||||
api_url: String,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> Result<Self, WasmClientError> {
|
||||
let topology = current_network_topology_async(api_url).await?;
|
||||
Ok(NymNodeTesterBuilder::new(topology, gateway))
|
||||
}
|
||||
|
||||
pub fn new_with_api(gateway: Option<IdentityKey>, api_url: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::_new_with_api(api_url, gateway)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
async fn gateway_info<R: Rng + CryptoRng>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
client_store: &ClientStorage,
|
||||
) -> Result<GatewayEndpointConfig, WasmClientError> {
|
||||
gateway_from_topology(
|
||||
rng,
|
||||
self.gateway.as_deref(),
|
||||
&self.base_topology,
|
||||
client_store,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
|
||||
let mut rng = OsRng;
|
||||
let task_manager = TaskManager::default();
|
||||
|
||||
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
|
||||
|
||||
let gateway_endpoint = self.gateway_info(&mut rng, &client_store).await?;
|
||||
let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?;
|
||||
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &client_store).await;
|
||||
|
||||
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_endpoint.gateway_listener,
|
||||
managed_keys.identity_keypair(),
|
||||
gateway_identity,
|
||||
managed_keys.gateway_shared_key(),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
Duration::from_secs(10),
|
||||
self.bandwidth_controller.take(),
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(true);
|
||||
let shared_keys = gateway_client.authenticate_and_start().await?;
|
||||
managed_keys
|
||||
.deal_with_gateway_key(shared_keys, &client_store)
|
||||
.await?;
|
||||
|
||||
// TODO: make those values configurable later
|
||||
let tester = NodeTester::new(
|
||||
rng,
|
||||
self.base_topology,
|
||||
Some(address(&managed_keys, gateway_identity)),
|
||||
PacketSize::default(),
|
||||
Duration::from_millis(5),
|
||||
Duration::from_millis(5),
|
||||
managed_keys.ack_key(),
|
||||
);
|
||||
|
||||
let (processed_sender, processed_receiver) = mpsc::unbounded();
|
||||
|
||||
let mut receiver = SimpleMessageReceiver::new_sphinx_receiver(
|
||||
managed_keys.encryption_keypair(),
|
||||
managed_keys.ack_key(),
|
||||
mixnet_message_receiver,
|
||||
ack_receiver,
|
||||
processed_sender,
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
|
||||
nym_task::spawn(async move { receiver.run().await });
|
||||
|
||||
Ok(NymNodeTester {
|
||||
test_in_progress: Arc::new(AtomicBool::new(false)),
|
||||
current_test_nonce: Default::default(),
|
||||
tester: Arc::new(SyncMutex::new(tester)),
|
||||
gateway_client: Arc::new(AsyncMutex::new(gateway_client)),
|
||||
processed_receiver: ReceivedReceiverWrapper::new(processed_receiver),
|
||||
_task_manager: task_manager,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn setup_client(self) -> Promise {
|
||||
future_to_promise(async move { self._setup_client().await.into_promise_result() })
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_mixnode(
|
||||
test_packets: Vec<PreparedFragment>,
|
||||
gateway_client: LockedGatewayClient,
|
||||
processed_receiver: ReceivedReceiverWrapper,
|
||||
_test_marker: TestMarker,
|
||||
timeout: Duration,
|
||||
) -> Result<NodeTestResult, WasmClientError> {
|
||||
let num_test_packets = test_packets.len() as u32;
|
||||
|
||||
let expected_ack_ids = test_packets
|
||||
.iter()
|
||||
.map(|p| p.fragment_identifier)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mix_packets = test_packets.into_iter().map(|p| p.mix_packet).collect();
|
||||
|
||||
// start by clearing any messages that might have been received between tests
|
||||
processed_receiver.clear_received_channel().await;
|
||||
|
||||
// locking the gateway client so that we could get mutable access to data without having to declare
|
||||
// self mutable
|
||||
let mut gateway_permit = gateway_client.lock().await;
|
||||
gateway_permit.batch_send_mix_packets(mix_packets).await?;
|
||||
|
||||
let receiver_permit = processed_receiver.lock().await;
|
||||
let result =
|
||||
EphemeralTestReceiver::new(num_test_packets, expected_ack_ids, receiver_permit, timeout)
|
||||
.perform_test()
|
||||
.await;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymNodeTester {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(topology: WasmNymTopology, gateway: Option<IdentityKey>) -> Promise {
|
||||
console_log!("constructing node tester!");
|
||||
NymNodeTesterBuilder::new(topology, gateway).setup_client()
|
||||
}
|
||||
|
||||
async fn _new_with_api(
|
||||
api_url: String,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> Result<Self, WasmClientError> {
|
||||
NymNodeTesterBuilder::_new_with_api(api_url, gateway)
|
||||
.await?
|
||||
._setup_client()
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn new_with_api(api_url: String, gateway: Option<IdentityKey>) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::_new_with_api(api_url, gateway)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_test_packets(
|
||||
&self,
|
||||
mixnode_identity: String,
|
||||
test_nonce: u32,
|
||||
num_test_packets: u32,
|
||||
) -> Result<Vec<PreparedFragment>, WasmClientError> {
|
||||
let test_ext = WasmTestMessageExt::new(test_nonce);
|
||||
let mut tester_permit = self.tester.lock().expect("mutex got poisoned");
|
||||
tester_permit
|
||||
.existing_identity_mixnode_test_packets(
|
||||
mixnode_identity,
|
||||
test_ext,
|
||||
num_test_packets,
|
||||
None,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn test_node(
|
||||
&self,
|
||||
mixnode_identity: String,
|
||||
timeout_millis: Option<u64>,
|
||||
num_test_packets: Option<u32>,
|
||||
) -> Promise {
|
||||
// establish test parameters
|
||||
let timeout = timeout_millis
|
||||
.map(Duration::from_millis)
|
||||
.unwrap_or(DEFAULT_TEST_TIMEOUT);
|
||||
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
|
||||
|
||||
// mark start of the test
|
||||
if self.test_in_progress.swap(true, Ordering::SeqCst) {
|
||||
return WasmClientError::TestInProgress.into_rejected_promise();
|
||||
}
|
||||
|
||||
// prepare test packets
|
||||
// (I simultaneously feel both disgusted and amazed by this workaround)
|
||||
let test_nonce = self.current_test_nonce.fetch_add(1, Ordering::Relaxed);
|
||||
let test_packets = check_promise_result!(self.prepare_test_packets(
|
||||
mixnode_identity,
|
||||
test_nonce,
|
||||
num_test_packets
|
||||
));
|
||||
|
||||
let processed_receiver_clone = self.processed_receiver.clone();
|
||||
let gateway_client_clone = Arc::clone(&self.gateway_client);
|
||||
let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress));
|
||||
|
||||
// start doing async things (send packets and watch for anything coming back)
|
||||
future_to_promise(async move {
|
||||
test_mixnode(
|
||||
test_packets,
|
||||
gateway_client_clone,
|
||||
processed_receiver_clone,
|
||||
tester_marker,
|
||||
timeout,
|
||||
)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::mix::{Layer, MixnodeConversionError};
|
||||
use nym_topology::{gateway, mix, MixLayer, NymTopology};
|
||||
use nym_validator_client::client::{IdentityKeyRef, MixId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::{console_log, simple_js_error};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WasmTopologyError {
|
||||
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
|
||||
InvalidMixLayer { value: u8 },
|
||||
|
||||
#[error(transparent)]
|
||||
GatewayConversion(#[from] GatewayConversionError),
|
||||
|
||||
#[error(transparent)]
|
||||
MixnodeConversion(#[from] MixnodeConversionError),
|
||||
|
||||
#[error("The provided mixnode map was malformed: {source}")]
|
||||
MalformedMixnodeMap { source: serde_wasm_bindgen::Error },
|
||||
|
||||
#[error("The provided gateway list was malformed: {source}")]
|
||||
MalformedGatewayList { source: serde_wasm_bindgen::Error },
|
||||
}
|
||||
|
||||
impl From<WasmTopologyError> for JsValue {
|
||||
fn from(value: WasmTopologyError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct WasmNymTopology {
|
||||
inner: NymTopology,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmNymTopology {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
// expected: BTreeMap<MixLayer, Vec<WasmMixNode>>,
|
||||
// HashMap<MixLayer, Vec<WasmMixNode>> will also work because it has the same json representation
|
||||
mixnodes: JsValue,
|
||||
// expected: Vec<WasmGateway>
|
||||
gateways: JsValue,
|
||||
) -> Result<WasmNymTopology, WasmTopologyError> {
|
||||
let mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>> =
|
||||
serde_wasm_bindgen::from_value(mixnodes)
|
||||
.map_err(|source| WasmTopologyError::MalformedMixnodeMap { source })?;
|
||||
|
||||
let gateways: Vec<WasmGateway> = serde_wasm_bindgen::from_value(gateways)
|
||||
.map_err(|source| WasmTopologyError::MalformedGatewayList { source })?;
|
||||
|
||||
let mut converted_mixes = BTreeMap::new();
|
||||
|
||||
for (layer, nodes) in mixnodes {
|
||||
let layer_nodes = nodes
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
converted_mixes.insert(layer, layer_nodes);
|
||||
}
|
||||
|
||||
let gateways = gateways
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(WasmNymTopology {
|
||||
inner: NymTopology::new(converted_mixes, gateways),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
|
||||
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
|
||||
self.inner
|
||||
.gateways()
|
||||
.iter()
|
||||
.any(|g| g.identity_key.to_base58_string() == gateway_id)
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
if !self.inner.mixes().is_empty() {
|
||||
console_log!("mixnodes:");
|
||||
for (layer, nodes) in self.inner.mixes() {
|
||||
console_log!("\tlayer {layer}:");
|
||||
for node in nodes {
|
||||
console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console_log!("NO MIXNODES")
|
||||
}
|
||||
|
||||
if !self.inner.gateways().is_empty() {
|
||||
console_log!("gateways:");
|
||||
for gateway in self.inner.gateways() {
|
||||
console_log!("\t{}", gateway.identity_key)
|
||||
}
|
||||
} else {
|
||||
console_log!("NO GATEWAYS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmNymTopology> for NymTopology {
|
||||
fn from(value: WasmNymTopology) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymTopology> for WasmNymTopology {
|
||||
fn from(value: NymTopology) -> Self {
|
||||
WasmNymTopology { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct WasmMixNode {
|
||||
pub mix_id: MixId,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub owner: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub identity_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub sphinx_key: String,
|
||||
pub layer: MixLayer,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmMixNode {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
mix_id: MixId,
|
||||
owner: String,
|
||||
host: String,
|
||||
mix_port: u16,
|
||||
identity_key: String,
|
||||
sphinx_key: String,
|
||||
layer: MixLayer,
|
||||
version: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
mix_id,
|
||||
owner,
|
||||
host,
|
||||
mix_port,
|
||||
identity_key,
|
||||
sphinx_key,
|
||||
layer,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WasmMixNode> for mix::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
|
||||
let host = mix::Node::parse_host(&value.host)?;
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = mix::Node::extract_mix_host(&host, value.mix_port)?;
|
||||
|
||||
Ok(mix::Node {
|
||||
mix_id: value.mix_id,
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
layer: Layer::try_from(value.layer)
|
||||
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
|
||||
version: value.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct WasmGateway {
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub owner: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
pub clients_port: u16,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub identity_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub sphinx_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmGateway {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
owner: String,
|
||||
host: String,
|
||||
mix_port: u16,
|
||||
clients_port: u16,
|
||||
identity_key: String,
|
||||
sphinx_key: String,
|
||||
version: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
owner,
|
||||
host,
|
||||
mix_port,
|
||||
clients_port,
|
||||
identity_key,
|
||||
sphinx_key,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WasmGateway> for gateway::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
|
||||
let host = gateway::Node::parse_host(&value.host)?;
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = gateway::Node::extract_mix_host(&host, value.mix_port)?;
|
||||
|
||||
Ok(gateway::Node {
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
clients_port: value.clients_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
version: value.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ impl AsyncFileWatcher {
|
||||
Ok(event) => {
|
||||
let now = Instant::now();
|
||||
if self.should_propagate(&event, now) {
|
||||
self.last_received.insert(event.kind.clone(), now);
|
||||
self.last_received.insert(event.kind, now);
|
||||
if let Err(_err) = self.event_sender.unbounded_send(event) {
|
||||
log::error!("the file watcher receiver has been dropped!");
|
||||
}
|
||||
|
||||
@@ -55,11 +55,16 @@ where
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub async fn get_credential<C: DkgQueryClient + Send + Sync, St: Storage>(
|
||||
pub async fn get_credential<C, St>(
|
||||
state: &State,
|
||||
client: &C,
|
||||
storage: &St,
|
||||
) -> Result<(), BandwidthControllerError> {
|
||||
) -> Result<(), BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: Storage,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let epoch_id = client.get_current_epoch().await?.epoch_id;
|
||||
let threshold = client
|
||||
.get_current_epoch_threshold()
|
||||
@@ -83,7 +88,6 @@ pub async fn get_credential<C: DkgQueryClient + Send + Sync, St: Storage>(
|
||||
signature.to_bs58(),
|
||||
epoch_id.to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ pub enum BandwidthControllerError {
|
||||
Nyxd(#[from] nym_validator_client::nyxd::error::NyxdError),
|
||||
|
||||
#[error("There was a credential storage error - {0}")]
|
||||
CredentialStorageError(#[from] StorageError),
|
||||
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
// this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now
|
||||
#[error(transparent)]
|
||||
StorageError(#[from] StorageError),
|
||||
|
||||
#[error("Coconut error - {0}")]
|
||||
CoconutError(#[from] CoconutError),
|
||||
|
||||
@@ -26,7 +26,7 @@ pub mod error;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm_mockups;
|
||||
|
||||
pub struct BandwidthController<C, St: Storage> {
|
||||
pub struct BandwidthController<C, St> {
|
||||
storage: St,
|
||||
client: C,
|
||||
}
|
||||
@@ -45,8 +45,13 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
|
||||
let bandwidth_credential = self
|
||||
.storage
|
||||
.get_next_coconut_credential()
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
|
||||
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
|
||||
.map_err(|_| StorageError::InconsistentData)?;
|
||||
let voucher_info = bandwidth_credential.voucher_info.clone();
|
||||
@@ -82,10 +87,16 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError> {
|
||||
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
// JS: shouldn't we send some contract/validator/gateway message here to actually, you know,
|
||||
// consume it?
|
||||
Ok(self.storage.consume_coconut_credential(id).await?)
|
||||
self.storage
|
||||
.consume_coconut_credential(id)
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ clap_complete_fig = "4.0"
|
||||
log = { workspace = true }
|
||||
pretty_env_logger = "0.4.0"
|
||||
semver = "0.11"
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
|
||||
## tracing
|
||||
@@ -36,5 +36,5 @@ vergen = { version = "=7.4.3", default-features = false, features = [
|
||||
|
||||
[features]
|
||||
default = []
|
||||
output_format = ["serde", "serde_json"]
|
||||
output_format = ["serde_json"]
|
||||
tracing = ["tracing-appender", "tracing-subscriber", "tracing-tree"]
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// TODO: at a later date this crate should probably also expose `ContractBuildInformation`
|
||||
// and be used by our smart contracts
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BinaryBuildInformation {
|
||||
// VERGEN_BUILD_TIMESTAMP
|
||||
@@ -99,8 +101,7 @@ impl BinaryBuildInformation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BinaryBuildInformationOwned {
|
||||
// VERGEN_BUILD_TIMESTAMP
|
||||
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
pub mod build_information;
|
||||
pub mod completions;
|
||||
pub mod logging;
|
||||
pub mod output_format;
|
||||
pub mod version_checker;
|
||||
|
||||
#[cfg(feature = "output_format")]
|
||||
pub mod output_format;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// use tracing_subscriber::{
|
||||
// fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry,
|
||||
// };
|
||||
// use tracing_tree::HierarchicalLayer;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use tracing_appender;
|
||||
@@ -12,6 +10,12 @@ pub use tracing_subscriber;
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use tracing_tree;
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettings {
|
||||
// well, we need to implement something here at some point...
|
||||
}
|
||||
|
||||
// I'd argue we should start transitioning from `log` to `tracing`
|
||||
pub fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
|
||||
@@ -23,6 +23,7 @@ url = { version ="2.2", features = ["serde"] }
|
||||
tungstenite = { version = "0.13.0", default-features = false }
|
||||
tokio = { version = "1.24.1", features = ["macros"]}
|
||||
time = "0.3.17"
|
||||
zeroize = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
||||
@@ -38,6 +39,7 @@ nym-topology = { path = "../topology" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client]
|
||||
path = "../client-libs/validator-client"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::{client::replies::reply_storage, config::DebugConfig};
|
||||
|
||||
pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty {
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::key_manager::KeyManager;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ManagedKeys;
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use crate::client::real_messages_control;
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
@@ -22,10 +24,11 @@ use crate::client::topology_control::{
|
||||
};
|
||||
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::spawn_future;
|
||||
use crate::{config, spawn_future};
|
||||
use futures::channel::mpsc;
|
||||
use log::{debug, info};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
@@ -34,26 +37,27 @@ use nym_gateway_client::{
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
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_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tap::TapFallible;
|
||||
use url::Url;
|
||||
|
||||
use nym_credential_storage::storage::Storage;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_validator_client::nyxd::traits::DkgQueryClient;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_validator_client::nyxd::traits::DkgQueryClient;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub mod non_wasm_helpers;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod storage;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientInput {
|
||||
@@ -152,52 +156,55 @@ impl From<bool> for CredentialsToggle {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BaseClientBuilder<'a, B, C, St: Storage> {
|
||||
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
// due to wasm limitations I had to split it like this : (
|
||||
gateway_config: &'a GatewayEndpointConfig,
|
||||
debug_config: &'a DebugConfig,
|
||||
disabled_credentials: bool,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
reply_storage_backend: B,
|
||||
reply_storage_backend: S::ReplyStore,
|
||||
key_store: S::KeyStore,
|
||||
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
key_manager: KeyManager,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
managed_keys: ManagedKeys,
|
||||
}
|
||||
|
||||
impl<'a, B, C, St> BaseClientBuilder<'a, B, C, St>
|
||||
impl<'a, C, S> BaseClientBuilder<'a, C, S>
|
||||
where
|
||||
B: ReplyStorageBackend + Send + Sync + 'static,
|
||||
C: DkgQueryClient + Sync + Send + 'static,
|
||||
St: Storage + 'static,
|
||||
S: MixnetClientStorage + 'static,
|
||||
C: DkgQueryClient + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new_from_base_config<T>(
|
||||
base_config: &'a Config<T>,
|
||||
key_manager: KeyManager,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
reply_storage_backend: B,
|
||||
) -> BaseClientBuilder<'a, B, C, St> {
|
||||
// TODO: combine all storages
|
||||
pub fn new_from_base_config(
|
||||
base_config: &'a Config,
|
||||
key_store: S::KeyStore,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
reply_storage_backend: S::ReplyStore,
|
||||
) -> BaseClientBuilder<'a, C, S> {
|
||||
BaseClientBuilder {
|
||||
gateway_config: base_config.get_gateway_endpoint_config(),
|
||||
debug_config: base_config.get_debug_config(),
|
||||
debug_config: &base_config.debug,
|
||||
disabled_credentials: base_config.get_disabled_credentials_mode(),
|
||||
nym_api_endpoints: base_config.get_nym_api_endpoints(),
|
||||
bandwidth_controller,
|
||||
reply_storage_backend,
|
||||
key_manager,
|
||||
key_store,
|
||||
managed_keys: ManagedKeys::Invalidated,
|
||||
custom_topology_provider: None,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: combine all storages
|
||||
pub fn new(
|
||||
gateway_config: &'a GatewayEndpointConfig,
|
||||
debug_config: &'a DebugConfig,
|
||||
key_manager: KeyManager,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
reply_storage_backend: B,
|
||||
key_store: S::KeyStore,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
reply_storage_backend: S::ReplyStore,
|
||||
credentials_toggle: CredentialsToggle,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
) -> BaseClientBuilder<'a, B, C, St> {
|
||||
) -> BaseClientBuilder<'a, C, S> {
|
||||
BaseClientBuilder {
|
||||
gateway_config,
|
||||
debug_config,
|
||||
@@ -206,19 +213,25 @@ where
|
||||
reply_storage_backend,
|
||||
custom_topology_provider: None,
|
||||
bandwidth_controller,
|
||||
key_manager,
|
||||
key_store,
|
||||
managed_keys: ManagedKeys::Invalidated,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
|
||||
pub fn with_topology_provider(
|
||||
mut self,
|
||||
provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Self {
|
||||
self.custom_topology_provider = Some(provider);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_mix_recipient(&self) -> Recipient {
|
||||
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
|
||||
// because it relies on the crypto keys being already loaded
|
||||
fn as_mix_recipient(&self) -> Recipient {
|
||||
Recipient::new(
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
*self.key_manager.encryption_keypair().public_key(),
|
||||
*self.managed_keys.identity_public_key(),
|
||||
*self.managed_keys.encryption_public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(),
|
||||
@@ -263,6 +276,7 @@ where
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
shutdown: TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
info!("Starting real traffic stream...");
|
||||
|
||||
@@ -278,7 +292,7 @@ where
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
)
|
||||
.start_with_shutdown(shutdown);
|
||||
.start_with_shutdown(shutdown, packet_type);
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -308,7 +322,11 @@ where
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient<C, St>, ClientCoreError> {
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let gateway_id = self.gateway_config.gateway_id.clone();
|
||||
if gateway_id.is_empty() {
|
||||
return Err(ClientCoreError::GatewayIdUnknown);
|
||||
@@ -321,19 +339,11 @@ where
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
|
||||
let shared_key = if self.key_manager.is_gateway_key_set() {
|
||||
Some(self.key_manager.gateway_shared_key())
|
||||
} else {
|
||||
log::info!("Gateway key not set! Will proceed anyway.");
|
||||
None
|
||||
};
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
self.managed_keys.identity_keypair(),
|
||||
gateway_identity,
|
||||
shared_key,
|
||||
self.managed_keys.gateway_shared_key(),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.debug_config
|
||||
@@ -345,19 +355,27 @@ where
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
|
||||
|
||||
gateway_client
|
||||
let shared_key = gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.tap_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}")
|
||||
})?;
|
||||
|
||||
self.managed_keys
|
||||
.deal_with_gateway_key(shared_key, &self.key_store)
|
||||
.await
|
||||
.map_err(|source| ClientCoreError::KeyStoreError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
|
||||
Ok(gateway_client)
|
||||
}
|
||||
|
||||
fn setup_topology_provider(
|
||||
custom_provider: Option<Box<dyn TopologyProvider>>,
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
nym_api_urls: Vec<Url>,
|
||||
) -> Box<dyn TopologyProvider> {
|
||||
) -> Box<dyn TopologyProvider + Send + Sync> {
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| {
|
||||
Box::new(NymApiTopologyProvider::new(
|
||||
@@ -370,12 +388,13 @@ where
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
async fn start_topology_refresher(
|
||||
topology_provider: Box<dyn TopologyProvider>,
|
||||
refresh_rate: Duration,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
topology_config: config::Topology,
|
||||
topology_accessor: TopologyAccessor,
|
||||
shutdown: TaskClient,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
|
||||
let topology_refresher_config =
|
||||
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
|
||||
|
||||
let mut topology_refresher = TopologyRefresher::new(
|
||||
topology_refresher_config,
|
||||
@@ -395,65 +414,81 @@ where
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology(err));
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
if topology_config.disable_refreshing {
|
||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||
info!("The topology refesher is not going to be started");
|
||||
shutdown.mark_as_success();
|
||||
} else {
|
||||
// don't spawn the refresher if we don't want to be refreshing the topology.
|
||||
// only use the initial values obtained
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
// controller for sending packets to mixnet (either real traffic or cover traffic)
|
||||
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_client: GatewayClient<C, St>,
|
||||
gateway_client: GatewayClient<C, S::CredentialStore>,
|
||||
shutdown: TaskClient,
|
||||
) -> BatchMixMessageSender {
|
||||
) -> BatchMixMessageSender
|
||||
where
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
|
||||
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
|
||||
async fn setup_persistent_reply_storage(
|
||||
backend: B,
|
||||
backend: S::ReplyStore,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<CombinedReplyStorage, ClientCoreError>
|
||||
where
|
||||
<B as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
S::ReplyStore: Send + Sync,
|
||||
{
|
||||
if backend.is_active() {
|
||||
log::trace!("Setup persistent reply storage");
|
||||
let persistent_storage = PersistentReplyStorage::new(backend);
|
||||
let mem_store = persistent_storage
|
||||
.load_state_from_backend()
|
||||
log::trace!("Setup persistent reply storage");
|
||||
let persistent_storage = PersistentReplyStorage::new(backend);
|
||||
let mem_store = persistent_storage
|
||||
.load_state_from_backend()
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
let store_clone = mem_store.clone();
|
||||
spawn_future(async move {
|
||||
persistent_storage
|
||||
.flush_on_shutdown(store_clone, shutdown)
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
});
|
||||
|
||||
let store_clone = mem_store.clone();
|
||||
spawn_future(async move {
|
||||
persistent_storage
|
||||
.flush_on_shutdown(store_clone, shutdown)
|
||||
.await
|
||||
});
|
||||
Ok(mem_store)
|
||||
}
|
||||
|
||||
Ok(mem_store)
|
||||
} else {
|
||||
log::trace!("Setup inactive reply storage");
|
||||
Ok(backend
|
||||
.get_inactive_storage()
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?)
|
||||
}
|
||||
async fn initial_key_setup(&mut self) {
|
||||
assert!(!self.managed_keys.is_valid());
|
||||
let mut rng = OsRng;
|
||||
self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await;
|
||||
}
|
||||
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
|
||||
where
|
||||
<B as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
S::ReplyStore: Send + Sync,
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
info!("Starting nym client");
|
||||
self.initial_key_setup().await;
|
||||
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
// rather than creating them here, so say for example the buffer controller would create the request channels
|
||||
@@ -500,14 +535,14 @@ where
|
||||
);
|
||||
Self::start_topology_refresher(
|
||||
topology_provider,
|
||||
self.debug_config.topology.topology_refresh_rate,
|
||||
self.debug_config.topology,
|
||||
shared_topology_accessor.clone(),
|
||||
task_manager.subscribe(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Self::start_received_messages_buffer_controller(
|
||||
self.key_manager.encryption_keypair(),
|
||||
self.managed_keys.encryption_keypair(),
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
reply_storage.key_storage(),
|
||||
@@ -515,11 +550,11 @@ where
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
|
||||
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
|
||||
// The message_sender is the transmitter for any component generating sphinx packets
|
||||
// that are to be sent to the mixnet. They are used by cover traffic stream and real
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
let sphinx_message_sender =
|
||||
let message_sender =
|
||||
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
@@ -532,7 +567,7 @@ where
|
||||
|
||||
let controller_config = real_messages_control::Config::new(
|
||||
self.debug_config,
|
||||
self.key_manager.ack_key(),
|
||||
self.managed_keys.ack_key(),
|
||||
self_address,
|
||||
);
|
||||
|
||||
@@ -541,13 +576,14 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
message_sender.clone(),
|
||||
reply_storage,
|
||||
reply_controller_sender.clone(),
|
||||
reply_controller_receiver,
|
||||
shared_lane_queue_lengths.clone(),
|
||||
client_connection_rx,
|
||||
task_manager.subscribe(),
|
||||
self.debug_config.traffic.packet_type,
|
||||
);
|
||||
|
||||
if !self
|
||||
@@ -557,10 +593,10 @@ where
|
||||
{
|
||||
Self::start_cover_traffic_stream(
|
||||
self.debug_config,
|
||||
self.key_manager.ack_key(),
|
||||
self.managed_keys.ack_key(),
|
||||
self_address,
|
||||
shared_topology_accessor.clone(),
|
||||
sphinx_message_sender,
|
||||
message_sender,
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
}
|
||||
@@ -569,6 +605,7 @@ where
|
||||
debug!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(BaseClient {
|
||||
address: self_address,
|
||||
client_input: ClientInputStatus::AwaitingProducer {
|
||||
client_input: ClientInput {
|
||||
connection_command_sender: client_connection_tx,
|
||||
@@ -591,6 +628,7 @@ where
|
||||
}
|
||||
|
||||
pub struct BaseClient {
|
||||
pub address: Recipient,
|
||||
pub client_input: ClientInputStatus,
|
||||
pub client_output: ClientOutputStatus,
|
||||
pub client_state: ClientState,
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::{
|
||||
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
|
||||
};
|
||||
use crate::config::DebugConfig;
|
||||
use crate::config;
|
||||
use crate::config::Config;
|
||||
use crate::error::ClientCoreError;
|
||||
use log::{error, info};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_validator_client::nyxd::QueryNyxdClient;
|
||||
use nym_validator_client::Client;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
db_path: P,
|
||||
debug_config: &DebugConfig,
|
||||
surb_config: &config::ReplySurbs,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
info!("creating fresh surb database");
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
@@ -30,12 +36,8 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
// it will only be happening on the very first run and in practice won't incur huge
|
||||
// costs since the storage is going to be empty
|
||||
let mem_store = CombinedReplyStorage::new(
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
surb_config.minimum_reply_surb_storage_threshold,
|
||||
surb_config.maximum_reply_surb_storage_threshold,
|
||||
);
|
||||
storage_backend
|
||||
.init_fresh(&mem_store)
|
||||
@@ -47,17 +49,13 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
Ok(storage_backend)
|
||||
}
|
||||
|
||||
fn setup_inactive_backend(debug_config: &DebugConfig) -> fs_backend::Backend {
|
||||
info!("creating inactive surb database");
|
||||
fs_backend::Backend::new_inactive(
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
)
|
||||
}
|
||||
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
|
||||
// info!("creating inactive surb database");
|
||||
// fs_backend::Backend::new_inactive(
|
||||
// surb_config.minimum_reply_surb_storage_threshold,
|
||||
// surb_config.maximum_reply_surb_storage_threshold,
|
||||
// )
|
||||
// }
|
||||
|
||||
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
let db_path = db_path.as_ref();
|
||||
@@ -81,28 +79,56 @@ fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
}
|
||||
|
||||
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
db_path: Option<P>,
|
||||
debug_config: &DebugConfig,
|
||||
db_path: P,
|
||||
surb_config: &config::ReplySurbs,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
if let Some(db_path) = db_path {
|
||||
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load
|
||||
// the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load
|
||||
// the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
archive_corrupted_database(db_path)?;
|
||||
setup_fresh_backend(db_path, debug_config).await
|
||||
}
|
||||
archive_corrupted_database(db_path)?;
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
} else {
|
||||
setup_fresh_backend(db_path, debug_config).await
|
||||
}
|
||||
} else {
|
||||
Ok(setup_inactive_backend(debug_config))
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_bandwidth_controller<St: CredentialStorage>(
|
||||
config: &Config,
|
||||
storage: St,
|
||||
) -> BandwidthController<Client<QueryNyxdClient>, St> {
|
||||
let nyxd_url = config
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nyxd validator endpoint provided");
|
||||
let api_url = config
|
||||
.get_nym_api_endpoints()
|
||||
.pop()
|
||||
.expect("No validator api endpoint provided");
|
||||
|
||||
create_bandwidth_controller_with_urls(nyxd_url, api_url, storage)
|
||||
}
|
||||
|
||||
pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
|
||||
nyxd_url: Url,
|
||||
nym_api_url: Url,
|
||||
storage: St,
|
||||
) -> BandwidthController<Client<QueryNyxdClient>, St> {
|
||||
let details = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let mut client_config = nym_validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
// overwrite env configuration with config URLs
|
||||
client_config = client_config.with_urls(nyxd_url, nym_api_url);
|
||||
let client = nym_validator_client::Client::new_query(client_config)
|
||||
.expect("Could not construct query client");
|
||||
|
||||
BandwidthController::new(storage, client)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: combine those more closely. Perhaps into a single underlying store.
|
||||
// Like for persistent, on-disk, storage, what's the point of having 3 different databases?
|
||||
|
||||
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
|
||||
use crate::client::replies::reply_storage;
|
||||
use crate::client::replies::reply_storage::ReplyStorageBackend;
|
||||
use nym_credential_storage::ephemeral_storage::{
|
||||
EphemeralStorage as EphemeralCredentialStorage, EphemeralStorage,
|
||||
};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use crate::client::base_client::non_wasm_helpers;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use crate::client::key_manager::persistence::OnDiskKeys;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use crate::client::replies::reply_storage::fs_backend;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use crate::config::{self, disk_persistence::CommonClientPaths};
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use crate::error::ClientCoreError;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
|
||||
|
||||
pub trait MixnetClientStorage {
|
||||
type KeyStore: KeyStore;
|
||||
type ReplyStore: ReplyStorageBackend;
|
||||
type CredentialStore: CredentialStorage;
|
||||
|
||||
// this is a TERRIBLE name...
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore);
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore;
|
||||
fn reply_store(&self) -> &Self::ReplyStore;
|
||||
fn credential_store(&self) -> &Self::CredentialStore;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ephemeral {
|
||||
key_store: InMemEphemeralKeys,
|
||||
reply_store: reply_storage::Empty,
|
||||
credential_store: EphemeralStorage,
|
||||
}
|
||||
|
||||
impl Ephemeral {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnetClientStorage for Ephemeral {
|
||||
type KeyStore = InMemEphemeralKeys;
|
||||
type ReplyStore = reply_storage::Empty;
|
||||
type CredentialStore = EphemeralCredentialStorage;
|
||||
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
|
||||
(self.key_store, self.reply_store, self.credential_store)
|
||||
}
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore {
|
||||
&self.key_store
|
||||
}
|
||||
|
||||
fn reply_store(&self) -> &Self::ReplyStore {
|
||||
&self.reply_store
|
||||
}
|
||||
|
||||
fn credential_store(&self) -> &Self::CredentialStore {
|
||||
&self.credential_store
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub struct OnDiskPersistent {
|
||||
pub(crate) key_store: OnDiskKeys,
|
||||
pub(crate) reply_store: fs_backend::Backend,
|
||||
pub(crate) credential_store: PersistentCredentialStorage,
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
impl OnDiskPersistent {
|
||||
pub fn new(
|
||||
key_store: OnDiskKeys,
|
||||
reply_store: fs_backend::Backend,
|
||||
credential_store: PersistentCredentialStorage,
|
||||
) -> Self {
|
||||
Self {
|
||||
key_store,
|
||||
reply_store,
|
||||
credential_store,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_paths(
|
||||
paths: CommonClientPaths,
|
||||
debug_config: &config::DebugConfig,
|
||||
) -> Result<Self, ClientCoreError> {
|
||||
let key_store = OnDiskKeys::new(paths.keys);
|
||||
|
||||
let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
paths.reply_surb_database,
|
||||
&debug_config.reply_surbs,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let credential_store =
|
||||
nym_credential_storage::initialise_persistent_storage(paths.credentials_database).await;
|
||||
|
||||
Ok(OnDiskPersistent {
|
||||
key_store,
|
||||
reply_store,
|
||||
credential_store,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
impl MixnetClientStorage for OnDiskPersistent {
|
||||
type KeyStore = OnDiskKeys;
|
||||
type ReplyStore = fs_backend::Backend;
|
||||
type CredentialStore = PersistentCredentialStorage;
|
||||
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
|
||||
(self.key_store, self.reply_store, self.credential_store)
|
||||
}
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore {
|
||||
&self.key_store
|
||||
}
|
||||
|
||||
fn reply_store(&self) -> &Self::ReplyStore {
|
||||
&self.reply_store
|
||||
}
|
||||
|
||||
fn credential_store(&self) -> &Self::CredentialStore {
|
||||
&self.credential_store
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ where
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
next_delay: Pin<Box<wasm_timer::Delay>>,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// Channel used for sending prepared nym packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
mix_tx: BatchMixMessageSender,
|
||||
|
||||
@@ -194,6 +194,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
self.average_ack_delay,
|
||||
self.cover_traffic.loop_cover_traffic_average_delay,
|
||||
cover_traffic_packet_size,
|
||||
nym_sphinx::params::PacketType::Mix,
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
|
||||
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
|
||||
@@ -7,6 +12,14 @@ pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputMessage {
|
||||
/// Fire an already prepared mix packets into the network.
|
||||
/// No guarantees are made about it. For example no retransmssion
|
||||
/// will be attempted if it gets dropped.
|
||||
Premade {
|
||||
msgs: Vec<MixPacket>,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
|
||||
/// The simplest message variant where no additional information is attached.
|
||||
/// You're simply sending your `data` to specified `recipient` without any tagging.
|
||||
///
|
||||
@@ -41,14 +54,49 @@ pub enum InputMessage {
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
|
||||
MessageWrapper {
|
||||
message: Box<InputMessage>,
|
||||
packet_type: PacketType,
|
||||
},
|
||||
}
|
||||
|
||||
impl InputMessage {
|
||||
pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
|
||||
InputMessage::Regular {
|
||||
pub fn new_premade(
|
||||
msgs: Vec<MixPacket>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
) -> Self {
|
||||
let message = InputMessage::Premade { msgs, lane };
|
||||
if packet_type == PacketType::Mix {
|
||||
message
|
||||
} else {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_wrapper(message: InputMessage, packet_type: PacketType) -> Self {
|
||||
InputMessage::MessageWrapper {
|
||||
message: Box::new(message),
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_regular(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: Option<PacketType>,
|
||||
) -> Self {
|
||||
let message = InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,12 +105,18 @@ impl InputMessage {
|
||||
data: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
packet_type: Option<PacketType>,
|
||||
) -> Self {
|
||||
InputMessage::Anonymous {
|
||||
let message = InputMessage::Anonymous {
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +124,17 @@ impl InputMessage {
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: Option<PacketType>,
|
||||
) -> Self {
|
||||
InputMessage::Reply {
|
||||
let message = InputMessage::Reply {
|
||||
recipient_tag,
|
||||
data,
|
||||
lane,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +142,9 @@ impl InputMessage {
|
||||
match self {
|
||||
InputMessage::Regular { lane, .. }
|
||||
| InputMessage::Anonymous { lane, .. }
|
||||
| InputMessage::Reply { lane, .. } => lane,
|
||||
| InputMessage::Reply { lane, .. }
|
||||
| InputMessage::Premade { lane, .. } => lane,
|
||||
InputMessage::MessageWrapper { message, .. } => message.lane(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use log::*;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
||||
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
||||
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
|
||||
// And I guess there will need to be some mechanism for a grace period when you can still
|
||||
// use the old key after new one was issued.
|
||||
|
||||
// Remember that Arc<T> has Deref implementation for T
|
||||
#[derive(Clone)]
|
||||
pub struct KeyManager {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// encryption key associated with the client instance.
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// shared key derived with the gateway during "registration handshake"
|
||||
gateway_shared_key: Option<Arc<SharedKeys>>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
}
|
||||
|
||||
// The expected flow of a KeyManager "lifetime" is as follows:
|
||||
/*
|
||||
1. ::new() is called during client-init
|
||||
2. after gateway registration is completed [in init] ::insert_gateway_shared_key() is called
|
||||
3. ::store_keys() is called before init finishes execution.
|
||||
4. ::load_keys() is called at the beginning of each subsequent client-run
|
||||
5. [not implemented] ::rotate_keys() is called periodically during client-run I presume?
|
||||
*/
|
||||
|
||||
impl KeyManager {
|
||||
/// Creates new instance of a [`KeyManager`]
|
||||
pub fn new<R>(rng: &mut R) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
KeyManager {
|
||||
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
|
||||
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
|
||||
gateway_shared_key: None,
|
||||
ack_key: Arc::new(AckKey::new(rng)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_keys(
|
||||
id_keypair: identity::KeyPair,
|
||||
enc_keypair: encryption::KeyPair,
|
||||
gateway_shared_key: SharedKeys,
|
||||
ack_key: AckKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
identity_keypair: Arc::new(id_keypair),
|
||||
encryption_keypair: Arc::new(enc_keypair),
|
||||
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
|
||||
ack_key: Arc::new(ack_key),
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads previously stored client keys from the disk.
|
||||
fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_identity_key().to_owned(),
|
||||
client_pathfinder.public_identity_key().to_owned(),
|
||||
))?;
|
||||
let encryption_keypair: encryption::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_encryption_key().to_owned(),
|
||||
client_pathfinder.public_encryption_key().to_owned(),
|
||||
))?;
|
||||
|
||||
let ack_key: AckKey = nym_pemstore::load_key(client_pathfinder.ack_key())?;
|
||||
|
||||
Ok(KeyManager {
|
||||
identity_keypair: Arc::new(identity_keypair),
|
||||
encryption_keypair: Arc::new(encryption_keypair),
|
||||
gateway_shared_key: None,
|
||||
ack_key: Arc::new(ack_key),
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads previously stored keys from the disk. Fails if not all, including the shared gateway
|
||||
/// key, is available.
|
||||
pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
|
||||
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
|
||||
|
||||
let gateway_shared_key: SharedKeys =
|
||||
nym_pemstore::load_key(client_pathfinder.gateway_shared_key())?;
|
||||
|
||||
key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key));
|
||||
|
||||
Ok(key_manager)
|
||||
}
|
||||
|
||||
/// Loads previously stored keys from the disk. Fails if client keys are not availabe, but the
|
||||
/// shared gateway key is optional.
|
||||
pub fn load_keys_but_gateway_is_optional(
|
||||
client_pathfinder: &ClientKeyPathfinder,
|
||||
) -> io::Result<Self> {
|
||||
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
|
||||
|
||||
let gateway_shared_key: Result<SharedKeys, io::Error> =
|
||||
nym_pemstore::load_key(client_pathfinder.gateway_shared_key());
|
||||
|
||||
// It's ok if the gateway key was not found
|
||||
let gateway_shared_key = match gateway_shared_key {
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
Ok(key) => Ok(Some(key)),
|
||||
}?;
|
||||
|
||||
key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new);
|
||||
|
||||
Ok(key_manager)
|
||||
}
|
||||
|
||||
/// Stores all available keys on the disk.
|
||||
// While perhaps there is no much point in storing the `AckKey` on the disk,
|
||||
// it is done so for the consistency sake so that you wouldn't require an rng instance
|
||||
// during `load_keys` to generate the said key.
|
||||
pub fn store_keys(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
|
||||
nym_pemstore::store_keypair(
|
||||
self.identity_keypair.as_ref(),
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_identity_key().to_owned(),
|
||||
client_pathfinder.public_identity_key().to_owned(),
|
||||
),
|
||||
)?;
|
||||
nym_pemstore::store_keypair(
|
||||
self.encryption_keypair.as_ref(),
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_encryption_key().to_owned(),
|
||||
client_pathfinder.public_encryption_key().to_owned(),
|
||||
),
|
||||
)?;
|
||||
|
||||
nym_pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?;
|
||||
|
||||
match self.gateway_shared_key.as_ref() {
|
||||
None => debug!("No gateway shared key available to store!"),
|
||||
Some(gate_key) => {
|
||||
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
|
||||
match self.gateway_shared_key.as_ref() {
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"trying to store a non-existing key",
|
||||
))
|
||||
}
|
||||
Some(gate_key) => {
|
||||
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Overwrite the existing identity keypair
|
||||
pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) {
|
||||
self.identity_keypair = Arc::new(id_keypair);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
/// Overwrite the existing encryption keypair
|
||||
pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) {
|
||||
self.encryption_keypair = Arc::new(enc_keypair);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
|
||||
/// Overwrite the existing ack key
|
||||
pub fn set_ack_key(&mut self, ack_key: AckKey) {
|
||||
self.ack_key = Arc::new(ack_key);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`AckKey`].
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
|
||||
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
|
||||
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
|
||||
self.gateway_shared_key = Some(gateway_shared_key)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`SharedKey`].
|
||||
// since this function is not fully public, it is not expected to be used externally and
|
||||
// hence it's up to us to ensure it's called in correct context
|
||||
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
|
||||
Arc::clone(
|
||||
self.gateway_shared_key
|
||||
.as_ref()
|
||||
.expect("tried to unwrap empty gateway key!"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_gateway_key_set(&self) -> bool {
|
||||
self.gateway_shared_key.is_some()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
pub mod persistence;
|
||||
|
||||
pub enum ManagedKeys {
|
||||
Initial(KeyManagerBuilder),
|
||||
FullyDerived(KeyManager),
|
||||
|
||||
// I really hate the existence of this variant, but I couldn't come up with a better way to handle
|
||||
// `Self::deal_with_gateway_key` otherwise.
|
||||
Invalidated,
|
||||
}
|
||||
|
||||
impl From<KeyManagerBuilder> for ManagedKeys {
|
||||
fn from(value: KeyManagerBuilder) -> Self {
|
||||
ManagedKeys::Initial(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyManager> for ManagedKeys {
|
||||
fn from(value: KeyManager) -> Self {
|
||||
ManagedKeys::FullyDerived(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagedKeys {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!matches!(self, ManagedKeys::Invalidated)
|
||||
}
|
||||
|
||||
pub async fn try_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
|
||||
Ok(ManagedKeys::FullyDerived(
|
||||
KeyManager::load_keys(key_store).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn generate_new<R>(rng: &mut R) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
|
||||
}
|
||||
|
||||
pub async fn load_or_generate<R, S>(rng: &mut R, key_store: &S) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
S: KeyStore,
|
||||
{
|
||||
Self::try_load(key_store)
|
||||
.await
|
||||
.unwrap_or_else(|_| Self::generate_new(rng))
|
||||
}
|
||||
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.identity_keypair(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.identity_keypair(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.encryption_keypair(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.ack_key(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.ack_key(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
|
||||
match self {
|
||||
ManagedKeys::Initial(_) => None,
|
||||
ManagedKeys::FullyDerived(keys) => Some(keys.gateway_shared_key()),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity_public_key(&self) -> &identity::PublicKey {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.identity_keypair.public_key(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.identity_keypair.public_key(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encryption_public_key(&self) -> &encryption::PublicKey {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.encryption_keypair.public_key(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair.public_key(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn deal_with_gateway_key<S: KeyStore>(
|
||||
&mut self,
|
||||
gateway_shared_key: Arc<SharedKeys>,
|
||||
key_store: &S,
|
||||
) -> Result<(), S::StorageError> {
|
||||
let key_manager = match std::mem::replace(self, ManagedKeys::Invalidated) {
|
||||
ManagedKeys::Initial(keys) => {
|
||||
let key_manager = keys.insert_gateway_shared_key(gateway_shared_key);
|
||||
key_manager.persist_keys(key_store).await?;
|
||||
key_manager
|
||||
}
|
||||
ManagedKeys::FullyDerived(key_manager) => {
|
||||
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|
||||
|| key_manager.gateway_shared_key != gateway_shared_key
|
||||
{
|
||||
// this should NEVER happen thus panic here
|
||||
panic!("derived fresh gateway shared key whilst already holding one!")
|
||||
}
|
||||
key_manager
|
||||
}
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
};
|
||||
|
||||
*self = ManagedKeys::FullyDerived(key_manager);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// all of the keys really shouldn't be wrapped in `Arc`, but due to how the gateway client is currently
|
||||
// constructed, changing that would require more work than what it's worth
|
||||
pub struct KeyManagerBuilder {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// encryption key associated with the client instance.
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
}
|
||||
|
||||
impl KeyManagerBuilder {
|
||||
/// Creates new instance of a [`KeyManager`]
|
||||
pub fn new<R>(rng: &mut R) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
KeyManagerBuilder {
|
||||
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
|
||||
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
|
||||
ack_key: Arc::new(AckKey::new(rng)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_gateway_shared_key(self, gateway_shared_key: Arc<SharedKeys>) -> KeyManager {
|
||||
KeyManager {
|
||||
identity_keypair: self.identity_keypair,
|
||||
encryption_keypair: self.encryption_keypair,
|
||||
gateway_shared_key,
|
||||
ack_key: self.ack_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
||||
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
||||
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
|
||||
// And I guess there will need to be some mechanism for a grace period when you can still
|
||||
// use the old key after new one was issued.
|
||||
|
||||
// Remember that Arc<T> has Deref implementation for T
|
||||
#[derive(Clone)]
|
||||
pub struct KeyManager {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// encryption key associated with the client instance.
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// shared key derived with the gateway during "registration handshake"
|
||||
gateway_shared_key: Arc<SharedKeys>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
}
|
||||
|
||||
impl KeyManager {
|
||||
pub fn from_keys(
|
||||
id_keypair: identity::KeyPair,
|
||||
enc_keypair: encryption::KeyPair,
|
||||
gateway_shared_key: SharedKeys,
|
||||
ack_key: AckKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
identity_keypair: Arc::new(id_keypair),
|
||||
encryption_keypair: Arc::new(enc_keypair),
|
||||
gateway_shared_key: Arc::new(gateway_shared_key),
|
||||
ack_key: Arc::new(ack_key),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_keys<S: KeyStore>(store: &S) -> Result<Self, S::StorageError> {
|
||||
store.load_keys().await
|
||||
}
|
||||
|
||||
pub async fn persist_keys<S: KeyStore>(&self, store: &S) -> Result<(), S::StorageError> {
|
||||
store.store_keys(self).await
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
/// Gets an atomically reference counted pointer to [`AckKey`].
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`SharedKey`].
|
||||
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
|
||||
Arc::clone(&self.gateway_shared_key)
|
||||
}
|
||||
|
||||
pub fn remove_gateway_key(self) -> KeyManagerBuilder {
|
||||
if Arc::strong_count(&self.gateway_shared_key) > 1 {
|
||||
panic!("attempted to remove gateway key whilst still holding multiple references!")
|
||||
}
|
||||
KeyManagerBuilder {
|
||||
identity_keypair: self.identity_keypair,
|
||||
encryption_keypair: self.encryption_keypair,
|
||||
ack_key: self.ack_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _assert_keys_zeroize_on_drop() {
|
||||
fn _assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
_assert_zeroize_on_drop::<identity::KeyPair>();
|
||||
_assert_zeroize_on_drop::<encryption::KeyPair>();
|
||||
_assert_zeroize_on_drop::<AckKey>();
|
||||
_assert_zeroize_on_drop::<SharedKeys>();
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::KeyManager;
|
||||
use async_trait::async_trait;
|
||||
use std::error::Error;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::KeyPairPath;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
|
||||
// we have to define it as an async trait since wasm storage is async
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait KeyStore {
|
||||
type StorageError: Error;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError>;
|
||||
|
||||
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum OnDiskKeysError {
|
||||
#[error("failed to load {keys} keys from {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
|
||||
KeyPairLoadFailure {
|
||||
keys: String,
|
||||
paths: nym_pemstore::KeyPairPath,
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to store {keys} keys to {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
|
||||
KeyPairStoreFailure {
|
||||
keys: String,
|
||||
paths: nym_pemstore::KeyPairPath,
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to load {key} key from {path}: {err}")]
|
||||
KeyLoadFailure {
|
||||
key: String,
|
||||
path: String,
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to store {key} key to {path}: {err}")]
|
||||
KeyStoreFailure {
|
||||
key: String,
|
||||
path: String,
|
||||
err: std::io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct OnDiskKeys {
|
||||
paths: ClientKeysPaths,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl From<ClientKeysPaths> for OnDiskKeys {
|
||||
fn from(paths: ClientKeysPaths) -> Self {
|
||||
OnDiskKeys { paths }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl OnDiskKeys {
|
||||
pub fn new(paths: ClientKeysPaths) -> Self {
|
||||
OnDiskKeys { paths }
|
||||
}
|
||||
|
||||
fn load_key<T: PemStorableKey>(
|
||||
&self,
|
||||
path: &std::path::Path,
|
||||
name: impl Into<String>,
|
||||
) -> Result<T, OnDiskKeysError> {
|
||||
nym_pemstore::load_key(path).map_err(|err| OnDiskKeysError::KeyLoadFailure {
|
||||
key: name.into(),
|
||||
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_keypair<T: PemStorableKeyPair>(
|
||||
&self,
|
||||
paths: KeyPairPath,
|
||||
name: impl Into<String>,
|
||||
) -> Result<T, OnDiskKeysError> {
|
||||
nym_pemstore::load_keypair(&paths).map_err(|err| OnDiskKeysError::KeyPairLoadFailure {
|
||||
keys: name.into(),
|
||||
paths,
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
fn store_key<T: PemStorableKey>(
|
||||
&self,
|
||||
key: &T,
|
||||
path: &std::path::Path,
|
||||
name: impl Into<String>,
|
||||
) -> Result<(), OnDiskKeysError> {
|
||||
nym_pemstore::store_key(key, path).map_err(|err| OnDiskKeysError::KeyStoreFailure {
|
||||
key: name.into(),
|
||||
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
fn store_keypair<T: PemStorableKeyPair>(
|
||||
&self,
|
||||
keys: &T,
|
||||
paths: KeyPairPath,
|
||||
name: impl Into<String>,
|
||||
) -> Result<(), OnDiskKeysError> {
|
||||
nym_pemstore::store_keypair(keys, &paths).map_err(|err| {
|
||||
OnDiskKeysError::KeyPairStoreFailure {
|
||||
keys: name.into(),
|
||||
paths,
|
||||
err,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
|
||||
let identity_paths = self.paths.identity_key_pair_path();
|
||||
let encryption_paths = self.paths.encryption_key_pair_path();
|
||||
|
||||
let identity_keypair: identity::KeyPair =
|
||||
self.load_keypair(identity_paths, "identity keys")?;
|
||||
let encryption_keypair: encryption::KeyPair =
|
||||
self.load_keypair(encryption_paths, "encryption keys")?;
|
||||
|
||||
let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?;
|
||||
let gateway_shared_key: SharedKeys =
|
||||
self.load_key(self.paths.gateway_shared_key(), "gateway shared keys")?;
|
||||
|
||||
Ok(KeyManager::from_keys(
|
||||
identity_keypair,
|
||||
encryption_keypair,
|
||||
gateway_shared_key,
|
||||
ack_key,
|
||||
))
|
||||
}
|
||||
|
||||
fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> {
|
||||
let identity_paths = self.paths.identity_key_pair_path();
|
||||
let encryption_paths = self.paths.encryption_key_pair_path();
|
||||
|
||||
self.store_keypair(
|
||||
keys.identity_keypair.as_ref(),
|
||||
identity_paths,
|
||||
"identity keys",
|
||||
)?;
|
||||
self.store_keypair(
|
||||
keys.encryption_keypair.as_ref(),
|
||||
encryption_paths,
|
||||
"encryption keys",
|
||||
)?;
|
||||
|
||||
self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?;
|
||||
self.store_key(
|
||||
keys.gateway_shared_key.as_ref(),
|
||||
self.paths.gateway_shared_key(),
|
||||
"gateway shared keys",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl KeyStore for OnDiskKeys {
|
||||
type StorageError = OnDiskKeysError;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
|
||||
self.load_keys()
|
||||
}
|
||||
|
||||
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
|
||||
self.store_keys(keys)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InMemEphemeralKeys;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("ephemeral keys can't be loaded from storage")]
|
||||
pub struct EphemeralKeysError;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl KeyStore for InMemEphemeralKeys {
|
||||
type StorageError = EphemeralKeysError;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
|
||||
Err(EphemeralKeysError)
|
||||
}
|
||||
|
||||
async fn store_keys(&self, _keys: &KeyManager) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -35,19 +35,20 @@ impl<C, St> MixTrafficController<C, St>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send + 'static,
|
||||
St: Storage + 'static,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
gateway_client: GatewayClient<C, St>,
|
||||
) -> (MixTrafficController<C, St>, BatchMixMessageSender) {
|
||||
let (sphinx_message_sender, sphinx_message_receiver) =
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_client,
|
||||
mix_rx: sphinx_message_receiver,
|
||||
mix_rx: message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
},
|
||||
sphinx_message_sender,
|
||||
message_sender,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ impl AcknowledgementListener {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
acks = self.ack_receiver.next() => match acks {
|
||||
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
||||
Some(acks) => {self.handle_ack_receiver_item(acks).await}
|
||||
None => {
|
||||
log::trace!("AcknowledgementListener: Stopping since channel closed");
|
||||
break;
|
||||
|
||||
+59
-4
@@ -3,10 +3,13 @@
|
||||
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver};
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
@@ -41,6 +44,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_premade_packets(&mut self, packets: Vec<MixPacket>, lane: TransmissionLane) {
|
||||
self.message_handler
|
||||
.send_premade_mix_packets(
|
||||
packets
|
||||
.into_iter()
|
||||
.map(|p| RealMessage::new(p, None))
|
||||
.collect(),
|
||||
lane,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn handle_reply(
|
||||
&mut self,
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
@@ -57,10 +72,11 @@ where
|
||||
recipient: Recipient,
|
||||
content: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_plain_message(recipient, content, lane)
|
||||
.try_send_plain_message(recipient, content, lane, packet_type)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send a plain message - {err}")
|
||||
@@ -73,10 +89,11 @@ where
|
||||
content: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
|
||||
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send a repliable message - {err}")
|
||||
@@ -89,14 +106,17 @@ where
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
} => self.handle_plain_message(recipient, data, lane).await,
|
||||
} => {
|
||||
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
|
||||
.await
|
||||
}
|
||||
InputMessage::Anonymous {
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
} => {
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane)
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
|
||||
.await
|
||||
}
|
||||
InputMessage::Reply {
|
||||
@@ -106,6 +126,41 @@ where
|
||||
} => {
|
||||
self.handle_reply(recipient_tag, data, lane).await;
|
||||
}
|
||||
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
|
||||
InputMessage::MessageWrapper {
|
||||
message,
|
||||
packet_type,
|
||||
} => match *message {
|
||||
InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
} => {
|
||||
self.handle_plain_message(recipient, data, lane, packet_type)
|
||||
.await
|
||||
}
|
||||
InputMessage::Anonymous {
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
} => {
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
|
||||
.await
|
||||
}
|
||||
InputMessage::Reply {
|
||||
recipient_tag,
|
||||
data,
|
||||
lane,
|
||||
} => {
|
||||
self.handle_reply(recipient_tag, data, lane).await;
|
||||
}
|
||||
InputMessage::Premade { msgs, lane } => {
|
||||
self.handle_premade_packets(msgs, lane).await
|
||||
}
|
||||
// MessageWrappers can't be nested
|
||||
InputMessage::MessageWrapper { .. } => unimplemented!(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::{
|
||||
acknowledgements::AckKey,
|
||||
addressing::clients::Recipient,
|
||||
@@ -249,7 +249,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
|
||||
pub(super) fn start_with_shutdown(
|
||||
self,
|
||||
shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener;
|
||||
let mut input_message_listener = self.input_message_listener;
|
||||
let mut retransmission_request_listener = self.retransmission_request_listener;
|
||||
@@ -275,7 +279,7 @@ where
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
retransmission_request_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.run_with_shutdown(shutdown_handle, packet_type)
|
||||
.await;
|
||||
debug!("The retransmission request listener has finished execution!");
|
||||
});
|
||||
|
||||
+16
-5
@@ -11,9 +11,9 @@ use crate::client::real_messages_control::real_traffic_stream::RealMessage;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
@@ -48,17 +48,20 @@ where
|
||||
&mut self,
|
||||
packet_recipient: Recipient,
|
||||
chunk_data: Fragment,
|
||||
packet_type: PacketType,
|
||||
) -> Result<PreparedFragment, PreparationError> {
|
||||
debug!("retransmitting normal packet...");
|
||||
|
||||
// TODO: Figure out retransmission packet type signaling
|
||||
self.message_handler
|
||||
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data)
|
||||
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn on_retransmission_request(
|
||||
&mut self,
|
||||
weak_timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
let timed_out_ack = match weak_timed_out_ack.upgrade() {
|
||||
Some(timed_out_ack) => timed_out_ack,
|
||||
@@ -85,6 +88,7 @@ where
|
||||
self.prepare_normal_retransmission_chunk(
|
||||
**recipient,
|
||||
timed_out_ack.message_chunk.clone(),
|
||||
packet_type,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -131,19 +135,26 @@ where
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
self.message_handler
|
||||
.forward_messages(
|
||||
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
|
||||
vec![RealMessage::new(
|
||||
prepared_fragment.mix_packet,
|
||||
Some(frag_id),
|
||||
)],
|
||||
TransmissionLane::Retransmission,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
pub(super) async fn run_with_shutdown(
|
||||
&mut self,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
|
||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
|
||||
None => {
|
||||
log::trace!("RetransmissionRequestListener: Stopping since channel closed");
|
||||
break;
|
||||
|
||||
@@ -15,7 +15,7 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
|
||||
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
|
||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
|
||||
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS};
|
||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nym_sphinx::Delay;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
@@ -27,7 +27,7 @@ use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
// TODO: move that error elsewhere since it seems to be contaminating different files
|
||||
#[derive(Debug, Clone, Error)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PreparationError {
|
||||
#[error(transparent)]
|
||||
NymTopologyError(#[from] NymTopologyError),
|
||||
@@ -291,8 +291,10 @@ where
|
||||
.try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone)
|
||||
.await?;
|
||||
|
||||
let real_messages =
|
||||
RealMessage::new(prepared_fragment.mix_packet, chunk.fragment_identifier());
|
||||
let real_messages = RealMessage::new(
|
||||
prepared_fragment.mix_packet,
|
||||
Some(chunk.fragment_identifier()),
|
||||
);
|
||||
let delay = prepared_fragment.total_delay;
|
||||
let pending_ack =
|
||||
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
|
||||
@@ -384,7 +386,8 @@ where
|
||||
let lane = raw.0;
|
||||
let fragment = raw.1;
|
||||
|
||||
let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
|
||||
let real_message =
|
||||
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
|
||||
let delay = prepared.total_delay;
|
||||
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
|
||||
|
||||
@@ -401,14 +404,23 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn send_premade_mix_packets(
|
||||
&mut self,
|
||||
msgs: Vec<RealMessage>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
self.forward_messages(msgs, lane).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_plain_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(), PreparationError> {
|
||||
let message = NymMessage::new_plain(message);
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane)
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -417,7 +429,9 @@ where
|
||||
message: NymMessage,
|
||||
recipient: Recipient,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("Sending non-reply message with packet type {packet_type}");
|
||||
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
|
||||
debug_assert!(!matches!(message, NymMessage::Reply(_)));
|
||||
|
||||
@@ -425,7 +439,11 @@ where
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
let packet_size = self.optimal_packet_size(&message);
|
||||
let packet_size = if packet_type == PacketType::Outfox {
|
||||
PacketSize::OutfoxRegularPacket
|
||||
} else {
|
||||
self.optimal_packet_size(&message)
|
||||
};
|
||||
debug!("Using {packet_size} packets for {message}");
|
||||
let fragments = self
|
||||
.message_preparer
|
||||
@@ -442,10 +460,13 @@ where
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
packet_type,
|
||||
)?;
|
||||
|
||||
let real_message =
|
||||
RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier());
|
||||
let real_message = RealMessage::new(
|
||||
prepared_fragment.mix_packet,
|
||||
Some(fragment.fragment_identifier()),
|
||||
);
|
||||
let delay = prepared_fragment.total_delay;
|
||||
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
|
||||
|
||||
@@ -463,7 +484,9 @@ where
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
amount: u32,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("Sending additional reply SURBs with packet type {packet_type}");
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
let (reply_surbs, reply_keys) =
|
||||
self.generate_reply_surbs_with_keys(amount as usize).await?;
|
||||
@@ -477,6 +500,7 @@ where
|
||||
message,
|
||||
recipient,
|
||||
TransmissionLane::AdditionalReplySurbs,
|
||||
packet_type,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -492,7 +516,9 @@ where
|
||||
message: Vec<u8>,
|
||||
num_reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
debug!("Sending message with reply SURBs with packet type {packet_type}");
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
let (reply_surbs, reply_keys) = self
|
||||
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
|
||||
@@ -501,7 +527,7 @@ where
|
||||
let message =
|
||||
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
|
||||
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane)
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
|
||||
.await?;
|
||||
|
||||
log::trace!("storing {} reply keys", reply_keys.len());
|
||||
@@ -514,13 +540,21 @@ where
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
chunk: Fragment,
|
||||
packet_type: PacketType,
|
||||
) -> Result<PreparedFragment, PreparationError> {
|
||||
debug!("Sending single chunk with packet type {packet_type}");
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk, topology, &self.config.ack_key, &recipient)
|
||||
.prepare_chunk_for_sending(
|
||||
chunk,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
packet_type,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(prepared_fragment)
|
||||
@@ -556,6 +590,7 @@ where
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
reply_surb,
|
||||
PacketType::Mix,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
@@ -575,7 +610,13 @@ where
|
||||
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_reply_chunk_for_sending(chunk, topology, &self.config.ack_key, reply_surb)
|
||||
.prepare_reply_chunk_for_sending(
|
||||
chunk,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
reply_surb,
|
||||
PacketType::Mix,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(prepared_fragment)
|
||||
|
||||
@@ -26,6 +26,7 @@ use log::*;
|
||||
use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
@@ -207,7 +208,7 @@ impl RealMessagesController<OsRng> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
|
||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient, packet_type: PacketType) {
|
||||
let mut out_queue_control = self.out_queue_control;
|
||||
let ack_control = self.ack_control;
|
||||
let mut reply_control = self.reply_control;
|
||||
@@ -223,6 +224,6 @@ impl RealMessagesController<OsRng> {
|
||||
debug!("The reply controller has finished execution!");
|
||||
});
|
||||
|
||||
ack_control.start_with_shutdown(shutdown);
|
||||
ack_control.start_with_shutdown(shutdown, packet_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use self::sending_delay_controller::SendingDelayController;
|
||||
@@ -92,7 +92,7 @@ where
|
||||
// messages.
|
||||
sending_delay_controller: SendingDelayController,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// Channel used for sending prepared packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
mix_tx: BatchMixMessageSender,
|
||||
|
||||
@@ -121,7 +121,7 @@ where
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RealMessage {
|
||||
mix_packet: MixPacket,
|
||||
fragment_id: FragmentIdentifier,
|
||||
fragment_id: Option<FragmentIdentifier>,
|
||||
// TODO: add info about it being constructed with reply-surb
|
||||
}
|
||||
|
||||
@@ -129,17 +129,17 @@ impl From<PreparedFragment> for RealMessage {
|
||||
fn from(fragment: PreparedFragment) -> Self {
|
||||
RealMessage {
|
||||
mix_packet: fragment.mix_packet,
|
||||
fragment_id: fragment.fragment_identifier,
|
||||
fragment_id: Some(fragment.fragment_identifier),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RealMessage {
|
||||
pub(crate) fn packet_size(&self) -> usize {
|
||||
self.mix_packet.sphinx_packet().len()
|
||||
self.mix_packet.packet().len()
|
||||
}
|
||||
|
||||
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
|
||||
pub(crate) fn new(mix_packet: MixPacket, fragment_id: Option<FragmentIdentifier>) -> Self {
|
||||
RealMessage {
|
||||
mix_packet,
|
||||
fragment_id,
|
||||
@@ -247,6 +247,7 @@ where
|
||||
self.config.average_ack_delay,
|
||||
self.config.traffic.average_packet_delay,
|
||||
cover_traffic_packet_size,
|
||||
self.config.traffic.packet_type,
|
||||
)
|
||||
.expect(
|
||||
"Somehow failed to generate a loop cover message with a valid topology",
|
||||
@@ -255,7 +256,7 @@ where
|
||||
)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
(real_message.mix_packet, Some(real_message.fragment_id))
|
||||
(real_message.mix_packet, real_message.fragment_id)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -386,7 +387,7 @@ where
|
||||
|
||||
// On every iteration we get new messages from upstream. Given that these come bunched
|
||||
// in `Vec`, this ensures that on average we will fetch messages faster than we can
|
||||
// send, which is a condition for being able to multiplex sphinx packets from multiple
|
||||
// send, which is a condition for being able to multiplex packets from multiple
|
||||
// data streams.
|
||||
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
|
||||
@@ -512,7 +512,11 @@ where
|
||||
let to_send = min(remaining, 100);
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_additional_reply_surbs(recipient, to_send)
|
||||
.try_send_additional_reply_surbs(
|
||||
recipient,
|
||||
to_send,
|
||||
nym_sphinx::params::PacketType::Mix,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send additional surbs to {recipient} - {err}");
|
||||
|
||||
@@ -5,8 +5,6 @@ use crate::client::replies::reply_storage::backend::Empty;
|
||||
use crate::client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
// well, right now we don't have the browser storage : (
|
||||
// so we keep everything in memory
|
||||
#[derive(Debug)]
|
||||
@@ -29,22 +27,6 @@ impl Backend {
|
||||
impl ReplyStorageBackend for Backend {
|
||||
type StorageError = <Empty as ReplyStorageBackend>::StorageError;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
_db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
Ok(Backend {
|
||||
empty: Empty {
|
||||
min_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
storage: &CombinedReplyStorage,
|
||||
@@ -59,8 +41,4 @@ impl ReplyStorageBackend for Backend {
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
self.empty.load_surb_storage().await
|
||||
}
|
||||
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
self.empty.get_inactive_storage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::base_client::non_wasm_helpers;
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::manager::StorageManager;
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
|
||||
@@ -23,49 +22,11 @@ mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum StorageManagerState {
|
||||
Storage(StorageManager),
|
||||
Inactive(InactiveMetadata),
|
||||
}
|
||||
|
||||
// When the storage backaed is initialized as inactive, it will still contain metadata parameters
|
||||
// that will be needed when the in-mem storage is fetched for use.
|
||||
#[derive(Debug)]
|
||||
struct InactiveMetadata {
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
}
|
||||
|
||||
impl StorageManagerState {
|
||||
fn get(&self) -> &StorageManager {
|
||||
match self {
|
||||
StorageManagerState::Storage(manager) => manager,
|
||||
StorageManagerState::Inactive(_) => {
|
||||
panic!("tried to get storage of an inactive backend")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut StorageManager {
|
||||
match self {
|
||||
StorageManagerState::Storage(manager) => manager,
|
||||
StorageManagerState::Inactive(_) => {
|
||||
panic!("tried to get storage of an inactive backend")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
matches!(self, StorageManagerState::Storage(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Backend {
|
||||
temporary_old_path: Option<PathBuf>,
|
||||
database_path: PathBuf,
|
||||
manager: StorageManagerState,
|
||||
manager: StorageManager,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
@@ -85,26 +46,12 @@ impl Backend {
|
||||
let backend = Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager: StorageManagerState::Storage(manager),
|
||||
manager,
|
||||
};
|
||||
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub fn new_inactive(
|
||||
minimum_reply_surb_storage_threshold: usize,
|
||||
maximum_reply_surb_storage_threshold: usize,
|
||||
) -> Self {
|
||||
Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: PathBuf::new(),
|
||||
manager: StorageManagerState::Inactive(InactiveMetadata {
|
||||
minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
@@ -176,12 +123,13 @@ impl Backend {
|
||||
Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager: StorageManagerState::Storage(manager),
|
||||
// manager: StorageManagerState::Storage(manager),
|
||||
manager,
|
||||
})
|
||||
}
|
||||
|
||||
async fn close_pool(&mut self) {
|
||||
self.manager.get_mut().connection_pool.close().await;
|
||||
self.manager.connection_pool.close().await;
|
||||
}
|
||||
|
||||
async fn rotate(&mut self) -> Result<(), StorageError> {
|
||||
@@ -200,9 +148,8 @@ impl Backend {
|
||||
|
||||
fs::rename(&self.database_path, &temp_old)
|
||||
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
|
||||
self.manager =
|
||||
StorageManagerState::Storage(StorageManager::init(&self.database_path, true).await?);
|
||||
self.manager.get_mut().create_status_table().await?;
|
||||
self.manager = StorageManager::init(&self.database_path, true).await?;
|
||||
self.manager.create_status_table().await?;
|
||||
|
||||
self.temporary_old_path = Some(temp_old);
|
||||
Ok(())
|
||||
@@ -219,27 +166,26 @@ impl Backend {
|
||||
}
|
||||
|
||||
async fn start_storage_flush(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.get().set_flush_status(true).await?)
|
||||
Ok(self.manager.set_flush_status(true).await?)
|
||||
}
|
||||
|
||||
async fn end_storage_flush(&self) -> Result<(), StorageError> {
|
||||
self.manager
|
||||
.get()
|
||||
.set_previous_flush_timestamp(OffsetDateTime::now_utc().unix_timestamp())
|
||||
.await?;
|
||||
Ok(self.manager.get().set_flush_status(false).await?)
|
||||
Ok(self.manager.set_flush_status(false).await?)
|
||||
}
|
||||
|
||||
async fn start_client_use(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.get().set_client_in_use_status(true).await?)
|
||||
Ok(self.manager.set_client_in_use_status(true).await?)
|
||||
}
|
||||
|
||||
async fn stop_client_use(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.get().set_client_in_use_status(false).await?)
|
||||
Ok(self.manager.set_client_in_use_status(false).await?)
|
||||
}
|
||||
|
||||
async fn get_stored_tags(&self) -> Result<UsedSenderTags, StorageError> {
|
||||
let stored = self.manager.get().get_tags().await?;
|
||||
let stored = self.manager.get_tags().await?;
|
||||
|
||||
// stop at the first instance of corruption. if even a single entry is malformed,
|
||||
// something weird has happened and we can't trust the rest of the data
|
||||
@@ -255,7 +201,6 @@ impl Backend {
|
||||
for map_ref in tags.as_raw_iter() {
|
||||
let (recipient, tag) = map_ref.pair();
|
||||
self.manager
|
||||
.get()
|
||||
.insert_tag(StoredSenderTag::new(*recipient, *tag))
|
||||
.await?;
|
||||
}
|
||||
@@ -263,7 +208,7 @@ impl Backend {
|
||||
}
|
||||
|
||||
async fn get_stored_reply_keys(&self) -> Result<SentReplyKeys, StorageError> {
|
||||
let stored = self.manager.get().get_reply_keys().await?;
|
||||
let stored = self.manager.get_reply_keys().await?;
|
||||
|
||||
// stop at the first instance of corruption. if even a single entry is malformed,
|
||||
// something weird has happened and we can't trust the rest of the data
|
||||
@@ -279,7 +224,6 @@ impl Backend {
|
||||
for map_ref in reply_keys.as_raw_iter() {
|
||||
let (digest, key) = map_ref.pair();
|
||||
self.manager
|
||||
.get()
|
||||
.insert_reply_key(StoredReplyKey::new(*digest, *key))
|
||||
.await?;
|
||||
}
|
||||
@@ -287,7 +231,7 @@ impl Backend {
|
||||
}
|
||||
|
||||
async fn get_stored_reply_surbs(&self) -> Result<ReceivedReplySurbsMap, StorageError> {
|
||||
let surb_senders = self.manager.get().get_surb_senders().await?;
|
||||
let surb_senders = self.manager.get_surb_senders().await?;
|
||||
|
||||
let metadata = self.get_reply_surb_storage_metadata().await?;
|
||||
let mut received_surbs = Vec::with_capacity(surb_senders.len());
|
||||
@@ -297,7 +241,6 @@ impl Backend {
|
||||
sender.try_into()?;
|
||||
let stored_surbs = self
|
||||
.manager
|
||||
.get()
|
||||
.get_reply_surbs(sender_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -325,7 +268,6 @@ impl Backend {
|
||||
let (tag, received_surbs) = map_ref.pair();
|
||||
let sender_id = self
|
||||
.manager
|
||||
.get()
|
||||
.insert_surb_sender(StoredSurbSender::new(
|
||||
*tag,
|
||||
received_surbs.surbs_last_received_at(),
|
||||
@@ -334,7 +276,6 @@ impl Backend {
|
||||
|
||||
for reply_surb in received_surbs.surbs_ref() {
|
||||
self.manager
|
||||
.get()
|
||||
.insert_reply_surb(StoredReplySurb::new(sender_id, reply_surb))
|
||||
.await?
|
||||
}
|
||||
@@ -346,7 +287,6 @@ impl Backend {
|
||||
&self,
|
||||
) -> Result<ReplySurbStorageMetadata, StorageError> {
|
||||
self.manager
|
||||
.get()
|
||||
.get_reply_surb_storage_metadata()
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
@@ -357,7 +297,6 @@ impl Backend {
|
||||
reply_surbs: &ReceivedReplySurbsMap,
|
||||
) -> Result<(), StorageError> {
|
||||
self.manager
|
||||
.get()
|
||||
.insert_reply_surb_storage_metadata(ReplySurbStorageMetadata::new(
|
||||
reply_surbs.min_surb_threshold(),
|
||||
reply_surbs.max_surb_threshold(),
|
||||
@@ -371,24 +310,6 @@ impl Backend {
|
||||
impl ReplyStorageBackend for Backend {
|
||||
type StorageError = error::StorageError;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(db_path, debug_config)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
log::error!("Failed to create storage: {err}");
|
||||
Self::StorageError::FailedToCreateStorage {
|
||||
source: Box::new(err),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
self.manager.is_active()
|
||||
}
|
||||
|
||||
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
|
||||
self.start_client_use().await
|
||||
}
|
||||
@@ -426,18 +347,6 @@ impl ReplyStorageBackend for Backend {
|
||||
Ok(CombinedReplyStorage::load(reply_keys, reply_surbs, tags))
|
||||
}
|
||||
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
match self.manager {
|
||||
StorageManagerState::Storage(_) => {
|
||||
panic!("tried to get inactive storage from an active storage backend")
|
||||
}
|
||||
StorageManagerState::Inactive(ref state) => Ok(CombinedReplyStorage::new(
|
||||
state.minimum_reply_surb_storage_threshold,
|
||||
state.maximum_reply_surb_storage_threshold,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||
self.stop_client_use().await
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use async_trait::async_trait;
|
||||
use std::{error::Error, path::PathBuf};
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -26,24 +26,19 @@ pub struct Empty {
|
||||
pub max_surb_threshold: usize,
|
||||
}
|
||||
|
||||
impl Default for Empty {
|
||||
fn default() -> Self {
|
||||
Empty {
|
||||
min_surb_threshold: 20,
|
||||
max_surb_threshold: 200,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReplyStorageBackend for Empty {
|
||||
type StorageError = UndefinedError;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
_db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
Ok(Self {
|
||||
min_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
})
|
||||
}
|
||||
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
_storage: &CombinedReplyStorage,
|
||||
@@ -64,28 +59,12 @@ impl ReplyStorageBackend for Empty {
|
||||
self.max_surb_threshold,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
Ok(CombinedReplyStorage::new(
|
||||
self.min_surb_threshold,
|
||||
self.max_surb_threshold,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ReplyStorageBackend: Sized {
|
||||
type StorageError: Error + 'static;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError>;
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -103,11 +82,6 @@ pub trait ReplyStorageBackend: Sized {
|
||||
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError>;
|
||||
|
||||
/// In the case the storage backend is initialized in an inactive state (persisting data is
|
||||
/// disabled), we might still need to fetch the (in-mem) storage and the parameters it was
|
||||
/// created with.
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError>;
|
||||
|
||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ impl TopologyRefresherConfig {
|
||||
}
|
||||
|
||||
pub struct TopologyRefresher {
|
||||
topology_provider: Box<dyn TopologyProvider>,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
topology_accessor: TopologyAccessor,
|
||||
|
||||
refresh_rate: Duration,
|
||||
@@ -37,7 +37,7 @@ impl TopologyRefresher {
|
||||
pub fn new(
|
||||
cfg: TopologyRefresherConfig,
|
||||
topology_accessor: TopologyAccessor,
|
||||
topology_provider: Box<dyn TopologyProvider>,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Self {
|
||||
TopologyRefresher {
|
||||
topology_provider,
|
||||
@@ -47,7 +47,7 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider>) {
|
||||
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider + Send + Sync>) {
|
||||
self.topology_provider = provider;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user