Compare commits
77 Commits
base
...
release/v1.1.21
| Author | SHA1 | Date | |
|---|---|---|---|
| 4caa9390cf | |||
| 79f5983c76 | |||
| 335453b63c | |||
| 92e902c81e | |||
| c47bc174bc | |||
| f97f0475e9 | |||
| 67a945a15f | |||
| d1a28826d5 | |||
| 8f026ab6c6 | |||
| bdcdcf7f8b | |||
| 182e147a86 | |||
| 687b437ea0 | |||
| f2c5dbb696 | |||
| a8bf690c17 | |||
| 1328ba35be | |||
| 8b046d4139 | |||
| f4cd372808 | |||
| 7228331db6 | |||
| 45f3f3ec01 | |||
| 5f9e54c83c | |||
| f8c2f90502 | |||
| 71fb6a1ba1 | |||
| 542fd92a46 | |||
| 892653cd96 | |||
| a7471ef324 | |||
| 403141c1f5 | |||
| d8c82bf6d0 | |||
| dd86ba36dd | |||
| 7c55483585 | |||
| 0320220219 | |||
| e32ee2ccf3 | |||
| b8ca1762c2 | |||
| d1e9fcf03a | |||
| 303a774378 | |||
| c9ca71f47b | |||
| 92faf1e3d5 | |||
| e509989ac3 | |||
| c04cc9a4cf | |||
| 17258d1445 | |||
| 8f3d7606f5 | |||
| fd97f0e8ca | |||
| d4ce1635a8 | |||
| 2bc564ad01 | |||
| 5910bcbc02 | |||
| 8c63fe9d0d | |||
| 6e5a1973da | |||
| 1aa11887aa | |||
| 07740cbf08 | |||
| 87cb8a6b20 | |||
| 2977b8f25f | |||
| 9ae4fd04ac | |||
| 4470969bec | |||
| 1a4c3a7709 | |||
| 99b31920d5 | |||
| 019b3299f2 | |||
| d684957423 | |||
| cb4eda4c62 | |||
| ac5f380ee2 | |||
| 4e278ca07d | |||
| cd6a725875 | |||
| 62ccb6b4cd | |||
| 365e0134b4 | |||
| d5514a060c | |||
| 8432c30f6c | |||
| c2764f90b3 | |||
| 958b6d37ee | |||
| 5e36bb014c | |||
| 8d821881ae | |||
| fca9761145 | |||
| 11481e4d13 | |||
| a6a39d1234 | |||
| 5f35d54fcb | |||
| c8b82a9553 | |||
| 00c2f5359c | |||
| 1a4e0f4e08 | |||
| 69230a10cb | |||
| 3a0c8f3f4e |
@@ -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
|
||||
@@ -107,6 +107,8 @@ jobs:
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_service_provider_directory.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_name_service.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -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: ???
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,54 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.21] (2023-06-13)
|
||||
|
||||
- mixFetch: Change socks5 `SendRequest` to include OrderedMessage index as a field rather than making it serialized inside the `data` field
|
||||
([#3534])
|
||||
- Explorer - add more data columns to the Service Provider section: ([#3474])
|
||||
- network-requester: support report if they run an open proxy using `ControlRequest` API ([#3461])
|
||||
- Refactor client configs (London discussion) ([#3444])
|
||||
- Increase `DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE` to 2000 to improve reliability ([#3433])
|
||||
- socks5: sender waits for lanes to clear even though the connection is closed ([#3366])
|
||||
- version bump for variables ([#3545])
|
||||
|
||||
[#3534]: https://github.com/nymtech/nym/issues/3534
|
||||
[#3474]: https://github.com/nymtech/nym/issues/3474
|
||||
[#3461]: https://github.com/nymtech/nym/issues/3461
|
||||
[#3444]: https://github.com/nymtech/nym/issues/3444
|
||||
[#3433]: https://github.com/nymtech/nym/issues/3433
|
||||
[#3366]: https://github.com/nymtech/nym/issues/3366
|
||||
[#3545]: https://github.com/nymtech/nym/pull/3545
|
||||
|
||||
## [v1.1.20] (2023-06-06)
|
||||
|
||||
- Explorer - Fix SP supported apps list ([#3458])
|
||||
- Investigate if we need to lower `SHUTDOWN_TIMEOUT` in socks5 to zero (or almost zero) ([#3438])
|
||||
- Explorer - show all gateways in the default view regardless of the version number ([#3427])
|
||||
- service-provider-directory: add signature check when announcing ([#3360])
|
||||
- Support functionality for nym-name-service (nym-api, nym-cli, etc) ([#3355])
|
||||
- Edit the nym-network-requester to support the enabled-credentials-mode flag ([#3101])
|
||||
- [BUG] network requester documentation update ([#3493])
|
||||
- removing hardcoded version numbers ([#3485])
|
||||
- [BUG] network requester documentation update ([#3481])
|
||||
- [BUG] network requester documentation update ([#3469])
|
||||
- Sign when announcing service providers to the directory contract ([#3459])
|
||||
- mixnode documentation update ([#3435])
|
||||
- updated readme with new developer chat links + new docs links ([#3141])
|
||||
|
||||
[#3458]: https://github.com/nymtech/nym/issues/3458
|
||||
[#3438]: https://github.com/nymtech/nym/issues/3438
|
||||
[#3427]: https://github.com/nymtech/nym/issues/3427
|
||||
[#3360]: https://github.com/nymtech/nym/issues/3360
|
||||
[#3355]: https://github.com/nymtech/nym/issues/3355
|
||||
[#3101]: https://github.com/nymtech/nym/issues/3101
|
||||
[#3493]: https://github.com/nymtech/nym/pull/3493
|
||||
[#3485]: https://github.com/nymtech/nym/pull/3485
|
||||
[#3481]: https://github.com/nymtech/nym/pull/3481
|
||||
[#3469]: https://github.com/nymtech/nym/pull/3469
|
||||
[#3459]: https://github.com/nymtech/nym/pull/3459
|
||||
[#3435]: https://github.com/nymtech/nym/pull/3435
|
||||
[#3141]: https://github.com/nymtech/nym/pull/3141
|
||||
|
||||
## [v1.1.19] (2023-05-16)
|
||||
|
||||
|
||||
Generated
+50
-15
@@ -378,6 +378,15 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bip32"
|
||||
version = "0.3.0"
|
||||
@@ -1243,6 +1252,20 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-controllers"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.13.4"
|
||||
@@ -1632,7 +1655,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.2.7",
|
||||
@@ -3176,7 +3199,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.20"
|
||||
version = "1.1.22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3310,7 +3333,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -3359,6 +3382,7 @@ dependencies = [
|
||||
"nym-name-service-common",
|
||||
"nym-network-defaults",
|
||||
"nym-service-provider-directory-common",
|
||||
"nym-sphinx",
|
||||
"nym-validator-client",
|
||||
"nym-vesting-contract-common",
|
||||
"rand 0.6.5",
|
||||
@@ -3373,7 +3397,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
dependencies = [
|
||||
"clap 4.2.7",
|
||||
"dirs",
|
||||
@@ -3531,7 +3555,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-contracts-common"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -3587,7 +3611,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-crypto"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"aes 0.8.2",
|
||||
"blake3",
|
||||
@@ -3644,7 +3668,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3776,7 +3800,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -3795,7 +3819,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.20"
|
||||
version = "1.1.22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3905,10 +3929,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-file-watcher",
|
||||
"async-trait",
|
||||
"bs58",
|
||||
"clap 4.2.7",
|
||||
"dirs",
|
||||
"futures",
|
||||
@@ -3923,7 +3949,6 @@ dependencies = [
|
||||
"nym-credential-storage",
|
||||
"nym-crypto",
|
||||
"nym-network-defaults",
|
||||
"nym-ordered-buffer",
|
||||
"nym-sdk",
|
||||
"nym-service-providers-common",
|
||||
"nym-socks5-proxy-helpers",
|
||||
@@ -3931,11 +3956,13 @@ dependencies = [
|
||||
"nym-sphinx",
|
||||
"nym-statistics-common",
|
||||
"nym-task",
|
||||
"nym-types",
|
||||
"pretty_env_logger",
|
||||
"publicsuffix",
|
||||
"rand 0.7.3",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx 0.6.3",
|
||||
"tap",
|
||||
"tempfile",
|
||||
@@ -3947,7 +3974,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -4022,7 +4049,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-pemstore"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
]
|
||||
@@ -4063,8 +4090,12 @@ name = "nym-service-provider-directory-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
"cw-utils",
|
||||
"nym-contracts-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4076,6 +4107,7 @@ dependencies = [
|
||||
"log",
|
||||
"nym-bin-common",
|
||||
"nym-sdk",
|
||||
"nym-socks5-requests",
|
||||
"nym-sphinx-anonymous-replies",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4085,7 +4117,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
dependencies = [
|
||||
"clap 4.2.7",
|
||||
"lazy_static",
|
||||
@@ -4180,10 +4212,13 @@ dependencies = [
|
||||
name = "nym-socks5-requests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"log",
|
||||
"nym-service-providers-common",
|
||||
"nym-sphinx-addressing",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tap",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -4481,7 +4516,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"nym-contracts-common",
|
||||
|
||||
+2
-1
@@ -120,8 +120,9 @@ cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-utils = "=0.13.4"
|
||||
cw-controllers = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw-utils = "=0.13.4"
|
||||
cw2 = { version = "=0.13.4" }
|
||||
cw3 = { version = "=0.13.4" }
|
||||
cw3-fixed-multisig = { version = "=0.13.4" }
|
||||
|
||||
@@ -21,8 +21,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
|
||||
### Building
|
||||
|
||||
Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/building-nym.html).
|
||||
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/stable/nym-apps/wallet#for-developers).
|
||||
Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/build-nym.html).
|
||||
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/wallet/desktop-wallet.html).
|
||||
|
||||
### Developing
|
||||
|
||||
@@ -32,7 +32,11 @@ For Typescript components, please see [ts-packages](./ts-packages).
|
||||
|
||||
### Developer chat
|
||||
|
||||
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.
|
||||
> We used to use Keybase for developer chats, but we have since migrated to Matrix and Discord. We no longer check the old **nymtech.friends** Keybase team.
|
||||
|
||||
You can chat to us in two places:
|
||||
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
|
||||
* The various developer channels on [Discord](https://discord.gg/nym)
|
||||
|
||||
### Rewards
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
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"
|
||||
|
||||
Generated
+23
-5
@@ -865,6 +865,20 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-controllers"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.13.4"
|
||||
@@ -2367,7 +2381,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-contracts-common"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -2402,7 +2416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-crypto"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"aes 0.8.2",
|
||||
"blake3",
|
||||
@@ -2509,7 +2523,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -2609,7 +2623,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-pemstore"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
]
|
||||
@@ -2619,8 +2633,12 @@ name = "nym-service-provider-directory-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
"cw-utils",
|
||||
"nym-contracts-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2873,7 +2891,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"nym-contracts-common",
|
||||
|
||||
@@ -565,7 +565,7 @@ where
|
||||
fragments: Vec<Fragment>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
) -> Result<Vec<PreparedFragment>, SurbWrappedPreparationError> {
|
||||
debug_assert_ne!(
|
||||
debug_assert_eq!(
|
||||
fragments.len(),
|
||||
reply_surbs.len(),
|
||||
"attempted to send {} fragments with {} reply surbs",
|
||||
|
||||
@@ -155,7 +155,7 @@ pub async fn get_registered_gateway<S>(
|
||||
key_store: &S::KeyStore,
|
||||
setup: GatewaySetup,
|
||||
overwrite_keys: bool,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError>
|
||||
) -> Result<(GatewayEndpointConfig, ManagedKeys), ClientCoreError>
|
||||
where
|
||||
S: MixnetClientStorage,
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
@@ -164,11 +164,11 @@ where
|
||||
|
||||
// try load keys
|
||||
let mut managed_keys = match ManagedKeys::try_load(key_store).await {
|
||||
Ok(_) => {
|
||||
Ok(loaded_keys) => {
|
||||
// if we loaded something and we don't have full gateway details, check if we can overwrite the data
|
||||
if let GatewaySetup::Predefined { config } = setup {
|
||||
// we already have defined gateway details AND a shared key, so nothing more for us to do
|
||||
return Ok(config);
|
||||
return Ok((config, loaded_keys));
|
||||
} else if overwrite_keys {
|
||||
ManagedKeys::generate_new(&mut rng)
|
||||
} else {
|
||||
@@ -196,7 +196,7 @@ where
|
||||
|
||||
// TODO: here we should be probably persisting gateway details as opposed to returning them
|
||||
|
||||
Ok(gateway_details)
|
||||
Ok((gateway_details, managed_keys))
|
||||
}
|
||||
|
||||
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
|
||||
@@ -299,6 +299,15 @@ pub fn get_client_address(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_identity_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
/// Get the client address by loading the keys from stored files.
|
||||
// TODO: rethink that sucker
|
||||
pub fn get_client_address_from_stored_ondisk_keys<T>(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use nym_contracts_common::ContractBuildInformation;
|
||||
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
|
||||
use nym_service_provider_directory_common::{
|
||||
msg::QueryMsg as SpQueryMsg,
|
||||
response::{
|
||||
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
|
||||
},
|
||||
NymAddress, ServiceId, ServiceInfo,
|
||||
NymAddress, Service, ServiceId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -63,17 +63,14 @@ pub trait SpDirectoryQueryClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
|
||||
async fn get_all_services(&self) -> Result<Vec<Service>, NyxdError> {
|
||||
let mut services = Vec::new();
|
||||
let mut start_after = None;
|
||||
|
||||
loop {
|
||||
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
|
||||
|
||||
let last_id = paged_response.services.last().map(|serv| serv.service_id);
|
||||
services.append(&mut paged_response.services);
|
||||
|
||||
if let Some(start_after_res) = last_id {
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
@@ -82,6 +79,13 @@ pub trait SpDirectoryQueryClient {
|
||||
|
||||
Ok(services)
|
||||
}
|
||||
|
||||
async fn get_service_signing_nonce(&self, address: &AccountId) -> Result<Nonce, NyxdError> {
|
||||
self.query_service_provider_contract(SpQueryMsg::SigningNonce {
|
||||
address: address.to_string(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use nym_service_provider_directory_common::{
|
||||
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
|
||||
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceDetails, ServiceId,
|
||||
};
|
||||
|
||||
use crate::nyxd::{
|
||||
@@ -22,16 +23,16 @@ pub trait SpDirectorySigningClient {
|
||||
|
||||
async fn announce_service_provider(
|
||||
&self,
|
||||
nym_address: NymAddress,
|
||||
service_type: ServiceType,
|
||||
service: ServiceDetails,
|
||||
owner_signature: MessageSignature,
|
||||
deposit: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_service_provider_directory_contract(
|
||||
fee,
|
||||
SpExecuteMsg::Announce {
|
||||
nym_address,
|
||||
service_type,
|
||||
service,
|
||||
owner_signature,
|
||||
},
|
||||
vec![deposit],
|
||||
)
|
||||
|
||||
@@ -40,3 +40,4 @@ nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
|
||||
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct Mixnet {
|
||||
pub command: MixnetCommands,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetCommands {
|
||||
/// Query the mixnet directory
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct MixnetOperators {
|
||||
pub command: MixnetOperatorsCommands,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsCommands {
|
||||
/// Manage your mixnode
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceDetails, ServiceType};
|
||||
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
@@ -10,9 +11,15 @@ pub struct Args {
|
||||
#[clap(long)]
|
||||
pub nym_address: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub signature: MessageSignature,
|
||||
|
||||
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
|
||||
#[clap(long)]
|
||||
pub deposit: u128,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
}
|
||||
|
||||
pub async fn announce(args: Args, client: SigningClient) {
|
||||
@@ -20,12 +27,17 @@ pub async fn announce(args: Args, client: SigningClient) {
|
||||
|
||||
let nym_address = NymAddress::Address(args.nym_address);
|
||||
let service_type = ServiceType::NetworkRequester;
|
||||
let service = ServiceDetails {
|
||||
nym_address,
|
||||
service_type,
|
||||
identity_key: args.identity_key,
|
||||
};
|
||||
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
let deposit = Coin::new(args.deposit, denom);
|
||||
|
||||
let res = client
|
||||
.announce_service_provider(nym_address, service_type, deposit.into(), None)
|
||||
.announce_service_provider(service, args.signature, deposit.into(), None)
|
||||
.await
|
||||
.expect("Failed to announce service provider");
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
context::SigningClient,
|
||||
utils::{account_id_to_cw_addr, DataWrapper},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmwasm_std::Coin;
|
||||
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_service_provider_directory_common::{
|
||||
signing_types::construct_service_provider_announce_sign_payload, NymAddress,
|
||||
ServiceType::NetworkRequester,
|
||||
};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub nym_address: Recipient,
|
||||
|
||||
#[clap(long)]
|
||||
pub amount: u128,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
pub async fn create_payload(args: Args, client: SigningClient) {
|
||||
let service = nym_service_provider_directory_common::ServiceDetails {
|
||||
nym_address: NymAddress::new(&args.nym_address.to_string()),
|
||||
service_type: NetworkRequester,
|
||||
identity_key: args.identity_key,
|
||||
};
|
||||
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
let deposit = Coin::new(args.amount, denom);
|
||||
|
||||
let nonce = match client.get_service_signing_nonce(client.address()).await {
|
||||
Ok(nonce) => nonce,
|
||||
Err(err) => {
|
||||
eprint!(
|
||||
"failed to query for the signing nonce of {}: {err}",
|
||||
client.address()
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let address = account_id_to_cw_addr(client.address());
|
||||
let payload =
|
||||
construct_service_provider_announce_sign_payload(nonce, address, deposit, service);
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod announce;
|
||||
pub mod announce_sign_payload;
|
||||
pub mod delete;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
@@ -10,10 +11,13 @@ pub struct MixnetOperatorsService {
|
||||
pub command: MixnetOperatorsServiceCommands,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsServiceCommands {
|
||||
/// Announce service provider to the world
|
||||
Announce(announce::Args),
|
||||
/// Delete entry for service provider from the directory
|
||||
Delete(delete::Args),
|
||||
/// Create base58-encoded payload required for producing valid announce signature.
|
||||
CreateServiceAnnounceSignPayload(announce_sign_payload::Args),
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
for service in res.services {
|
||||
table.add_row(vec![
|
||||
service.service_id.to_string(),
|
||||
service.service.announcer.to_string(),
|
||||
service.announcer.to_string(),
|
||||
service.service.service_type.to_string(),
|
||||
service.service.nym_address.to_string(),
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-contracts-common"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
description = "Common library for Nym cosmwasm contracts"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
@@ -11,6 +11,9 @@ use std::ops::Mul;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type IdentityKey = String;
|
||||
pub type IdentityKeyRef<'a> = &'a str;
|
||||
|
||||
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
|
||||
amount * Uint128::new(1)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
description = "Common library for the Nym mixnet contract"
|
||||
rust-version = "1.62"
|
||||
edition = { workspace = true }
|
||||
@@ -15,7 +15,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
thiserror = "1.0"
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
|
||||
# use 0.4.1 as that's the version used by cosmwasm-std 1.0.0
|
||||
# (and ideally we don't want to pull the same dependency twice)
|
||||
serde-json-wasm = "=0.4.1"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::families::{Family, FamilyHead};
|
||||
use crate::{Layer, RewardedSetNodeStatus};
|
||||
use contracts_common::IdentityKey;
|
||||
use cosmwasm_std::Addr;
|
||||
use cosmwasm_std::Coin;
|
||||
use schemars::JsonSchema;
|
||||
@@ -11,8 +12,6 @@ use serde::{Deserialize, Serialize};
|
||||
use std::ops::Index;
|
||||
|
||||
// type aliases for better reasoning about available data
|
||||
pub type IdentityKey = String;
|
||||
pub type IdentityKeyRef<'a> = &'a str;
|
||||
pub type SphinxKey = String;
|
||||
pub type SphinxKeyRef<'a> = &'a str;
|
||||
pub type EpochId = u32;
|
||||
|
||||
@@ -7,5 +7,9 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = { workspace = true }
|
||||
nym-contracts-common = { path = "../contracts-common", version = "0.5.0" }
|
||||
schemars = "0.8"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
|
||||
+20
-3
@@ -1,10 +1,12 @@
|
||||
use cosmwasm_std::{Addr, StdError};
|
||||
use cw_controllers::AdminError;
|
||||
use nym_service_provider_directory_common::{NymAddress, ServiceId};
|
||||
use nym_contracts_common::signing::verifier::ApiVerifierError;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{NymAddress, ServiceId};
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
pub enum SpContractError {
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
|
||||
@@ -46,6 +48,21 @@ pub enum ContractError {
|
||||
value: String,
|
||||
error_message: String,
|
||||
},
|
||||
|
||||
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
|
||||
MalformedEd25519IdentityKey(String),
|
||||
|
||||
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
|
||||
MalformedEd25519Signature(String),
|
||||
|
||||
#[error("Provided ed25519 signature did not verify correctly")]
|
||||
InvalidEd25519Signature,
|
||||
|
||||
#[error("failed to verify message signature: {source}")]
|
||||
SignatureVerificationFailure {
|
||||
#[from]
|
||||
source: ApiVerifierError,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) type Result<T, E = ContractError> = std::result::Result<T, E>;
|
||||
pub type Result<T, E = SpContractError> = std::result::Result<T, E>;
|
||||
@@ -39,16 +39,16 @@ pub fn new_announce_event(service_id: ServiceId, service: Service) -> Event {
|
||||
Event::new(ServiceProviderEventType::Announce)
|
||||
.add_attribute(ACTION, ServiceProviderEventType::Announce)
|
||||
.add_attribute(SERVICE_ID, service_id.to_string())
|
||||
.add_attribute(SERVICE_TYPE, service.service_type.to_string())
|
||||
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
|
||||
.add_attribute(SERVICE_TYPE, service.service.service_type.to_string())
|
||||
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
|
||||
.add_attribute(OWNER, service.announcer.to_string())
|
||||
}
|
||||
|
||||
pub fn new_delete_id_event(service_id: ServiceId, service: Service) -> Event {
|
||||
pub fn new_delete_id_event(service: Service) -> Event {
|
||||
Event::new(ServiceProviderEventType::DeleteId)
|
||||
.add_attribute(ACTION, ServiceProviderEventType::DeleteId)
|
||||
.add_attribute(SERVICE_ID, service_id.to_string())
|
||||
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
|
||||
.add_attribute(SERVICE_ID, service.service_id.to_string())
|
||||
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
|
||||
}
|
||||
|
||||
pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod msg;
|
||||
pub mod response;
|
||||
pub mod signing_types;
|
||||
pub mod types;
|
||||
|
||||
// Re-export all types at the top-level
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{NymAddress, ServiceId, ServiceType};
|
||||
use crate::{NymAddress, ServiceDetails, ServiceId};
|
||||
use cosmwasm_std::Coin;
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
@@ -22,8 +23,8 @@ pub struct MigrateMsg {}
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
Announce {
|
||||
nym_address: NymAddress,
|
||||
service_type: ServiceType,
|
||||
service: ServiceDetails,
|
||||
owner_signature: MessageSignature,
|
||||
},
|
||||
DeleteId {
|
||||
service_id: ServiceId,
|
||||
@@ -44,9 +45,12 @@ impl ExecuteMsg {
|
||||
pub fn default_memo(&self) -> String {
|
||||
match self {
|
||||
ExecuteMsg::Announce {
|
||||
nym_address,
|
||||
service_type,
|
||||
} => format!("announcing {nym_address} as type {service_type}"),
|
||||
service,
|
||||
owner_signature: _,
|
||||
} => format!(
|
||||
"announcing {} as type {}",
|
||||
service.nym_address, service.service_type
|
||||
),
|
||||
ExecuteMsg::DeleteId { service_id } => {
|
||||
format!("deleting service with service id {service_id}")
|
||||
}
|
||||
@@ -76,6 +80,9 @@ pub enum QueryMsg {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<ServiceId>,
|
||||
},
|
||||
SigningNonce {
|
||||
address: String,
|
||||
},
|
||||
Config {},
|
||||
GetContractVersion {},
|
||||
#[serde(rename = "get_cw2_contract_version")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
|
||||
use crate::{Service, ServiceId};
|
||||
use cosmwasm_std::Coin;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -13,22 +13,17 @@ pub struct ServiceInfoResponse {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ServicesListResponse {
|
||||
pub services: Vec<ServiceInfo>,
|
||||
pub services: Vec<Service>,
|
||||
}
|
||||
|
||||
impl ServicesListResponse {
|
||||
pub fn new(services: Vec<(ServiceId, Service)>) -> ServicesListResponse {
|
||||
ServicesListResponse {
|
||||
services: services
|
||||
.into_iter()
|
||||
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
|
||||
.collect(),
|
||||
}
|
||||
pub fn new(services: Vec<Service>) -> ServicesListResponse {
|
||||
ServicesListResponse { services }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[ServiceInfo]> for ServicesListResponse {
|
||||
fn from(services: &[ServiceInfo]) -> Self {
|
||||
impl From<&[Service]> for ServicesListResponse {
|
||||
fn from(services: &[Service]) -> Self {
|
||||
Self {
|
||||
services: services.to_vec(),
|
||||
}
|
||||
@@ -38,21 +33,17 @@ impl From<&[ServiceInfo]> for ServicesListResponse {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PagedServicesListResponse {
|
||||
pub services: Vec<ServiceInfo>,
|
||||
pub services: Vec<Service>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<ServiceId>,
|
||||
}
|
||||
|
||||
impl PagedServicesListResponse {
|
||||
pub fn new(
|
||||
services: Vec<(ServiceId, Service)>,
|
||||
services: Vec<Service>,
|
||||
per_page: usize,
|
||||
start_next_after: Option<ServiceId>,
|
||||
) -> PagedServicesListResponse {
|
||||
let services = services
|
||||
.into_iter()
|
||||
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
|
||||
.collect();
|
||||
PagedServicesListResponse {
|
||||
services,
|
||||
per_page,
|
||||
@@ -66,12 +57,3 @@ impl PagedServicesListResponse {
|
||||
pub struct ConfigResponse {
|
||||
pub deposit_required: Coin,
|
||||
}
|
||||
|
||||
impl From<Service> for ExecuteMsg {
|
||||
fn from(service: Service) -> Self {
|
||||
ExecuteMsg::Announce {
|
||||
nym_address: service.nym_address,
|
||||
service_type: service.service_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use nym_contracts_common::signing::{
|
||||
ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ServiceDetails;
|
||||
|
||||
pub type SignableServiceProviderAnnounceMsg =
|
||||
SignableMessage<ContractMessageContent<ServiceProviderAnnounce>>;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ServiceProviderAnnounce {
|
||||
service: ServiceDetails,
|
||||
}
|
||||
|
||||
impl SigningPurpose for ServiceProviderAnnounce {
|
||||
fn message_type() -> MessageType {
|
||||
MessageType::new("service-provider-announce")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct_service_provider_announce_sign_payload(
|
||||
nonce: Nonce,
|
||||
sender: Addr,
|
||||
deposit: Coin,
|
||||
service: ServiceDetails,
|
||||
) -> SignableServiceProviderAnnounceMsg {
|
||||
let payload = ServiceProviderAnnounce { service };
|
||||
let proxy = None;
|
||||
let content = ContractMessageContent::new(sender, proxy, vec![deposit], payload);
|
||||
SignableMessage::new(nonce, content)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -9,11 +10,11 @@ pub type ServiceId = u32;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
|
||||
pub struct Service {
|
||||
/// The address of the service.
|
||||
pub nym_address: NymAddress,
|
||||
/// The service type.
|
||||
pub service_type: ServiceType,
|
||||
/// Service owner.
|
||||
/// Unique id assigned to the anounced service.
|
||||
pub service_id: ServiceId,
|
||||
/// The announced service.
|
||||
pub service: ServiceDetails,
|
||||
/// Address of the service owner.
|
||||
pub announcer: Addr,
|
||||
/// Block height at which the service was added.
|
||||
pub block_height: u64,
|
||||
@@ -21,6 +22,16 @@ pub struct Service {
|
||||
pub deposit: Coin,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
|
||||
pub struct ServiceDetails {
|
||||
/// The address of the service.
|
||||
pub nym_address: NymAddress,
|
||||
/// The service type.
|
||||
pub service_type: ServiceType,
|
||||
/// The identity key of the service.
|
||||
pub identity_key: IdentityKey,
|
||||
}
|
||||
|
||||
/// The types of addresses supported.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -28,7 +39,7 @@ pub enum NymAddress {
|
||||
/// String representation of a nym address, which is of the form
|
||||
/// client_id.client_enc@gateway_id.
|
||||
Address(String),
|
||||
// For the future when we have a nym-dns contract
|
||||
// String name that can looked up in the nym-name-service contract (once it exists)
|
||||
//Name(String),
|
||||
}
|
||||
|
||||
@@ -41,6 +52,7 @@ impl NymAddress {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
NymAddress::Address(address) => address,
|
||||
//NymAddress::Name(name) => name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,19 +78,3 @@ impl std::fmt::Display for ServiceType {
|
||||
write!(f, "{service_type}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ServiceInfo {
|
||||
pub service_id: ServiceId,
|
||||
pub service: Service,
|
||||
}
|
||||
|
||||
impl ServiceInfo {
|
||||
pub fn new(service_id: ServiceId, service: Service) -> Self {
|
||||
Self {
|
||||
service_id,
|
||||
service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
description = "Common library for the Nym vesting contract"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -9,8 +9,8 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = { workspace = true }
|
||||
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
|
||||
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
ts-rs = {version = "6.1.2", optional = true}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-crypto"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
description = "Crypto library for the nym mixnet"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -28,7 +28,7 @@ zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
||||
|
||||
# internal
|
||||
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.2.0" }
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.2"
|
||||
|
||||
@@ -25,12 +25,12 @@ nym-sphinx-types = { path = "types" }
|
||||
|
||||
# those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved
|
||||
# to separate crate?
|
||||
nym-crypto = { path = "../crypto", version = "0.3.0" }
|
||||
nym-crypto = { path = "../crypto", version = "0.4.0" }
|
||||
nym-topology = { path = "../topology" }
|
||||
|
||||
[dev-dependencies]
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-crypto = { path = "../crypto", version = "0.3.0", features = ["asymmetric"] }
|
||||
nym-crypto = { path = "../crypto", version = "0.4.0", features = ["asymmetric"] }
|
||||
|
||||
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
|
||||
# net2 via tokio-util -> tokio -> mio -> net2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nym-pemstore"
|
||||
description = "Store private-public keypairs in PEM format"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
@@ -428,18 +428,14 @@ impl SocksClient {
|
||||
Some(self.lane_queue_lengths.clone()),
|
||||
self.shutdown_listener.clone(),
|
||||
)
|
||||
.run(move |conn_id, read_data, socket_closed| {
|
||||
let provider_request = Socks5Request::new_send(
|
||||
request_version.provider_protocol,
|
||||
conn_id,
|
||||
read_data,
|
||||
socket_closed,
|
||||
);
|
||||
.run(move |socket_data| {
|
||||
let lane = TransmissionLane::ConnectionId(socket_data.header.connection_id);
|
||||
let provider_request =
|
||||
Socks5Request::new_send(request_version.provider_protocol, socket_data);
|
||||
let provider_message = Socks5ProviderRequest::new_provider_data(
|
||||
request_version.provider_interface,
|
||||
provider_request,
|
||||
);
|
||||
let lane = TransmissionLane::ConnectionId(conn_id);
|
||||
if anonymous {
|
||||
InputMessage::new_anonymous(
|
||||
recipient,
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Socks5ClientCoreError;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
|
||||
use nym_client_core::client::received_buffer::ReconstructedMessagesReceiver;
|
||||
use nym_client_core::client::received_buffer::{
|
||||
ReceivedBufferMessage, ReceivedBufferRequestSender,
|
||||
};
|
||||
use nym_service_providers_common::interface::{ControlResponse, ResponseContent};
|
||||
use nym_socks5_proxy_helpers::connection_controller::ControllerSender;
|
||||
use nym_socks5_proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
|
||||
use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
|
||||
use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::TaskClient;
|
||||
|
||||
use crate::error::Socks5ClientCoreError;
|
||||
|
||||
pub(crate) struct MixnetResponseListener {
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
mix_response_receiver: ReconstructedMessagesReceiver,
|
||||
@@ -79,12 +80,20 @@ impl MixnetResponseListener {
|
||||
);
|
||||
Err(err_response.into())
|
||||
}
|
||||
Socks5ResponseContent::NetworkData(response) => {
|
||||
Socks5ResponseContent::NetworkData { content } => {
|
||||
self.controller_sender
|
||||
.unbounded_send(response.into())
|
||||
.unbounded_send(ControllerCommand::new_send(content))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Socks5ResponseContent::Query(response) => {
|
||||
error!("received a query response which we don't know how to handle yet!");
|
||||
error!("got: {:?}", response);
|
||||
|
||||
// I guess we'd need another channel here to forward those to where they need to go
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
use crate::message::OrderedMessage;
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use log::*;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum OrderedMessageError {
|
||||
#[error("received message with sequence number {received}, which is way higher than our current {current}")]
|
||||
MessageSequenceTooLarge { current: u64, received: u64 },
|
||||
|
||||
#[error("received message with sequence number {received}, while we're already at {current}!")]
|
||||
MessageAlreadyReconstructed { current: u64, received: u64 },
|
||||
|
||||
#[error("attempted to overwrite message at sequence {received}")]
|
||||
AttemptedToOverwriteSequence { received: u64 },
|
||||
}
|
||||
|
||||
/// Stores messages and emits them in order.
|
||||
///
|
||||
@@ -9,36 +24,58 @@ use std::collections::HashMap;
|
||||
/// to fill up with the full sequence.
|
||||
#[derive(Debug)]
|
||||
pub struct OrderedMessageBuffer {
|
||||
next_index: u64,
|
||||
messages: HashMap<u64, OrderedMessage>,
|
||||
next_sequence: u64,
|
||||
messages: BTreeMap<u64, Vec<u8>>,
|
||||
}
|
||||
|
||||
/// Data returned from `OrderedMessageBuffer` on a successful read of gapless ordered data.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ReadContiguousData {
|
||||
pub data: Vec<u8>,
|
||||
pub last_index: u64,
|
||||
pub last_sequence: u64,
|
||||
}
|
||||
|
||||
const MAX_REASONABLE_OFFSET: u64 = 1000;
|
||||
|
||||
impl OrderedMessageBuffer {
|
||||
pub fn new() -> OrderedMessageBuffer {
|
||||
OrderedMessageBuffer {
|
||||
next_index: 0,
|
||||
messages: HashMap::new(),
|
||||
next_sequence: 0,
|
||||
messages: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a message to the buffer. messages are sort on insertion, so
|
||||
/// that later on multiple reads for incomplete sequences don't result in
|
||||
/// useless sort work.
|
||||
pub fn write(&mut self, message: OrderedMessage) {
|
||||
pub fn write(&mut self, sequence: u64, data: Vec<u8>) -> Result<(), OrderedMessageError> {
|
||||
// reject messages that have clearly malformed sequence
|
||||
if sequence > self.next_sequence + MAX_REASONABLE_OFFSET {
|
||||
return Err(OrderedMessageError::MessageSequenceTooLarge {
|
||||
current: self.next_sequence,
|
||||
received: sequence,
|
||||
});
|
||||
}
|
||||
|
||||
if self.messages.contains_key(&sequence) {
|
||||
return Err(OrderedMessageError::AttemptedToOverwriteSequence { received: sequence });
|
||||
}
|
||||
|
||||
if sequence < self.next_sequence {
|
||||
return Err(OrderedMessageError::MessageAlreadyReconstructed {
|
||||
current: self.next_sequence,
|
||||
received: sequence,
|
||||
});
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Writing message index: {} length {:?} to OrderedMessageBuffer.",
|
||||
message.index,
|
||||
message.data.len()
|
||||
"Writing message index: {} length {} to OrderedMessageBuffer.",
|
||||
sequence,
|
||||
data.len()
|
||||
);
|
||||
|
||||
self.messages.insert(message.index, message);
|
||||
self.messages.insert(sequence, data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless
|
||||
@@ -49,33 +86,31 @@ impl OrderedMessageBuffer {
|
||||
/// a read will return the bytes of messages 0, 1, 2. Subsequent reads will
|
||||
/// return `None` until message 3 comes in, at which point 3, 4, and any
|
||||
/// further contiguous messages which have arrived will be returned.
|
||||
#[must_use]
|
||||
pub fn read(&mut self) -> Option<ReadContiguousData> {
|
||||
if !self.messages.contains_key(&self.next_index) {
|
||||
if !self.messages.contains_key(&self.next_sequence) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut contiguous_messages = Vec::new();
|
||||
let mut index = self.next_index;
|
||||
let mut seq = self.next_sequence;
|
||||
|
||||
while let Some(ordered_message) = self.messages.remove(&index) {
|
||||
contiguous_messages.push(ordered_message);
|
||||
index += 1;
|
||||
while let Some(mut data) = self.messages.remove(&seq) {
|
||||
contiguous_messages.append(&mut data);
|
||||
seq += 1;
|
||||
}
|
||||
|
||||
let high_water = index;
|
||||
self.next_index = high_water;
|
||||
trace!("Next high water mark is: {}", high_water);
|
||||
let high_water = seq;
|
||||
self.next_sequence = high_water;
|
||||
trace!("Next high water mark is: {high_water}");
|
||||
|
||||
// dig out the bytes from inside the struct
|
||||
let data: Vec<u8> = contiguous_messages
|
||||
.into_iter()
|
||||
.flat_map(|message| message.data)
|
||||
.collect();
|
||||
|
||||
trace!("Returning {} bytes from ordered message buffer", data.len());
|
||||
trace!(
|
||||
"Returning {} bytes from ordered message buffer",
|
||||
contiguous_messages.len()
|
||||
);
|
||||
Some(ReadContiguousData {
|
||||
data,
|
||||
last_index: index,
|
||||
data: contiguous_messages,
|
||||
last_sequence: seq,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -90,6 +125,64 @@ impl Default for OrderedMessageBuffer {
|
||||
mod test_chunking_and_reassembling {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn trying_to_write_unreasonable_high_sequence() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
let first_message = vec![1, 2, 3, 4];
|
||||
let second_message = vec![5, 6, 7, 8];
|
||||
|
||||
buffer.write(0, first_message).unwrap();
|
||||
buffer.write(1, second_message).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Err(OrderedMessageError::MessageSequenceTooLarge {
|
||||
current: 0,
|
||||
received: 12345678
|
||||
}),
|
||||
buffer.write(12345678, b"foomp".to_vec())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trying_to_overwrite_sequence() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
let message = vec![1, 2, 3, 4];
|
||||
|
||||
buffer.write(0, message.clone()).unwrap();
|
||||
buffer.write(1, message.clone()).unwrap();
|
||||
buffer.write(2, message.clone()).unwrap();
|
||||
buffer.write(3, message.clone()).unwrap();
|
||||
|
||||
for seq in 0..=3 {
|
||||
assert_eq!(
|
||||
Err(OrderedMessageError::AttemptedToOverwriteSequence { received: seq }),
|
||||
buffer.write(seq, message.clone())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writing_past_data() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
let message = vec![1, 2, 3, 4];
|
||||
|
||||
buffer.write(0, message.clone()).unwrap();
|
||||
buffer.write(1, message.clone()).unwrap();
|
||||
buffer.write(2, message.clone()).unwrap();
|
||||
buffer.write(3, message.clone()).unwrap();
|
||||
let _ = buffer.read().unwrap();
|
||||
|
||||
for seq in 0..=3 {
|
||||
assert_eq!(
|
||||
Err(OrderedMessageError::MessageAlreadyReconstructed {
|
||||
current: 4,
|
||||
received: seq
|
||||
}),
|
||||
buffer.write(seq, message.clone())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod reading_from_and_writing_to_the_buffer {
|
||||
use super::*;
|
||||
@@ -102,20 +195,14 @@ mod test_chunking_and_reassembling {
|
||||
fn read_returns_ordered_bytes_and_resets_buffer() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
|
||||
let first_message = OrderedMessage {
|
||||
data: vec![1, 2, 3, 4],
|
||||
index: 0,
|
||||
};
|
||||
let second_message = OrderedMessage {
|
||||
data: vec![5, 6, 7, 8],
|
||||
index: 1,
|
||||
};
|
||||
let first_message = vec![1, 2, 3, 4];
|
||||
let second_message = vec![5, 6, 7, 8];
|
||||
|
||||
buffer.write(first_message);
|
||||
buffer.write(0, first_message).unwrap();
|
||||
let first_read = buffer.read().unwrap().data;
|
||||
assert_eq!(vec![1, 2, 3, 4], first_read);
|
||||
|
||||
buffer.write(second_message);
|
||||
buffer.write(1, second_message).unwrap();
|
||||
let second_read = buffer.read().unwrap().data;
|
||||
assert_eq!(vec![5, 6, 7, 8], second_read);
|
||||
|
||||
@@ -126,17 +213,11 @@ mod test_chunking_and_reassembling {
|
||||
fn test_multiple_adds_stacks_up_bytes_in_the_buffer() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
|
||||
let first_message = OrderedMessage {
|
||||
data: vec![1, 2, 3, 4],
|
||||
index: 0,
|
||||
};
|
||||
let second_message = OrderedMessage {
|
||||
data: vec![5, 6, 7, 8],
|
||||
index: 1,
|
||||
};
|
||||
let first_message = vec![1, 2, 3, 4];
|
||||
let second_message = vec![5, 6, 7, 8];
|
||||
|
||||
buffer.write(first_message);
|
||||
buffer.write(second_message);
|
||||
buffer.write(0, first_message).unwrap();
|
||||
buffer.write(1, second_message).unwrap();
|
||||
let second_read = buffer.read();
|
||||
assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], second_read.unwrap().data);
|
||||
assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty
|
||||
@@ -146,17 +227,11 @@ mod test_chunking_and_reassembling {
|
||||
fn out_of_order_adds_results_in_ordered_byte_vector() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
|
||||
let first_message = OrderedMessage {
|
||||
data: vec![1, 2, 3, 4],
|
||||
index: 0,
|
||||
};
|
||||
let second_message = OrderedMessage {
|
||||
data: vec![5, 6, 7, 8],
|
||||
index: 1,
|
||||
};
|
||||
let first_message = vec![1, 2, 3, 4];
|
||||
let second_message = vec![5, 6, 7, 8];
|
||||
|
||||
buffer.write(second_message);
|
||||
buffer.write(first_message);
|
||||
buffer.write(1, second_message).unwrap();
|
||||
buffer.write(0, first_message).unwrap();
|
||||
let read = buffer.read().unwrap().data;
|
||||
assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], read);
|
||||
assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty
|
||||
@@ -170,23 +245,13 @@ mod test_chunking_and_reassembling {
|
||||
fn setup() -> OrderedMessageBuffer {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
|
||||
let zero_message = OrderedMessage {
|
||||
data: vec![0, 0, 0, 0],
|
||||
index: 0,
|
||||
};
|
||||
let one_message = OrderedMessage {
|
||||
data: vec![1, 1, 1, 1],
|
||||
index: 1,
|
||||
};
|
||||
let zero_message = vec![0, 0, 0, 0];
|
||||
let one_message = vec![1, 1, 1, 1];
|
||||
let three_message = vec![3, 3, 3, 3];
|
||||
|
||||
let three_message = OrderedMessage {
|
||||
data: vec![3, 3, 3, 3],
|
||||
index: 3,
|
||||
};
|
||||
|
||||
buffer.write(zero_message);
|
||||
buffer.write(one_message);
|
||||
buffer.write(three_message);
|
||||
buffer.write(0, zero_message).unwrap();
|
||||
buffer.write(1, one_message).unwrap();
|
||||
buffer.write(3, three_message).unwrap();
|
||||
buffer
|
||||
}
|
||||
#[test]
|
||||
@@ -199,43 +264,31 @@ mod test_chunking_and_reassembling {
|
||||
assert_eq!(None, buffer.read());
|
||||
|
||||
// let's add another message, leaving a gap in place at index 2
|
||||
let five_message = OrderedMessage {
|
||||
data: vec![5, 5, 5, 5],
|
||||
index: 5,
|
||||
};
|
||||
buffer.write(five_message);
|
||||
let five_message = vec![5, 5, 5, 5];
|
||||
buffer.write(5, five_message).unwrap();
|
||||
assert_eq!(None, buffer.read());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filling_the_gap_allows_us_to_get_everything() {
|
||||
let mut buffer = setup();
|
||||
buffer.read(); // that burns the first two. We still have a gap before the 3s.
|
||||
let _ = buffer.read(); // that burns the first two. We still have a gap before the 3s.
|
||||
|
||||
let two_message = OrderedMessage {
|
||||
data: vec![2, 2, 2, 2],
|
||||
index: 2,
|
||||
};
|
||||
buffer.write(two_message);
|
||||
let two_message = vec![2, 2, 2, 2];
|
||||
buffer.write(2, two_message).unwrap();
|
||||
|
||||
let more_ordered_bytes = buffer.read().unwrap().data;
|
||||
assert_eq!([2, 2, 2, 2, 3, 3, 3, 3].to_vec(), more_ordered_bytes);
|
||||
|
||||
// let's add another message
|
||||
let five_message = OrderedMessage {
|
||||
data: vec![5, 5, 5, 5],
|
||||
index: 5,
|
||||
};
|
||||
buffer.write(five_message);
|
||||
let five_message = vec![5, 5, 5, 5];
|
||||
buffer.write(5, five_message).unwrap();
|
||||
|
||||
assert_eq!(None, buffer.read());
|
||||
|
||||
// let's fill in the gap of 4s now and read again
|
||||
let four_message = OrderedMessage {
|
||||
data: vec![4, 4, 4, 4],
|
||||
index: 4,
|
||||
};
|
||||
buffer.write(four_message);
|
||||
let four_message = vec![4, 4, 4, 4];
|
||||
buffer.write(4, four_message).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
[4, 4, 4, 4, 5, 5, 5, 5].to_vec(),
|
||||
@@ -249,70 +302,47 @@ mod test_chunking_and_reassembling {
|
||||
#[test]
|
||||
fn filling_the_gap_allows_us_to_get_everything_when_last_element_is_empty() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
let zero_message = OrderedMessage {
|
||||
data: vec![0, 0, 0, 0],
|
||||
index: 0,
|
||||
};
|
||||
let one_message = OrderedMessage {
|
||||
data: vec![2, 2, 2, 2],
|
||||
index: 1,
|
||||
};
|
||||
let two_message = OrderedMessage {
|
||||
data: vec![],
|
||||
index: 2,
|
||||
};
|
||||
let zero_message = vec![0, 0, 0, 0];
|
||||
let one_message = vec![2, 2, 2, 2];
|
||||
let two_message = vec![];
|
||||
|
||||
buffer.write(zero_message);
|
||||
buffer.write(0, zero_message).unwrap();
|
||||
assert!(buffer.read().is_some()); // burn the buffer
|
||||
|
||||
buffer.write(two_message);
|
||||
buffer.write(one_message);
|
||||
buffer.write(2, two_message).unwrap();
|
||||
buffer.write(1, one_message).unwrap();
|
||||
assert!(buffer.read().is_some());
|
||||
assert_eq!(buffer.next_index, 3);
|
||||
assert_eq!(buffer.next_sequence, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_gaps_bigger_than_one() {
|
||||
let mut buffer = OrderedMessageBuffer::new();
|
||||
let zero_message = OrderedMessage {
|
||||
data: vec![0, 0, 0, 0],
|
||||
index: 0,
|
||||
};
|
||||
let one_message = OrderedMessage {
|
||||
data: vec![2, 2, 2, 2],
|
||||
index: 1,
|
||||
};
|
||||
let two_message = OrderedMessage {
|
||||
data: vec![2, 2, 2, 2],
|
||||
index: 2,
|
||||
};
|
||||
let three_message = OrderedMessage {
|
||||
data: vec![2, 2, 2, 2],
|
||||
index: 3,
|
||||
};
|
||||
let four_message = OrderedMessage {
|
||||
data: vec![2, 2, 2, 2],
|
||||
index: 4,
|
||||
};
|
||||
buffer.write(zero_message);
|
||||
let zero_message = vec![0, 0, 0, 0];
|
||||
let one_message = vec![2, 2, 2, 2];
|
||||
let two_message = vec![2, 2, 2, 2];
|
||||
let three_message = vec![2, 2, 2, 2];
|
||||
let four_message = vec![2, 2, 2, 2];
|
||||
|
||||
buffer.write(0, zero_message).unwrap();
|
||||
assert!(buffer.read().is_some());
|
||||
assert_eq!(buffer.next_index, 1);
|
||||
assert_eq!(buffer.next_sequence, 1);
|
||||
|
||||
buffer.write(four_message);
|
||||
buffer.write(4, four_message).unwrap();
|
||||
assert!(buffer.read().is_none());
|
||||
assert_eq!(buffer.next_index, 1);
|
||||
assert_eq!(buffer.next_sequence, 1);
|
||||
|
||||
buffer.write(three_message);
|
||||
buffer.write(3, three_message).unwrap();
|
||||
assert!(buffer.read().is_none());
|
||||
assert_eq!(buffer.next_index, 1);
|
||||
assert_eq!(buffer.next_sequence, 1);
|
||||
|
||||
buffer.write(two_message);
|
||||
buffer.write(2, two_message).unwrap();
|
||||
assert!(buffer.read().is_none());
|
||||
assert_eq!(buffer.next_index, 1);
|
||||
assert_eq!(buffer.next_sequence, 1);
|
||||
|
||||
buffer.write(one_message);
|
||||
buffer.write(1, one_message).unwrap();
|
||||
assert!(buffer.read().is_some());
|
||||
assert_eq!(buffer.next_index, 5)
|
||||
assert_eq!(buffer.next_sequence, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
mod buffer;
|
||||
mod message;
|
||||
mod sender;
|
||||
|
||||
pub use buffer::{OrderedMessageBuffer, ReadContiguousData};
|
||||
pub use message::MessageError;
|
||||
pub use message::OrderedMessage;
|
||||
pub use sender::OrderedMessageSender;
|
||||
pub use buffer::{OrderedMessageBuffer, OrderedMessageError, ReadContiguousData};
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum MessageError {
|
||||
#[error("the received message was empty")]
|
||||
NoData,
|
||||
|
||||
#[error("could not extract message index. Received {received} bytes, but expected {expected}")]
|
||||
IndexTooShort { received: usize, expected: usize },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct OrderedMessage {
|
||||
pub data: Vec<u8>,
|
||||
pub index: u64,
|
||||
}
|
||||
|
||||
impl OrderedMessage {
|
||||
/// Serializes an `OrderedMessage` into bytes.
|
||||
/// The output format is:
|
||||
/// | 8 bytes index | data... |
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.index
|
||||
.to_be_bytes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.data.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Attempts to deserialize an `OrderedMessage` from bytes.
|
||||
pub fn try_from_bytes(data: Vec<u8>) -> Result<OrderedMessage, MessageError> {
|
||||
if data.is_empty() {
|
||||
return Err(MessageError::NoData);
|
||||
}
|
||||
|
||||
if data.len() < 8 {
|
||||
return Err(MessageError::IndexTooShort {
|
||||
received: data.len(),
|
||||
expected: 8,
|
||||
});
|
||||
}
|
||||
let index = u64::from_be_bytes([
|
||||
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]);
|
||||
Ok(OrderedMessage {
|
||||
data: data[8..].to_vec(),
|
||||
index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Order messages by their index only, ignoring their data
|
||||
impl PartialOrd for OrderedMessage {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some((self.index).cmp(&(other.index)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod ordered_message_to_bytes {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn works() {
|
||||
let message = OrderedMessage {
|
||||
data: vec![123],
|
||||
index: 1,
|
||||
};
|
||||
let bytes = message.into_bytes();
|
||||
|
||||
let expected = vec![0, 0, 0, 0, 0, 0, 0, 1, 123];
|
||||
assert_eq!(expected, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod ordered_message_from_bytes {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_when_there_is_no_data() {
|
||||
let result = OrderedMessage::try_from_bytes(Vec::new());
|
||||
assert_eq!(Err(MessageError::NoData), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_when_data_is_too_short() {
|
||||
let result = OrderedMessage::try_from_bytes(vec![1, 2, 3]);
|
||||
assert_eq!(
|
||||
Err(MessageError::IndexTooShort {
|
||||
received: 3,
|
||||
expected: 8
|
||||
}),
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_there_is_enough_to_make_a_sequence_number_but_no_message_data() {
|
||||
let expected = OrderedMessage {
|
||||
data: Vec::new(),
|
||||
index: 1,
|
||||
};
|
||||
let result = OrderedMessage::try_from_bytes(vec![0, 0, 0, 0, 0, 0, 0, 1]).unwrap();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_there_is_seq_number_and_data() {
|
||||
let expected = OrderedMessage {
|
||||
data: vec![255, 255, 255],
|
||||
index: 1,
|
||||
};
|
||||
let result =
|
||||
OrderedMessage::try_from_bytes(vec![0, 0, 0, 0, 0, 0, 0, 1, 255, 255, 255]).unwrap();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_message_does_not_affect_ordering() {
|
||||
let mut msg1 = OrderedMessage {
|
||||
data: vec![255, 255, 255],
|
||||
index: 1,
|
||||
};
|
||||
|
||||
let mut msg2 = OrderedMessage {
|
||||
data: vec![],
|
||||
index: 2,
|
||||
};
|
||||
|
||||
assert!(msg1 < msg2);
|
||||
|
||||
msg1.index = 2;
|
||||
msg2.index = 1;
|
||||
|
||||
assert!(msg1 > msg2);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use crate::message::OrderedMessage;
|
||||
|
||||
/// Assigns sequence numbers to outbound byte vectors. These messages can then
|
||||
/// be reassembled into an ordered sequence by the `OrderedMessageSender`.
|
||||
#[derive(Debug)]
|
||||
pub struct OrderedMessageSender {
|
||||
next_index: u64,
|
||||
}
|
||||
|
||||
impl OrderedMessageSender {
|
||||
pub fn new() -> OrderedMessageSender {
|
||||
OrderedMessageSender { next_index: 0 }
|
||||
}
|
||||
|
||||
/// Turns raw bytes into an OrderedMessage containing the original bytes
|
||||
/// and a sequence number;
|
||||
pub fn wrap_message(&mut self, input: Vec<u8>) -> OrderedMessage {
|
||||
let message = OrderedMessage {
|
||||
data: input.to_vec(),
|
||||
index: self.next_index,
|
||||
};
|
||||
self.next_index += 1;
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OrderedMessageSender {
|
||||
fn default() -> Self {
|
||||
OrderedMessageSender::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod ordered_message_sender {
|
||||
use super::*;
|
||||
|
||||
mod when_input_bytes_are_empty {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sequence_index_numbers {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn increase_as_messages_are_sent() {
|
||||
let mut sender = OrderedMessageSender::new();
|
||||
let first_bytes = vec![1, 2, 3, 4];
|
||||
let second_bytes = vec![5, 6, 7, 8];
|
||||
|
||||
let first_message = sender.wrap_message(first_bytes);
|
||||
|
||||
assert_eq!(first_message.index, 0);
|
||||
|
||||
let second_message = sender.wrap_message(second_bytes);
|
||||
assert_eq!(second_message.index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData};
|
||||
use nym_socks5_requests::{ConnectionId, NetworkData, SendRequest};
|
||||
use nym_ordered_buffer::{OrderedMessageBuffer, ReadContiguousData};
|
||||
use nym_socks5_requests::{ConnectionId, SocketData};
|
||||
use nym_task::connections::{ConnectionCommand, ConnectionCommandSender};
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -40,29 +40,13 @@ pub enum ControllerCommand {
|
||||
connection_id: ConnectionId,
|
||||
},
|
||||
Send {
|
||||
connection_id: ConnectionId,
|
||||
data: Vec<u8>,
|
||||
is_closed: bool,
|
||||
data: SocketData,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<NetworkData> for ControllerCommand {
|
||||
fn from(value: NetworkData) -> Self {
|
||||
ControllerCommand::Send {
|
||||
connection_id: value.connection_id,
|
||||
data: value.data,
|
||||
is_closed: value.is_closed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SendRequest> for ControllerCommand {
|
||||
fn from(value: SendRequest) -> Self {
|
||||
ControllerCommand::Send {
|
||||
connection_id: value.conn_id,
|
||||
data: value.data,
|
||||
is_closed: value.local_closed,
|
||||
}
|
||||
impl ControllerCommand {
|
||||
pub fn new_send(data: SocketData) -> Self {
|
||||
ControllerCommand::Send { data }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,18 +58,13 @@ struct ActiveConnection {
|
||||
}
|
||||
|
||||
impl ActiveConnection {
|
||||
fn write_to_buf(&mut self, payload: Vec<u8>, is_closed: bool) {
|
||||
let ordered_message = match OrderedMessage::try_from_bytes(payload) {
|
||||
Ok(msg) => msg,
|
||||
Err(err) => {
|
||||
error!("Malformed ordered message - {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
fn write_to_buf(&mut self, seq: u64, payload: Vec<u8>, is_closed: bool) {
|
||||
if is_closed {
|
||||
self.closed_at_index = Some(ordered_message.index);
|
||||
self.closed_at_index = Some(seq);
|
||||
}
|
||||
if let Err(err) = self.ordered_buffer.write(seq, payload) {
|
||||
error!("failed to write to the buffer: {err}")
|
||||
}
|
||||
self.ordered_buffer.write(ordered_message);
|
||||
}
|
||||
|
||||
fn read_from_buf(&mut self) -> Option<ReadContiguousData> {
|
||||
@@ -117,7 +96,7 @@ pub struct Controller {
|
||||
|
||||
// buffer for messages received before connection was established due to mixnet being able to
|
||||
// un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here
|
||||
pending_messages: HashMap<ConnectionId, Vec<(Vec<u8>, bool)>>,
|
||||
pending_messages: HashMap<ConnectionId, Vec<SocketData>>,
|
||||
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
@@ -154,8 +133,8 @@ impl Controller {
|
||||
// check if there were any pending messages
|
||||
if let Some(pending) = self.pending_messages.remove(&conn_id) {
|
||||
debug!("There were some pending messages for {}", conn_id);
|
||||
for (payload, is_closed) in pending {
|
||||
self.send_to_connection(conn_id, payload, is_closed)
|
||||
for data in pending {
|
||||
self.send_to_connection(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,20 +163,25 @@ impl Controller {
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec<u8>, is_closed: bool) {
|
||||
if let Some(active_connection) = self.active_connections.get_mut(&conn_id) {
|
||||
if !payload.is_empty() {
|
||||
active_connection.write_to_buf(payload, is_closed);
|
||||
} else if !is_closed {
|
||||
error!("Tried to write an empty message to a not-closing connection. Please let us know if you see this message");
|
||||
}
|
||||
fn send_to_connection(&mut self, message: SocketData) {
|
||||
let hdr = message.header;
|
||||
if let Some(active_connection) = self.active_connections.get_mut(&hdr.connection_id) {
|
||||
// always write to the buffer even if payload is empty (because it could have been the keep-alive message)
|
||||
active_connection.write_to_buf(hdr.seq, message.data, hdr.local_socket_closed);
|
||||
|
||||
if let Some(payload) = active_connection.read_from_buf() {
|
||||
if let Some(closed_at_index) = active_connection.closed_at_index {
|
||||
if payload.last_index > closed_at_index {
|
||||
if payload.last_sequence > closed_at_index {
|
||||
active_connection.is_closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// however, don't send empty payload to the actual connection if it's not a close message
|
||||
// TODO: or should we?
|
||||
if payload.data.is_empty() && !active_connection.is_closed {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = active_connection
|
||||
.connection_sender
|
||||
.as_mut()
|
||||
@@ -207,34 +191,26 @@ impl Controller {
|
||||
socket_closed: active_connection.is_closed,
|
||||
})
|
||||
{
|
||||
error!("WTF IS THIS: {err}");
|
||||
error!("failed to send on the active connection channel: {err}");
|
||||
}
|
||||
|
||||
// TODO: ABOVE UNWRAP CAUSED A CRASH IN A NORMAL USE!!!!
|
||||
// TODO:
|
||||
// TODO: surprisingly it only happened on socks client, never on nSP
|
||||
// TODO:
|
||||
// TODO:
|
||||
// TODO:
|
||||
// TODO:
|
||||
}
|
||||
} else if !self.recently_closed.contains(&conn_id) {
|
||||
} else if !self.recently_closed.contains(&hdr.connection_id) {
|
||||
debug!("Received a 'Send' before 'Connect' - going to buffer the data");
|
||||
let pending = self
|
||||
.pending_messages
|
||||
.entry(conn_id)
|
||||
.entry(hdr.connection_id)
|
||||
.or_insert_with(Vec::new);
|
||||
pending.push((payload, is_closed));
|
||||
} else if !is_closed {
|
||||
pending.push(message);
|
||||
} else if !hdr.local_socket_closed {
|
||||
error!(
|
||||
"Tried to write to closed connection {} ({} bytes were 'lost)",
|
||||
conn_id,
|
||||
payload.len()
|
||||
hdr.connection_id,
|
||||
message.data.len()
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Tried to write to closed connection {}, but remote is already closed",
|
||||
conn_id
|
||||
hdr.connection_id
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -243,8 +219,8 @@ impl Controller {
|
||||
loop {
|
||||
tokio::select! {
|
||||
command = self.receiver.next() => match command {
|
||||
Some(ControllerCommand::Send{connection_id, data, is_closed}) => {
|
||||
self.send_to_connection(connection_id, data, is_closed)
|
||||
Some(ControllerCommand::Send{data}) => {
|
||||
self.send_to_connection(data)
|
||||
}
|
||||
Some(ControllerCommand::Insert{connection_id, connection_sender}) => {
|
||||
self.insert_connection(connection_id, connection_sender)
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
|
||||
pub mod available_reader;
|
||||
pub mod connection_controller;
|
||||
pub mod ordered_sender;
|
||||
pub mod proxy_runner;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::proxy_runner::MixProxySender;
|
||||
use bytes::Bytes;
|
||||
use log::{debug, error};
|
||||
use nym_socks5_requests::{ConnectionId, SocketData};
|
||||
use std::io;
|
||||
|
||||
pub(crate) struct OrderedMessageSender<F, S> {
|
||||
connection_id: ConnectionId,
|
||||
// addresses are provided for better logging
|
||||
local_destination_address: String,
|
||||
remote_source_address: String,
|
||||
mixnet_sender: MixProxySender<S>,
|
||||
|
||||
next_message_seq: u64,
|
||||
mix_message_adapter: F,
|
||||
}
|
||||
|
||||
impl<F, S> OrderedMessageSender<F, S>
|
||||
where
|
||||
F: Fn(SocketData) -> S,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
local_destination_address: String,
|
||||
remote_source_address: String,
|
||||
connection_id: ConnectionId,
|
||||
mixnet_sender: MixProxySender<S>,
|
||||
mix_message_adapter: F,
|
||||
) -> Self {
|
||||
OrderedMessageSender {
|
||||
local_destination_address,
|
||||
remote_source_address,
|
||||
connection_id,
|
||||
mixnet_sender,
|
||||
next_message_seq: 0,
|
||||
mix_message_adapter,
|
||||
}
|
||||
}
|
||||
|
||||
fn sequence(&mut self) -> u64 {
|
||||
let next = self.next_message_seq;
|
||||
self.next_message_seq += 1;
|
||||
next
|
||||
}
|
||||
|
||||
fn construct_message(&mut self, data: Vec<u8>, local_socket_closed: bool) -> S {
|
||||
let data = SocketData::new(
|
||||
self.sequence(),
|
||||
self.connection_id,
|
||||
local_socket_closed,
|
||||
data,
|
||||
);
|
||||
(self.mix_message_adapter)(data)
|
||||
}
|
||||
|
||||
async fn send_message(&self, message: S) {
|
||||
if self.mixnet_sender.send(message).await.is_err() {
|
||||
panic!("BatchRealMessageReceiver has stopped receiving!")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_empty_close(&mut self) {
|
||||
let message = self.construct_message(Vec::new(), true);
|
||||
self.send_message(message).await
|
||||
}
|
||||
|
||||
pub(crate) async fn send_empty_keepalive(&mut self) {
|
||||
log::trace!("Sending keepalive for connection: {}", self.connection_id);
|
||||
let message = self.construct_message(Vec::new(), false);
|
||||
self.send_message(message).await
|
||||
}
|
||||
|
||||
pub(crate) fn process_data(&self, read_data: Option<io::Result<Bytes>>) -> ProcessedData {
|
||||
let (read_data, is_finished) = match read_data {
|
||||
Some(data) => match data {
|
||||
Ok(data) => (data, false),
|
||||
Err(err) => {
|
||||
error!(target: &*format!("({}) socks5 inbound", self.connection_id), "failed to read request from the socket - {err}");
|
||||
(Default::default(), true)
|
||||
}
|
||||
},
|
||||
None => (Default::default(), true),
|
||||
};
|
||||
|
||||
ProcessedData {
|
||||
data: read_data,
|
||||
is_done: is_finished,
|
||||
}
|
||||
}
|
||||
|
||||
fn log_sent_message(&self, data: &ProcessedData) {
|
||||
debug!(
|
||||
target: &*format!("({}) socks5 inbound", self.connection_id),
|
||||
"[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}",
|
||||
data.data.len(),
|
||||
self.local_destination_address,
|
||||
self.remote_source_address,
|
||||
data.is_done
|
||||
);
|
||||
}
|
||||
|
||||
/// Send data read from local socket into the mixnet
|
||||
pub(crate) async fn send_data(&mut self, data: ProcessedData) {
|
||||
self.log_sent_message(&data);
|
||||
let message = self.construct_message(data.data.into(), data.is_done);
|
||||
self.send_message(message).await;
|
||||
}
|
||||
}
|
||||
|
||||
// helper wrapper to keep track of field meanings
|
||||
pub(crate) struct ProcessedData {
|
||||
data: Bytes,
|
||||
pub(crate) is_done: bool,
|
||||
}
|
||||
@@ -1,106 +1,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::MixProxySender;
|
||||
use super::SHUTDOWN_TIMEOUT;
|
||||
use crate::available_reader::AvailableReader;
|
||||
use crate::ordered_sender::OrderedMessageSender;
|
||||
use crate::proxy_runner::KEEPALIVE_INTERVAL;
|
||||
use bytes::Bytes;
|
||||
use futures::FutureExt;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_ordered_buffer::OrderedMessageSender;
|
||||
use nym_socks5_requests::ConnectionId;
|
||||
use nym_socks5_requests::{ConnectionId, SocketData};
|
||||
use nym_task::connections::LaneQueueLengths;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskClient;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{io, sync::Arc};
|
||||
use tokio::select;
|
||||
use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep};
|
||||
|
||||
async fn send_empty_close<F, S>(
|
||||
connection_id: ConnectionId,
|
||||
message_sender: &mut OrderedMessageSender,
|
||||
mix_sender: &MixProxySender<S>,
|
||||
adapter_fn: F,
|
||||
) where
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
|
||||
S: Debug,
|
||||
{
|
||||
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
|
||||
mix_sender
|
||||
.send(adapter_fn(connection_id, ordered_msg, true))
|
||||
.await
|
||||
.expect("BatchRealMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
async fn send_empty_keepalive<F, S>(
|
||||
connection_id: ConnectionId,
|
||||
message_sender: &mut OrderedMessageSender,
|
||||
mix_sender: &MixProxySender<S>,
|
||||
adapter_fn: F,
|
||||
) where
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
|
||||
S: Debug,
|
||||
{
|
||||
log::trace!("Sending keepalive for connection: {connection_id}");
|
||||
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
|
||||
mix_sender
|
||||
.send(adapter_fn(connection_id, ordered_msg, false))
|
||||
.await
|
||||
.expect("BatchRealMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
async fn deal_with_data<F, S>(
|
||||
read_data: Option<io::Result<Bytes>>,
|
||||
local_destination_address: &str,
|
||||
remote_source_address: &str,
|
||||
connection_id: ConnectionId,
|
||||
message_sender: &mut OrderedMessageSender,
|
||||
mix_sender: &MixProxySender<S>,
|
||||
adapter_fn: F,
|
||||
) -> bool
|
||||
where
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
|
||||
S: Debug,
|
||||
{
|
||||
let (read_data, is_finished) = match read_data {
|
||||
Some(data) => match data {
|
||||
Ok(data) => (data, false),
|
||||
Err(err) => {
|
||||
error!(target: &*format!("({connection_id}) socks5 inbound"), "failed to read request from the socket - {err}");
|
||||
(Default::default(), true)
|
||||
}
|
||||
},
|
||||
None => (Default::default(), true),
|
||||
};
|
||||
|
||||
debug!(
|
||||
target: &*format!("({connection_id}) socks5 inbound"),
|
||||
"[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}",
|
||||
read_data.len(),
|
||||
local_destination_address,
|
||||
remote_source_address,
|
||||
is_finished
|
||||
);
|
||||
|
||||
// if we're sending through the mixnet increase the sequence number...
|
||||
let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes();
|
||||
log::trace!(
|
||||
"pushing data down the input sender: size: {}",
|
||||
ordered_msg.len()
|
||||
);
|
||||
|
||||
mix_sender
|
||||
.send(adapter_fn(connection_id, ordered_msg, is_finished))
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
is_finished
|
||||
}
|
||||
|
||||
async fn wait_until_lane_empty(lane_queue_lengths: &Option<LaneQueueLengths>, connection_id: u64) {
|
||||
if let Some(lane_queue_lengths) = lane_queue_lengths {
|
||||
if tokio::time::timeout(
|
||||
@@ -158,27 +74,21 @@ async fn wait_for_lane(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) async fn run_inbound<F, S>(
|
||||
mut reader: OwnedReadHalf,
|
||||
local_destination_address: String, // addresses are provided for better logging
|
||||
remote_source_address: String,
|
||||
mut message_sender: OrderedMessageSender<F, S>,
|
||||
connection_id: ConnectionId,
|
||||
mix_sender: MixProxySender<S>,
|
||||
available_plaintext_per_mix_packet: usize,
|
||||
adapter_fn: F,
|
||||
shutdown_notify: Arc<Notify>,
|
||||
lane_queue_lengths: Option<LaneQueueLengths>,
|
||||
mut shutdown_listener: TaskClient,
|
||||
) -> OwnedReadHalf
|
||||
where
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static,
|
||||
S: Debug,
|
||||
F: Fn(SocketData) -> S + Send + 'static,
|
||||
{
|
||||
// TODO: this multiplication by 4 is completely arbitrary here
|
||||
let mut available_reader =
|
||||
AvailableReader::new(&mut reader, Some(available_plaintext_per_mix_packet * 4));
|
||||
let mut message_sender = OrderedMessageSender::new();
|
||||
|
||||
// Shutdown if outbound signled to shutdown
|
||||
let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT));
|
||||
@@ -217,7 +127,7 @@ where
|
||||
);
|
||||
// inform remote just in case it was closed because of lack of heartbeat.
|
||||
// worst case the remote will just have couple of false negatives
|
||||
send_empty_close(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await;
|
||||
message_sender.send_empty_close().await;
|
||||
break;
|
||||
}
|
||||
_ = shutdown_listener.recv() => {
|
||||
@@ -233,7 +143,7 @@ where
|
||||
break;
|
||||
}
|
||||
_ = keepalive_timer.tick() => {
|
||||
send_empty_keepalive(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await;
|
||||
message_sender.send_empty_keepalive().await;
|
||||
}
|
||||
// Read the next data when there is space in the lane.
|
||||
// The purpose of chaining the wait here is that it makes sure we can cancel the
|
||||
@@ -241,15 +151,12 @@ where
|
||||
read_data = wait_until_lane_almost_empty(&lane_queue_lengths, connection_id)
|
||||
.then(|_| available_reader.next()), if !we_are_closed =>
|
||||
{
|
||||
if deal_with_data(
|
||||
read_data,
|
||||
&local_destination_address,
|
||||
&remote_source_address,
|
||||
connection_id,
|
||||
&mut message_sender,
|
||||
&mix_sender,
|
||||
&adapter_fn,
|
||||
).await {
|
||||
let processed = message_sender.process_data(read_data);
|
||||
let is_done = processed.is_done;
|
||||
|
||||
message_sender.send_data(processed).await;
|
||||
|
||||
if is_done {
|
||||
// After reading the last data, notify the closing_future to wait until the
|
||||
// lane is clear before exiting.
|
||||
// We don't wait here since we want to be able to cancel the wait on close or
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::connection_controller::ConnectionReceiver;
|
||||
use nym_socks5_requests::ConnectionId;
|
||||
use crate::ordered_sender::OrderedMessageSender;
|
||||
use nym_socks5_requests::{ConnectionId, SocketData};
|
||||
use nym_task::connections::LaneQueueLengths;
|
||||
use nym_task::TaskClient;
|
||||
use std::fmt::Debug;
|
||||
@@ -13,7 +14,7 @@ mod inbound;
|
||||
mod outbound;
|
||||
|
||||
// TODO: make this configurable
|
||||
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
|
||||
// Send empty keepalive messages regurarly to keep the connection alive. This should be smaller
|
||||
// than [`MIX_TTL`].
|
||||
@@ -92,20 +93,24 @@ where
|
||||
// request/response as required by entity running particular side of the proxy.
|
||||
pub async fn run<F>(mut self, adapter_fn: F) -> Self
|
||||
where
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + Sync + 'static,
|
||||
F: Fn(SocketData) -> S + Send + Sync + 'static,
|
||||
{
|
||||
let (read_half, write_half) = self.socket.take().unwrap().into_split();
|
||||
let shutdown_notify = Arc::new(Notify::new());
|
||||
|
||||
// should run until either inbound closes or is notified from outbound
|
||||
let inbound_future = inbound::run_inbound(
|
||||
read_half,
|
||||
let ordered_sender = OrderedMessageSender::new(
|
||||
self.local_destination_address.clone(),
|
||||
self.remote_source_address.clone(),
|
||||
self.connection_id,
|
||||
self.mix_sender.clone(),
|
||||
self.available_plaintext_per_mix_packet,
|
||||
adapter_fn,
|
||||
);
|
||||
let inbound_future = inbound::run_inbound(
|
||||
read_half,
|
||||
ordered_sender,
|
||||
self.connection_id,
|
||||
self.available_plaintext_per_mix_packet,
|
||||
Arc::clone(&shutdown_notify),
|
||||
self.lane_queue_lengths.clone(),
|
||||
self.shutdown_listener.clone(),
|
||||
|
||||
@@ -7,9 +7,11 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
bincode = "1.3.3"
|
||||
log = { workspace = true }
|
||||
nym-service-providers-common = { path = "../../../service-providers/common" }
|
||||
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
|
||||
nym-service-providers-common = { path = "../../../service-providers/common" }
|
||||
tap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use nym_service_providers_common::interface;
|
||||
use nym_service_providers_common::interface::ServiceProviderMessagingError;
|
||||
use std::mem;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use request::*;
|
||||
@@ -16,6 +17,185 @@ pub mod version;
|
||||
pub type Socks5ProviderRequest = interface::Request<Socks5Request>;
|
||||
pub type Socks5ProviderResponse = interface::Response<Socks5Request>;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
#[error(
|
||||
"didn't receive enough data to recover socket data. got {received}, but expected at least {expected}"
|
||||
)]
|
||||
pub struct InsufficientSocketDataError {
|
||||
received: usize,
|
||||
expected: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct SocketDataHeader {
|
||||
pub seq: u64,
|
||||
pub connection_id: ConnectionId,
|
||||
pub local_socket_closed: bool,
|
||||
}
|
||||
|
||||
impl SocketDataHeader {
|
||||
const SERIALIZED_LEN: usize = mem::size_of::<ConnectionId>() + 1 + mem::size_of::<u64>();
|
||||
|
||||
// we need to have two serialization methods for backwards compatibility,
|
||||
// since we serialized those fields differently depending on whether it was ingress vs egress...
|
||||
|
||||
pub fn try_from_request_bytes(
|
||||
b: &[u8],
|
||||
) -> Result<SocketDataHeader, InsufficientSocketDataError> {
|
||||
if b.len() != Self::SERIALIZED_LEN {
|
||||
return Err(InsufficientSocketDataError {
|
||||
received: b.len(),
|
||||
expected: Self::SERIALIZED_LEN,
|
||||
});
|
||||
}
|
||||
|
||||
// the unwraps here are fine as we just ensured we have the exact amount of bytes we need
|
||||
let connection_id = ConnectionId::from_be_bytes(b[0..8].try_into().unwrap());
|
||||
let local_socket_closed = b[8] != 0;
|
||||
let seq = u64::from_be_bytes(b[9..].try_into().unwrap());
|
||||
|
||||
Ok(SocketDataHeader {
|
||||
seq,
|
||||
connection_id,
|
||||
local_socket_closed,
|
||||
})
|
||||
}
|
||||
|
||||
// the serialization of the header looks as follows:
|
||||
// (it's vital it's not modified as we need this exact structure for backwards compatibility)
|
||||
// CONNECTION_ID (8B) || SOCKET_CLOSED (1B) || SEQUENCE (8B)
|
||||
pub fn into_request_bytes(self) -> Vec<u8> {
|
||||
self.into_request_bytes_iter().collect()
|
||||
}
|
||||
|
||||
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
|
||||
self.connection_id
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(std::iter::once(self.local_socket_closed as u8))
|
||||
.chain(self.seq.to_be_bytes().into_iter())
|
||||
}
|
||||
|
||||
pub fn try_from_response_bytes(
|
||||
b: &[u8],
|
||||
) -> Result<SocketDataHeader, InsufficientSocketDataError> {
|
||||
if b.len() != Self::SERIALIZED_LEN {
|
||||
return Err(InsufficientSocketDataError {
|
||||
received: b.len(),
|
||||
expected: Self::SERIALIZED_LEN,
|
||||
});
|
||||
}
|
||||
|
||||
// the unwraps here are fine as we just ensured we have the exact amount of bytes we need
|
||||
let local_socket_closed = b[0] != 0;
|
||||
let connection_id = ConnectionId::from_be_bytes(b[1..9].try_into().unwrap());
|
||||
let seq = u64::from_be_bytes(b[9..].try_into().unwrap());
|
||||
|
||||
Ok(SocketDataHeader {
|
||||
seq,
|
||||
connection_id,
|
||||
local_socket_closed,
|
||||
})
|
||||
}
|
||||
|
||||
// the serialization of the header looks as follows:
|
||||
// (it's vital it's not modified as we need this exact structure for backwards compatibility)
|
||||
// SOCKET_CLOSED (1B) || CONNECTION_ID (8B) || SEQUENCE (8B)
|
||||
pub fn into_response_bytes(self) -> Vec<u8> {
|
||||
self.into_response_bytes_iter().collect()
|
||||
}
|
||||
|
||||
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
|
||||
std::iter::once(self.local_socket_closed as u8)
|
||||
.chain(self.connection_id.to_be_bytes().into_iter())
|
||||
.chain(self.seq.to_be_bytes().into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SocketData {
|
||||
pub header: SocketDataHeader,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SocketData {
|
||||
pub fn new(
|
||||
seq: u64,
|
||||
connection_id: ConnectionId,
|
||||
local_socket_closed: bool,
|
||||
data: Vec<u8>,
|
||||
) -> Self {
|
||||
SocketData {
|
||||
header: SocketDataHeader {
|
||||
seq,
|
||||
connection_id,
|
||||
local_socket_closed,
|
||||
},
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_deserialization_len(b: &[u8]) -> Result<(), InsufficientSocketDataError> {
|
||||
if b.is_empty() {
|
||||
return Err(InsufficientSocketDataError {
|
||||
received: 0,
|
||||
expected: SocketDataHeader::SERIALIZED_LEN,
|
||||
});
|
||||
}
|
||||
|
||||
if b.len() < SocketDataHeader::SERIALIZED_LEN {
|
||||
return Err(InsufficientSocketDataError {
|
||||
received: b.len(),
|
||||
expected: SocketDataHeader::SERIALIZED_LEN,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// we need to have two serialization methods for backwards compatibility,
|
||||
// since we serialized those fields differently depending on whether it was ingress vs egress...
|
||||
pub fn try_from_request_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
|
||||
Self::verify_deserialization_len(b)?;
|
||||
let header =
|
||||
SocketDataHeader::try_from_request_bytes(&b[..SocketDataHeader::SERIALIZED_LEN])?;
|
||||
let data = b[SocketDataHeader::SERIALIZED_LEN..].to_vec();
|
||||
|
||||
Ok(SocketData { header, data })
|
||||
}
|
||||
|
||||
// the serialization of the socket data looks as follows:
|
||||
// HEADER || DATA
|
||||
pub fn into_request_bytes(self) -> Vec<u8> {
|
||||
self.into_request_bytes_iter().collect()
|
||||
}
|
||||
|
||||
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
|
||||
self.header
|
||||
.into_request_bytes_iter()
|
||||
.chain(self.data.into_iter())
|
||||
}
|
||||
|
||||
pub fn try_from_response_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
|
||||
Self::verify_deserialization_len(b)?;
|
||||
|
||||
let header =
|
||||
SocketDataHeader::try_from_response_bytes(&b[..SocketDataHeader::SERIALIZED_LEN])?;
|
||||
let data = b[SocketDataHeader::SERIALIZED_LEN..].to_vec();
|
||||
|
||||
Ok(SocketData { header, data })
|
||||
}
|
||||
|
||||
pub fn into_response_bytes(self) -> Vec<u8> {
|
||||
self.into_response_bytes_iter().collect()
|
||||
}
|
||||
|
||||
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
|
||||
self.header
|
||||
.into_response_bytes_iter()
|
||||
.chain(self.data.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Socks5RequestError {
|
||||
#[error("failed to deserialize received request: {source}")]
|
||||
@@ -32,6 +212,18 @@ pub enum Socks5RequestError {
|
||||
|
||||
#[error(transparent)]
|
||||
ProviderInterfaceError(#[from] ServiceProviderMessagingError),
|
||||
|
||||
#[error("received unsupported request protocol version: {protocol_version}")]
|
||||
UnsupportedProtocolVersion {
|
||||
protocol_version: <Socks5Request as interface::ServiceProviderRequest>::ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
bincode::DefaultOptions::new()
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -39,6 +231,103 @@ mod tests {
|
||||
use super::*;
|
||||
use nym_service_providers_common::interface::RequestContent;
|
||||
|
||||
#[cfg(test)]
|
||||
mod socket_data_serialization {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn for_requests() {
|
||||
assert_eq!(
|
||||
InsufficientSocketDataError {
|
||||
received: 0,
|
||||
expected: SocketDataHeader::SERIALIZED_LEN
|
||||
},
|
||||
SocketData::try_from_request_bytes(&[]).unwrap_err()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
InsufficientSocketDataError {
|
||||
received: 10,
|
||||
expected: SocketDataHeader::SERIALIZED_LEN
|
||||
},
|
||||
SocketData::try_from_request_bytes(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap_err()
|
||||
);
|
||||
|
||||
let good_data = SocketData::new(42, 12345, false, vec![2, 3]);
|
||||
let serialized = good_data.clone().into_request_bytes();
|
||||
|
||||
assert_eq!(
|
||||
good_data,
|
||||
SocketData::try_from_request_bytes(&serialized).unwrap()
|
||||
);
|
||||
assert_ne!(
|
||||
good_data,
|
||||
SocketData::try_from_response_bytes(&serialized).unwrap()
|
||||
);
|
||||
|
||||
let raw_bytes = [
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255,
|
||||
];
|
||||
assert_eq!(
|
||||
SocketData {
|
||||
header: SocketDataHeader {
|
||||
seq: u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
|
||||
connection_id: ConnectionId::from_be_bytes([6, 6, 6, 6, 6, 6, 6, 6]),
|
||||
local_socket_closed: false,
|
||||
},
|
||||
data: vec![255, 255, 255],
|
||||
},
|
||||
SocketData::try_from_request_bytes(&raw_bytes).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_responses() {
|
||||
assert_eq!(
|
||||
InsufficientSocketDataError {
|
||||
received: 0,
|
||||
expected: SocketDataHeader::SERIALIZED_LEN
|
||||
},
|
||||
SocketData::try_from_response_bytes(&[]).unwrap_err()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
InsufficientSocketDataError {
|
||||
received: 10,
|
||||
expected: SocketDataHeader::SERIALIZED_LEN
|
||||
},
|
||||
SocketData::try_from_response_bytes(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap_err()
|
||||
);
|
||||
|
||||
let good_data = SocketData::new(42, 12345, false, vec![2, 3]);
|
||||
let serialized = good_data.clone().into_response_bytes();
|
||||
|
||||
assert_eq!(
|
||||
good_data,
|
||||
SocketData::try_from_response_bytes(&serialized).unwrap()
|
||||
);
|
||||
assert_ne!(
|
||||
good_data,
|
||||
SocketData::try_from_request_bytes(&serialized).unwrap()
|
||||
);
|
||||
|
||||
let raw_bytes = [
|
||||
0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255,
|
||||
];
|
||||
assert_eq!(
|
||||
SocketData {
|
||||
header: SocketDataHeader {
|
||||
seq: u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
|
||||
connection_id: ConnectionId::from_be_bytes([6, 6, 6, 6, 6, 6, 6, 6]),
|
||||
local_socket_closed: false,
|
||||
},
|
||||
data: vec![255, 255, 255],
|
||||
},
|
||||
SocketData::try_from_response_bytes(&raw_bytes).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod interface_backwards_compatibility {
|
||||
use super::*;
|
||||
@@ -87,9 +376,10 @@ mod tests {
|
||||
match new_deserialized.content {
|
||||
RequestContent::ProviderData(req) => match req.content {
|
||||
Socks5RequestContent::Send(send_req) => {
|
||||
assert_eq!(send_req.conn_id, 7810961472501196273);
|
||||
assert_eq!(send_req.data.len(), 111);
|
||||
assert!(!send_req.local_closed);
|
||||
assert_eq!(send_req.data.header.connection_id, 7810961472501196273);
|
||||
assert_eq!(send_req.data.header.seq, 0);
|
||||
assert_eq!(send_req.data.data.len(), 103);
|
||||
assert!(!send_req.data.header.local_socket_closed);
|
||||
}
|
||||
_ => panic!("unexpected request"),
|
||||
},
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response};
|
||||
use crate::{
|
||||
make_bincode_serializer, InsufficientSocketDataError, SocketData, Socks5ProtocolVersion,
|
||||
Socks5RequestError, Socks5Response,
|
||||
};
|
||||
use nym_service_providers_common::interface::{Serializable, ServiceProviderRequest};
|
||||
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use tap::TapFallible;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type ConnectionId = u64;
|
||||
@@ -15,6 +20,7 @@ pub type RemoteAddress = String;
|
||||
pub enum RequestFlag {
|
||||
Connect = 0,
|
||||
Send = 1,
|
||||
Query = 2,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for RequestFlag {
|
||||
@@ -24,6 +30,7 @@ impl TryFrom<u8> for RequestFlag {
|
||||
match value {
|
||||
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
|
||||
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
|
||||
_ if value == (RequestFlag::Query as u8) => Ok(Self::Query),
|
||||
value => Err(RequestDeserializationError::UnknownRequestFlag { value }),
|
||||
}
|
||||
}
|
||||
@@ -51,6 +58,15 @@ pub enum RequestDeserializationError {
|
||||
|
||||
#[error("malformed return address - {0}")]
|
||||
MalformedReturnAddress(RecipientFormattingError),
|
||||
|
||||
#[error("failed to deserialize query request: {source}")]
|
||||
QueryDeserializationError {
|
||||
#[from]
|
||||
source: bincode::Error,
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
InvalidSocketData(#[from] InsufficientSocketDataError),
|
||||
}
|
||||
|
||||
impl RequestDeserializationError {
|
||||
@@ -59,7 +75,7 @@ impl RequestDeserializationError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConnectRequest {
|
||||
// TODO: is connection_id redundant now?
|
||||
pub conn_id: ConnectionId,
|
||||
@@ -67,11 +83,15 @@ pub struct ConnectRequest {
|
||||
pub return_address: Option<Recipient>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SendRequest {
|
||||
pub conn_id: ConnectionId,
|
||||
pub data: Vec<u8>,
|
||||
pub local_closed: bool,
|
||||
pub data: SocketData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum QueryRequest {
|
||||
OpenProxy,
|
||||
Description,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -108,6 +128,13 @@ impl Serializable for Socks5Request {
|
||||
}
|
||||
|
||||
let protocol_version = Socks5ProtocolVersion::from(b[0]);
|
||||
if protocol_version > Self::max_supported_version() {
|
||||
return Err(Socks5RequestError::UnsupportedProtocolVersion { protocol_version });
|
||||
}
|
||||
|
||||
// TODO: handle the case then protocol version if less then the current one. Then we should
|
||||
// make sure to only respond with the same version
|
||||
|
||||
Ok(Socks5Request {
|
||||
protocol_version,
|
||||
content: Socks5RequestContent::try_from_bytes(&b[1..])?,
|
||||
@@ -155,22 +182,27 @@ impl Socks5Request {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_send(
|
||||
pub fn new_send(protocol_version: Socks5ProtocolVersion, data: SocketData) -> Socks5Request {
|
||||
Socks5Request {
|
||||
protocol_version,
|
||||
content: Socks5RequestContent::new_send(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_query(
|
||||
protocol_version: Socks5ProtocolVersion,
|
||||
conn_id: ConnectionId,
|
||||
data: Vec<u8>,
|
||||
local_closed: bool,
|
||||
query: QueryRequest,
|
||||
) -> Socks5Request {
|
||||
Socks5Request {
|
||||
protocol_version,
|
||||
content: Socks5RequestContent::new_send(conn_id, data, local_closed),
|
||||
content: Socks5RequestContent::Query(query),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request from a SOCKS5 client that a Nym Socks5 service provider should
|
||||
/// take an action for an application using a (probably local) Nym Socks5 proxy.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Socks5RequestContent {
|
||||
/// Start a new TCP connection to the specified `RemoteAddress` and send
|
||||
/// the request data up the connection.
|
||||
@@ -179,6 +211,8 @@ pub enum Socks5RequestContent {
|
||||
|
||||
/// Re-use an existing TCP connection, sending more request data up it.
|
||||
Send(SendRequest),
|
||||
|
||||
Query(QueryRequest),
|
||||
}
|
||||
|
||||
impl Socks5RequestContent {
|
||||
@@ -196,16 +230,8 @@ impl Socks5RequestContent {
|
||||
}
|
||||
|
||||
/// Construct a new Request::Send instance
|
||||
pub fn new_send(
|
||||
conn_id: ConnectionId,
|
||||
data: Vec<u8>,
|
||||
local_closed: bool,
|
||||
) -> Socks5RequestContent {
|
||||
Socks5RequestContent::Send(SendRequest {
|
||||
conn_id,
|
||||
data,
|
||||
local_closed,
|
||||
})
|
||||
pub fn new_send(data: SocketData) -> Socks5RequestContent {
|
||||
Socks5RequestContent::Send(SendRequest { data })
|
||||
}
|
||||
|
||||
/// Deserialize the request type, connection id, destination address and port,
|
||||
@@ -222,18 +248,27 @@ impl Socks5RequestContent {
|
||||
/// The request_flag tells us whether this is a new connection request (`new_connect`),
|
||||
/// an already-established connection we should send up (`new_send`), or
|
||||
/// a request to close an established connection (`new_close`).
|
||||
|
||||
// connect:
|
||||
// RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || <RETURN_ADDR>
|
||||
//
|
||||
// send:
|
||||
// RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA
|
||||
// where DATA: SEQ || TRUE_DATA
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
|
||||
// each request needs to at least contain flag and ConnectionId
|
||||
if b.is_empty() {
|
||||
return Err(RequestDeserializationError::NoData);
|
||||
}
|
||||
|
||||
if b.len() < 9 {
|
||||
return Err(RequestDeserializationError::ConnectionIdTooShort);
|
||||
}
|
||||
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
|
||||
match RequestFlag::try_from(b[0])? {
|
||||
RequestFlag::Connect => {
|
||||
if b.len() < 9 {
|
||||
return Err(RequestDeserializationError::ConnectionIdTooShort);
|
||||
}
|
||||
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
|
||||
|
||||
let connect_request_bytes = &b[9..];
|
||||
|
||||
// we need to be able to read at least 2 bytes that specify address length
|
||||
@@ -278,15 +313,13 @@ impl Socks5RequestContent {
|
||||
return_address,
|
||||
))
|
||||
}
|
||||
RequestFlag::Send => {
|
||||
let local_closed = b[9] != 0;
|
||||
let data = b[10..].to_vec();
|
||||
|
||||
Ok(Socks5RequestContent::Send(SendRequest {
|
||||
conn_id,
|
||||
data,
|
||||
local_closed,
|
||||
}))
|
||||
RequestFlag::Send => Ok(Socks5RequestContent::Send(SendRequest {
|
||||
data: SocketData::try_from_request_bytes(&b[1..])?,
|
||||
})),
|
||||
RequestFlag::Query => {
|
||||
use bincode::Options;
|
||||
let query = make_bincode_serializer().deserialize(&b[1..])?;
|
||||
Ok(Socks5RequestContent::Query(query))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,10 +346,21 @@ impl Socks5RequestContent {
|
||||
}
|
||||
}
|
||||
Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8)
|
||||
.chain(req.conn_id.to_be_bytes().into_iter())
|
||||
.chain(std::iter::once(req.local_closed as u8))
|
||||
.chain(req.data.into_iter())
|
||||
.chain(req.data.into_request_bytes_iter())
|
||||
.collect(),
|
||||
|
||||
Socks5RequestContent::Query(query) => {
|
||||
use bincode::Options;
|
||||
let query_bytes: Vec<u8> = make_bincode_serializer()
|
||||
.serialize(&query)
|
||||
.tap_err(|err| {
|
||||
log::error!("Failed to serialize query request: {:?}: {err}", query);
|
||||
})
|
||||
.unwrap_or_default();
|
||||
std::iter::once(RequestFlag::Query as u8)
|
||||
.chain(query_bytes.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,26 +603,7 @@ mod request_deserialization_tests {
|
||||
|
||||
#[test]
|
||||
fn works_when_request_is_sized_properly_even_without_data() {
|
||||
// correct 8 bytes of connection_id, 1 byte of local_closed and 0 bytes request data
|
||||
let request_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec();
|
||||
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Socks5RequestContent::Send(SendRequest {
|
||||
conn_id,
|
||||
data,
|
||||
local_closed,
|
||||
}) => {
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(Vec::<u8>::new(), data);
|
||||
assert!(!local_closed)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_request_is_sized_properly_and_has_data() {
|
||||
// correct 8 bytes of connection_id, 1 byte of local_closed and 3 bytes request data (all 255)
|
||||
// correct 8 bytes of connection_id, 1 byte of local_closed, 8 bytes of sequence and 0 bytes request data
|
||||
let request_bytes = [
|
||||
RequestFlag::Send as u8,
|
||||
1,
|
||||
@@ -590,6 +615,53 @@ mod request_deserialization_tests {
|
||||
7,
|
||||
8,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
.to_vec();
|
||||
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Socks5RequestContent::Send(SendRequest { data }) => {
|
||||
assert_eq!(
|
||||
u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
|
||||
data.header.connection_id
|
||||
);
|
||||
assert!(!data.header.local_socket_closed);
|
||||
assert_eq!(1, data.header.seq);
|
||||
assert!(data.data.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_request_is_sized_properly_and_has_data() {
|
||||
// correct 8 bytes of connection_id, 1 byte of local_closed, 8 bytes of sequence and 3 bytes request data (all 255)
|
||||
let request_bytes = [
|
||||
RequestFlag::Send as u8,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
@@ -598,17 +670,39 @@ mod request_deserialization_tests {
|
||||
|
||||
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Socks5RequestContent::Send(SendRequest {
|
||||
conn_id,
|
||||
data,
|
||||
local_closed,
|
||||
}) => {
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(vec![255, 255, 255], data);
|
||||
assert!(!local_closed)
|
||||
Socks5RequestContent::Send(SendRequest { data }) => {
|
||||
assert_eq!(
|
||||
u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
|
||||
data.header.connection_id
|
||||
);
|
||||
assert!(data.header.local_socket_closed);
|
||||
assert_eq!(1, data.header.seq);
|
||||
assert_eq!(vec![255, 255, 255], data.data);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod serialize_query_request {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_there_and_back() {
|
||||
let open_proxy = Socks5RequestContent::Query(QueryRequest::OpenProxy);
|
||||
let bytes_open_proxy = open_proxy.clone().into_bytes();
|
||||
assert_eq!(bytes_open_proxy, vec![2, 0]);
|
||||
|
||||
let description = Socks5RequestContent::Query(QueryRequest::Description);
|
||||
let bytes_description = description.clone().into_bytes();
|
||||
assert_eq!(bytes_description, vec![2, 1]);
|
||||
|
||||
let open_proxy2 = Socks5RequestContent::try_from_bytes(&bytes_open_proxy).unwrap();
|
||||
let description2 = Socks5RequestContent::try_from_bytes(&bytes_description).unwrap();
|
||||
|
||||
assert_eq!(open_proxy, open_proxy2);
|
||||
assert_eq!(description, description2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError};
|
||||
use crate::{
|
||||
make_bincode_serializer, ConnectionId, InsufficientSocketDataError, SocketData,
|
||||
Socks5ProtocolVersion, Socks5RequestError,
|
||||
};
|
||||
use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tap::TapFallible;
|
||||
use thiserror::Error;
|
||||
|
||||
// don't start tags from 0 for easier backwards compatibility since `NetworkData`
|
||||
@@ -13,6 +18,7 @@ use thiserror::Error;
|
||||
pub enum ResponseFlag {
|
||||
NetworkData = 1,
|
||||
ConnectionError = 2,
|
||||
Query = 3,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ResponseFlag {
|
||||
@@ -22,13 +28,20 @@ impl TryFrom<u8> for ResponseFlag {
|
||||
match value {
|
||||
_ if value == (ResponseFlag::NetworkData as u8) => Ok(Self::NetworkData),
|
||||
_ if value == (ResponseFlag::ConnectionError as u8) => Ok(Self::ConnectionError),
|
||||
_ if value == (ResponseFlag::Query as u8) => Ok(Self::Query),
|
||||
value => Err(ResponseDeserializationError::UnknownResponseFlag { value }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ResponseDeserializationError {
|
||||
#[error("the network data was malformed: {source}")]
|
||||
MalformedNetworkData {
|
||||
#[from]
|
||||
source: InsufficientSocketDataError,
|
||||
},
|
||||
|
||||
#[error("not enough bytes to recover the connection id")]
|
||||
ConnectionIdTooShort,
|
||||
|
||||
@@ -43,6 +56,12 @@ pub enum ResponseDeserializationError {
|
||||
#[from]
|
||||
source: std::string::FromUtf8Error,
|
||||
},
|
||||
|
||||
#[error("failed to deserialize query response: {source}")]
|
||||
QueryDeserializationError {
|
||||
#[from]
|
||||
source: bincode::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -105,23 +124,14 @@ impl Socks5Response {
|
||||
|
||||
pub fn new_network_data(
|
||||
protocol_version: Socks5ProtocolVersion,
|
||||
seq: u64,
|
||||
connection_id: ConnectionId,
|
||||
data: Vec<u8>,
|
||||
is_closed: bool,
|
||||
) -> Socks5Response {
|
||||
Socks5Response {
|
||||
protocol_version,
|
||||
content: Socks5ResponseContent::new_network_data(connection_id, data, is_closed),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_closed_empty(
|
||||
protocol_version: Socks5ProtocolVersion,
|
||||
connection_id: ConnectionId,
|
||||
) -> Socks5Response {
|
||||
Socks5Response {
|
||||
protocol_version,
|
||||
content: Socks5ResponseContent::new_closed_empty(connection_id),
|
||||
content: Socks5ResponseContent::new_network_data(seq, connection_id, data, is_closed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,25 +145,35 @@ impl Socks5Response {
|
||||
content: Socks5ResponseContent::new_connection_error(connection_id, error_message),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_query(
|
||||
protocol_version: Socks5ProtocolVersion,
|
||||
query_response: QueryResponse,
|
||||
) -> Socks5Response {
|
||||
Socks5Response {
|
||||
protocol_version,
|
||||
content: Socks5ResponseContent::Query(query_response),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Socks5ResponseContent {
|
||||
NetworkData(NetworkData),
|
||||
NetworkData { content: SocketData },
|
||||
ConnectionError(ConnectionError),
|
||||
Query(QueryResponse),
|
||||
}
|
||||
|
||||
impl Socks5ResponseContent {
|
||||
pub fn new_network_data(
|
||||
seq: u64,
|
||||
connection_id: ConnectionId,
|
||||
data: Vec<u8>,
|
||||
is_closed: bool,
|
||||
) -> Socks5ResponseContent {
|
||||
Socks5ResponseContent::NetworkData(NetworkData::new(connection_id, data, is_closed))
|
||||
}
|
||||
|
||||
pub fn new_closed_empty(connection_id: ConnectionId) -> Socks5ResponseContent {
|
||||
Socks5ResponseContent::NetworkData(NetworkData::new_closed_empty(connection_id))
|
||||
Socks5ResponseContent::NetworkData {
|
||||
content: SocketData::new(seq, connection_id, is_closed, data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_connection_error(
|
||||
@@ -165,9 +185,9 @@ impl Socks5ResponseContent {
|
||||
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
Socks5ResponseContent::NetworkData(res) => {
|
||||
Socks5ResponseContent::NetworkData { content } => {
|
||||
std::iter::once(ResponseFlag::NetworkData as u8)
|
||||
.chain(res.into_bytes().into_iter())
|
||||
.chain(content.into_response_bytes_iter())
|
||||
.collect()
|
||||
}
|
||||
Socks5ResponseContent::ConnectionError(res) => {
|
||||
@@ -175,6 +195,18 @@ impl Socks5ResponseContent {
|
||||
.chain(res.into_bytes().into_iter())
|
||||
.collect()
|
||||
}
|
||||
Socks5ResponseContent::Query(query) => {
|
||||
use bincode::Options;
|
||||
let query_bytes: Vec<u8> = make_bincode_serializer()
|
||||
.serialize(&query)
|
||||
.tap_err(|err| {
|
||||
log::error!("Failed to serialize query response: {:?}: {err}", query);
|
||||
})
|
||||
.unwrap_or_default();
|
||||
std::iter::once(ResponseFlag::Query as u8)
|
||||
.chain(query_bytes.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,84 +219,22 @@ impl Socks5ResponseContent {
|
||||
|
||||
let response_flag = ResponseFlag::try_from(b[0])?;
|
||||
match response_flag {
|
||||
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData(
|
||||
NetworkData::try_from_bytes(&b[1..])?,
|
||||
)),
|
||||
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData {
|
||||
content: SocketData::try_from_response_bytes(&b[1..])?,
|
||||
}),
|
||||
ResponseFlag::ConnectionError => Ok(Socks5ResponseContent::ConnectionError(
|
||||
ConnectionError::try_from_bytes(&b[1..])?,
|
||||
)),
|
||||
ResponseFlag::Query => {
|
||||
use bincode::Options;
|
||||
let query = make_bincode_serializer().deserialize(&b[1..])?;
|
||||
Ok(Socks5ResponseContent::Query(query))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A remote network network data response retrieved by the Socks5 service provider. This
|
||||
/// can be serialized and sent back through the mixnet to the requesting
|
||||
/// application.
|
||||
#[derive(Debug)]
|
||||
pub struct NetworkData {
|
||||
pub data: Vec<u8>,
|
||||
pub connection_id: ConnectionId,
|
||||
pub is_closed: bool,
|
||||
}
|
||||
|
||||
impl NetworkData {
|
||||
/// Constructor for responses
|
||||
pub fn new(connection_id: ConnectionId, data: Vec<u8>, is_closed: bool) -> Self {
|
||||
NetworkData {
|
||||
data,
|
||||
connection_id,
|
||||
is_closed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_closed_empty(connection_id: ConnectionId) -> Self {
|
||||
NetworkData {
|
||||
data: vec![],
|
||||
connection_id,
|
||||
is_closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<NetworkData, ResponseDeserializationError> {
|
||||
if b.is_empty() {
|
||||
return Err(ResponseDeserializationError::NoData);
|
||||
}
|
||||
|
||||
let is_closed = b[0] != 0;
|
||||
|
||||
if b.len() < 9 {
|
||||
return Err(ResponseDeserializationError::ConnectionIdTooShort);
|
||||
}
|
||||
|
||||
let mut connection_id_bytes = b.to_vec();
|
||||
let data = connection_id_bytes.split_off(9);
|
||||
|
||||
let connection_id = u64::from_be_bytes([
|
||||
connection_id_bytes[1],
|
||||
connection_id_bytes[2],
|
||||
connection_id_bytes[3],
|
||||
connection_id_bytes[4],
|
||||
connection_id_bytes[5],
|
||||
connection_id_bytes[6],
|
||||
connection_id_bytes[7],
|
||||
connection_id_bytes[8],
|
||||
]);
|
||||
|
||||
let response = NetworkData::new(connection_id, data, is_closed);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Serializes the response into bytes so that it can be sent back through
|
||||
/// the mixnet to the requesting application.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
std::iter::once(self.is_closed as u8)
|
||||
.chain(self.connection_id.to_be_bytes().iter().cloned())
|
||||
.chain(self.data.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ConnectionError {
|
||||
pub connection_id: ConnectionId,
|
||||
pub network_requester_error: String,
|
||||
@@ -318,62 +288,16 @@ impl ConnectionError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum QueryResponse {
|
||||
OpenProxy(bool),
|
||||
Description(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod constructing_socks5_data_responses_from_bytes {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_when_zero_bytes_are_supplied() {
|
||||
let response_bytes = Vec::new();
|
||||
|
||||
assert_eq!(
|
||||
ResponseDeserializationError::NoData,
|
||||
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_when_connection_id_bytes_are_too_short() {
|
||||
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6];
|
||||
assert_eq!(
|
||||
ResponseDeserializationError::ConnectionIdTooShort,
|
||||
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_there_is_no_data() {
|
||||
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7];
|
||||
let expected = NetworkData::new(
|
||||
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
|
||||
Vec::new(),
|
||||
false,
|
||||
);
|
||||
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
|
||||
assert_eq!(expected.connection_id, actual.connection_id);
|
||||
assert_eq!(expected.data, actual.data);
|
||||
assert_eq!(expected.is_closed, actual.is_closed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_there_is_data() {
|
||||
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255];
|
||||
let expected = NetworkData::new(
|
||||
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
|
||||
vec![255, 255, 255],
|
||||
false,
|
||||
);
|
||||
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
|
||||
assert_eq!(expected.connection_id, actual.connection_id);
|
||||
assert_eq!(expected.data, actual.data);
|
||||
assert_eq!(expected.is_closed, actual.is_closed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod connection_error_response_serde_tests {
|
||||
use super::*;
|
||||
@@ -396,11 +320,14 @@ mod tests {
|
||||
#[test]
|
||||
fn deserialization_errors() {
|
||||
let err = ConnectionError::try_from_bytes(&[]).err().unwrap();
|
||||
assert_eq!(err, ResponseDeserializationError::NoData);
|
||||
assert!(matches!(err, ResponseDeserializationError::NoData));
|
||||
|
||||
let bytes: [u8; 5] = [1, 2, 3, 4, 5];
|
||||
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
|
||||
assert_eq!(err, ResponseDeserializationError::ConnectionIdTooShort);
|
||||
assert!(matches!(
|
||||
err,
|
||||
ResponseDeserializationError::ConnectionIdTooShort
|
||||
));
|
||||
|
||||
let bytes: Vec<u8> = 42u64
|
||||
.to_be_bytes()
|
||||
@@ -414,4 +341,27 @@ mod tests {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod serialize_query_response {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_there_and_back() {
|
||||
let open_proxy = Socks5ResponseContent::Query(QueryResponse::OpenProxy(true));
|
||||
let bytes_open_proxy = open_proxy.clone().into_bytes();
|
||||
assert_eq!(bytes_open_proxy, vec![3, 0, 1]);
|
||||
|
||||
let description =
|
||||
Socks5ResponseContent::Query(QueryResponse::Description("foo".to_string()));
|
||||
let bytes_description = description.clone().into_bytes();
|
||||
assert_eq!(bytes_description, vec![3, 1, 3, 102, 111, 111]);
|
||||
|
||||
let open_proxy2 = Socks5ResponseContent::try_from_bytes(&bytes_open_proxy).unwrap();
|
||||
let description2 = Socks5ResponseContent::try_from_bytes(&bytes_description).unwrap();
|
||||
|
||||
assert_eq!(open_proxy, open_proxy2);
|
||||
assert_eq!(description, description2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+13
-5
@@ -1319,7 +1319,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-contracts-common"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -1330,7 +1330,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-crypto"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"ed25519-dalek",
|
||||
@@ -1378,7 +1378,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -1455,7 +1455,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-pemstore"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
]
|
||||
@@ -1465,6 +1465,7 @@ name = "nym-service-provider-directory"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
"cw-multi-test",
|
||||
@@ -1472,7 +1473,10 @@ dependencies = [
|
||||
"cw-utils",
|
||||
"cw2",
|
||||
"nym-contracts-common",
|
||||
"nym-crypto",
|
||||
"nym-service-provider-directory-common",
|
||||
"rand_chacha 0.2.2",
|
||||
"rstest",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -1484,8 +1488,12 @@ name = "nym-service-provider-directory-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
"cw-utils",
|
||||
"nym-contracts-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1522,7 +1530,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"nym-contracts-common",
|
||||
|
||||
@@ -22,9 +22,9 @@ name = "mixnet_contract"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.6.0" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
|
||||
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
|
||||
@@ -14,7 +14,7 @@ mod mixnet_contract_settings;
|
||||
mod mixnodes;
|
||||
mod queued_migrations;
|
||||
mod rewards;
|
||||
mod signing;
|
||||
pub mod signing;
|
||||
mod support;
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
|
||||
@@ -12,7 +12,7 @@ cw-controllers = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
|
||||
nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" }
|
||||
semver = { version = "1.0.16", default-features = false }
|
||||
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -188,6 +188,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
|
||||
|
||||
#[rstest::fixture]
|
||||
fn deps() -> TestDeps {
|
||||
instantiate_test_contract()
|
||||
@@ -691,7 +692,6 @@ mod tests {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn max_page_limit_is_applied() {
|
||||
// WIP(JON)
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
|
||||
nym-service-provider-directory-common = { path = "../../common/cosmwasm-smart-contracts/service-provider-directory" }
|
||||
semver = { version = "1.0.16", default-features = false }
|
||||
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
|
||||
@@ -22,5 +23,8 @@ thiserror = "1.0.39"
|
||||
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
|
||||
|
||||
[dev-dependencies]
|
||||
cw-multi-test = { workspace = true }
|
||||
anyhow = "1.0.40"
|
||||
cw-multi-test = { workspace = true }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
rand_chacha = "0.2"
|
||||
rstest = "0.17.0"
|
||||
|
||||
@@ -13,3 +13,5 @@ pub const SERVICE_ID_COUNTER_KEY: &str = "sidc";
|
||||
pub const SERVICES_PK_NAMESPACE: &str = "sernames";
|
||||
pub const SERVICES_ANNOUNCER_IDX_NAMESPACE: &str = "serown";
|
||||
pub const SERVICES_NYM_ADDRESS_IDX_NAMESPACE: &str = "sernyma";
|
||||
|
||||
pub const SIGNING_NONCES_NAMESPACE: &str = "sn";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
error::{ContractError, Result},
|
||||
state::{self, Config},
|
||||
Result, SpContractError,
|
||||
};
|
||||
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
|
||||
use nym_service_provider_directory_common::msg::{
|
||||
@@ -34,22 +34,25 @@ pub fn instantiate(
|
||||
.add_attribute("admin", info.sender))
|
||||
}
|
||||
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
_msg: MigrateMsg,
|
||||
) -> Result<Response, SpContractError> {
|
||||
// Note: don't remove this particular bit of code as we have to ALWAYS check whether we have to
|
||||
// update the stored version
|
||||
let version: Version =
|
||||
CONTRACT_VERSION
|
||||
.parse()
|
||||
.map_err(|error: semver::Error| ContractError::SemVerFailure {
|
||||
value: CONTRACT_VERSION.to_string(),
|
||||
error_message: error.to_string(),
|
||||
})?;
|
||||
let version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| {
|
||||
SpContractError::SemVerFailure {
|
||||
value: CONTRACT_VERSION.to_string(),
|
||||
error_message: error.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let storage_version_raw = cw2::get_contract_version(deps.storage)?.version;
|
||||
let storage_version: Version =
|
||||
storage_version_raw
|
||||
.parse()
|
||||
.map_err(|error: semver::Error| ContractError::SemVerFailure {
|
||||
.map_err(|error: semver::Error| SpContractError::SemVerFailure {
|
||||
value: storage_version_raw,
|
||||
error_message: error.to_string(),
|
||||
})?;
|
||||
@@ -69,12 +72,12 @@ pub fn execute(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
) -> Result<Response, SpContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::Announce {
|
||||
nym_address: client_address,
|
||||
service_type,
|
||||
} => execute::announce(deps, env, info, client_address, service_type),
|
||||
service,
|
||||
owner_signature,
|
||||
} => execute::announce(deps, env, info, service, owner_signature),
|
||||
ExecuteMsg::DeleteId { service_id } => execute::delete_id(deps, info, service_id),
|
||||
ExecuteMsg::DeleteNymAddress { nym_address } => {
|
||||
execute::delete_nym_address(deps, info, nym_address)
|
||||
@@ -95,6 +98,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<Binary> {
|
||||
QueryMsg::All { limit, start_after } => {
|
||||
to_binary(&query::query_all_paged(deps, limit, start_after)?)
|
||||
}
|
||||
QueryMsg::SigningNonce { address } => {
|
||||
to_binary(&query::query_current_signing_nonce(deps, address)?)
|
||||
}
|
||||
QueryMsg::Config {} => to_binary(&query::query_config(deps)?),
|
||||
QueryMsg::GetContractVersion {} => to_binary(&query::query_contract_version()),
|
||||
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?),
|
||||
@@ -107,16 +113,19 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test_helpers::{
|
||||
assert::{assert_config, assert_empty, assert_not_found, assert_service, assert_services},
|
||||
fixture::service_fixture,
|
||||
helpers::{get_attribute, nyms},
|
||||
assert::{
|
||||
assert_config, assert_current_nonce, assert_empty, assert_not_found, assert_service,
|
||||
assert_services,
|
||||
},
|
||||
fixture::new_service_details_with_sign,
|
||||
helpers::{get_attribute, nyms, test_rng},
|
||||
};
|
||||
|
||||
use cosmwasm_std::{
|
||||
testing::{mock_dependencies, mock_env, mock_info},
|
||||
Addr, Coin,
|
||||
};
|
||||
use nym_service_provider_directory_common::{msg::ExecuteMsg, ServiceId, ServiceInfo};
|
||||
use nym_service_provider_directory_common::{msg::ExecuteMsg, Service, ServiceId};
|
||||
|
||||
const DENOM: &str = "unym";
|
||||
|
||||
@@ -140,27 +149,33 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn announce_fails_incorrect_deposit() {
|
||||
fn announce_fails_incorrect_deposit_too_small() {
|
||||
let mut rng = test_rng();
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg::new(nyms(100));
|
||||
let info = mock_info("creator", &[]);
|
||||
let admin = info.sender.clone();
|
||||
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
assert_eq!(res.messages.len(), 0);
|
||||
|
||||
// Announce
|
||||
let msg: ExecuteMsg = service_fixture().into();
|
||||
let announcer = service_fixture().announcer.to_string();
|
||||
// Setup service
|
||||
let deposit = nyms(99);
|
||||
let announcer = "steve";
|
||||
let (service, owner_signature) =
|
||||
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", announcer, deposit);
|
||||
let msg = ExecuteMsg::Announce {
|
||||
service,
|
||||
owner_signature,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(&announcer, &[nyms(99)]),
|
||||
mock_info(announcer, &[nyms(99)]),
|
||||
msg.clone()
|
||||
)
|
||||
.unwrap_err(),
|
||||
ContractError::InsufficientDeposit {
|
||||
SpContractError::InsufficientDeposit {
|
||||
funds: 99u128.into(),
|
||||
deposit_required: 100u128.into(),
|
||||
}
|
||||
@@ -170,11 +185,55 @@ mod tests {
|
||||
execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(&announcer, &[nyms(101)]),
|
||||
msg
|
||||
mock_info(announcer, &[nyms(100)]),
|
||||
msg,
|
||||
)
|
||||
.unwrap_err(),
|
||||
ContractError::TooLargeDeposit {
|
||||
SpContractError::InvalidEd25519Signature,
|
||||
);
|
||||
}
|
||||
|
||||
// Announcing a service fails due to the signed deposit being different from the deposit in
|
||||
// the message.
|
||||
#[test]
|
||||
fn announce_fails_incorrect_deposit_too_large() {
|
||||
let mut rng = test_rng();
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg::new(nyms(100));
|
||||
let info = mock_info("creator", &[]);
|
||||
let admin = info.sender.clone();
|
||||
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
assert_eq!(res.messages.len(), 0);
|
||||
|
||||
// Setup service
|
||||
let deposit = nyms(101);
|
||||
let announcer = "steve";
|
||||
let (service, owner_signature) =
|
||||
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", announcer, deposit);
|
||||
let msg = ExecuteMsg::Announce {
|
||||
service,
|
||||
owner_signature,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(announcer, &[nyms(100)]),
|
||||
msg.clone()
|
||||
)
|
||||
.unwrap_err(),
|
||||
SpContractError::InvalidEd25519Signature,
|
||||
);
|
||||
assert_eq!(
|
||||
execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(announcer, &[nyms(101)]),
|
||||
msg,
|
||||
)
|
||||
.unwrap_err(),
|
||||
SpContractError::TooLargeDeposit {
|
||||
funds: 101u128.into(),
|
||||
deposit_required: 100u128.into(),
|
||||
}
|
||||
@@ -186,15 +245,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn announce_success() {
|
||||
let mut rng = test_rng();
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg::new(nyms(100));
|
||||
let info = mock_info("creator", &[]);
|
||||
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
assert_eq!(res.messages.len(), 0);
|
||||
assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 0);
|
||||
|
||||
// Setup service
|
||||
let deposit = nyms(100);
|
||||
let owner = "steve";
|
||||
let (service, owner_signature) =
|
||||
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", owner, deposit.clone());
|
||||
|
||||
// Announce
|
||||
let msg: ExecuteMsg = service_fixture().into();
|
||||
let info = mock_info("steve", &[nyms(100)]);
|
||||
let msg = ExecuteMsg::Announce {
|
||||
service: service.clone(),
|
||||
owner_signature,
|
||||
};
|
||||
let info = mock_info("steve", &[deposit.clone()]);
|
||||
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
|
||||
// Check that the service has had service id assigned to it
|
||||
@@ -208,10 +278,17 @@ mod tests {
|
||||
"network_requester".to_string()
|
||||
);
|
||||
|
||||
// Check that the nonce has been incremented, but only for the owner
|
||||
assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 1);
|
||||
assert_current_nonce(deps.as_ref(), &Addr::unchecked("timmy"), 0);
|
||||
|
||||
// The expected announced service
|
||||
let expected_service = ServiceInfo {
|
||||
let expected_service = Service {
|
||||
service_id: expected_id,
|
||||
service: service_fixture(),
|
||||
service,
|
||||
announcer: Addr::unchecked("steve"),
|
||||
block_height: 12345,
|
||||
deposit,
|
||||
};
|
||||
assert_services(deps.as_ref(), &[expected_service.clone()]);
|
||||
assert_service(deps.as_ref(), &expected_service);
|
||||
@@ -219,6 +296,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn delete() {
|
||||
let mut rng = test_rng();
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg::new(Coin::new(100, "unym"));
|
||||
let info = mock_info("creator", &[]);
|
||||
@@ -226,16 +304,25 @@ mod tests {
|
||||
assert_eq!(res.messages.len(), 0);
|
||||
|
||||
// Announce
|
||||
let msg: ExecuteMsg = service_fixture().into();
|
||||
let info_steve = mock_info("steve", &[nyms(100)]);
|
||||
assert_eq!(info_steve.sender, service_fixture().announcer);
|
||||
execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
|
||||
let deposit = nyms(100);
|
||||
let steve = "steve";
|
||||
let (service, owner_signature) =
|
||||
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", steve, deposit.clone());
|
||||
let msg = ExecuteMsg::Announce {
|
||||
service: service.clone(),
|
||||
owner_signature,
|
||||
};
|
||||
let info_steve = mock_info(steve, &[deposit.clone()]);
|
||||
execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap();
|
||||
|
||||
// The expected announced service
|
||||
let expected_id = 1;
|
||||
let expected_service = ServiceInfo {
|
||||
let expected_service = Service {
|
||||
service_id: expected_id,
|
||||
service: service_fixture(),
|
||||
service,
|
||||
announcer: Addr::unchecked(steve),
|
||||
block_height: 12345,
|
||||
deposit,
|
||||
};
|
||||
assert_services(deps.as_ref(), &[expected_service]);
|
||||
|
||||
@@ -244,27 +331,23 @@ mod tests {
|
||||
let info_timmy = mock_info("timmy", &[]);
|
||||
assert_eq!(
|
||||
execute(deps.as_mut(), mock_env(), info_timmy, msg).unwrap_err(),
|
||||
ContractError::Unauthorized {
|
||||
SpContractError::Unauthorized {
|
||||
sender: Addr::unchecked("timmy")
|
||||
}
|
||||
);
|
||||
|
||||
// Removing an non-existent service will fail
|
||||
let msg = ExecuteMsg::delete_id(expected_id + 1);
|
||||
let info_announcer = MessageInfo {
|
||||
sender: service_fixture().announcer,
|
||||
funds: vec![],
|
||||
};
|
||||
assert_eq!(
|
||||
execute(deps.as_mut(), mock_env(), info_announcer.clone(), msg).unwrap_err(),
|
||||
ContractError::NotFound {
|
||||
execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap_err(),
|
||||
SpContractError::NotFound {
|
||||
service_id: expected_id + 1
|
||||
}
|
||||
);
|
||||
|
||||
// Remove as correct announcer succeeds
|
||||
let msg = ExecuteMsg::delete_id(expected_id);
|
||||
let res = execute(deps.as_mut(), mock_env(), info_announcer, msg).unwrap();
|
||||
let res = execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
|
||||
assert_eq!(
|
||||
get_attribute(&res, "delete_id", "service_id"),
|
||||
expected_id.to_string()
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
use crate::{
|
||||
constants::{MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS, MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER},
|
||||
error::{ContractError, Result},
|
||||
state,
|
||||
state, Result, SpContractError,
|
||||
};
|
||||
use cosmwasm_std::{Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Response, Uint128};
|
||||
use nym_contracts_common::{
|
||||
signing::{MessageSignature, Verifier},
|
||||
IdentityKey,
|
||||
};
|
||||
use nym_service_provider_directory_common::{
|
||||
events::{new_announce_event, new_delete_id_event, new_update_deposit_required_event},
|
||||
NymAddress, Service, ServiceId, ServiceType,
|
||||
signing_types::construct_service_provider_announce_sign_payload,
|
||||
NymAddress, Service, ServiceDetails, ServiceId,
|
||||
};
|
||||
|
||||
use super::query;
|
||||
|
||||
fn ensure_correct_deposit(will_deposit: Uint128, deposit_required: Uint128) -> Result<()> {
|
||||
match will_deposit.cmp(&deposit_required) {
|
||||
std::cmp::Ordering::Less => Err(ContractError::InsufficientDeposit {
|
||||
std::cmp::Ordering::Less => Err(SpContractError::InsufficientDeposit {
|
||||
funds: will_deposit,
|
||||
deposit_required,
|
||||
}),
|
||||
std::cmp::Ordering::Equal => Ok(()),
|
||||
std::cmp::Ordering::Greater => Err(ContractError::TooLargeDeposit {
|
||||
std::cmp::Ordering::Greater => Err(SpContractError::TooLargeDeposit {
|
||||
funds: will_deposit,
|
||||
deposit_required,
|
||||
}),
|
||||
@@ -30,7 +34,7 @@ fn ensure_max_services_per_announcer(deps: Deps, announcer: Addr) -> Result<()>
|
||||
if current_entries.services.len() < MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER as usize {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ContractError::ReachedMaxProvidersForAdmin {
|
||||
Err(SpContractError::ReachedMaxProvidersForAdmin {
|
||||
max_providers: MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER,
|
||||
announcer,
|
||||
})
|
||||
@@ -42,7 +46,7 @@ fn ensure_max_aliases_per_nym_address(deps: Deps, nym_address: NymAddress) -> Re
|
||||
if current_entries.services.len() < MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS as usize {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ContractError::ReachedMaxAliasesForNymAddress {
|
||||
Err(SpContractError::ReachedMaxAliasesForNymAddress {
|
||||
max_aliases: MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS,
|
||||
nym_address,
|
||||
})
|
||||
@@ -50,10 +54,10 @@ fn ensure_max_aliases_per_nym_address(deps: Deps, nym_address: NymAddress) -> Re
|
||||
}
|
||||
|
||||
fn ensure_service_exists(deps: Deps, service_id: ServiceId) -> Result<()> {
|
||||
if state::services::has_service(deps.storage, service_id) {
|
||||
if state::has_service(deps.storage, service_id) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ContractError::NotFound { service_id })
|
||||
Err(SpContractError::NotFound { service_id })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +65,7 @@ fn ensure_sender_authorized(info: MessageInfo, service: &Service) -> Result<()>
|
||||
if info.sender == service.announcer {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ContractError::Unauthorized {
|
||||
Err(SpContractError::Unauthorized {
|
||||
sender: info.sender,
|
||||
})
|
||||
}
|
||||
@@ -74,31 +78,82 @@ fn return_deposit(service_to_delete: &Service) -> BankMsg {
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_announce_signature(
|
||||
deps: Deps<'_>,
|
||||
sender: Addr,
|
||||
deposit: Coin,
|
||||
service: ServiceDetails,
|
||||
signature: MessageSignature,
|
||||
) -> Result<()> {
|
||||
// recover the public key
|
||||
let public_key = decode_ed25519_identity_key(&service.identity_key)?;
|
||||
|
||||
// reconstruct the payload
|
||||
let nonce = state::get_signing_nonce(deps.storage, sender.clone())?;
|
||||
|
||||
let msg = construct_service_provider_announce_sign_payload(nonce, sender, deposit, service);
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SpContractError::InvalidEd25519Signature)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_ed25519_identity_key(encoded: &IdentityKey) -> Result<[u8; 32]> {
|
||||
let mut public_key = [0u8; 32];
|
||||
let used = bs58::decode(encoded)
|
||||
.into(&mut public_key)
|
||||
.map_err(|err| SpContractError::MalformedEd25519IdentityKey(err.to_string()))?;
|
||||
|
||||
if used != 32 {
|
||||
return Err(SpContractError::MalformedEd25519IdentityKey(
|
||||
"Too few bytes provided for the public key".into(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(public_key)
|
||||
}
|
||||
|
||||
/// Announce a new service. It will be assigned a new service provider id.
|
||||
pub fn announce(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
nym_address: NymAddress,
|
||||
service_type: ServiceType,
|
||||
service: ServiceDetails,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response> {
|
||||
ensure_max_services_per_announcer(deps.as_ref(), info.sender.clone())?;
|
||||
ensure_max_aliases_per_nym_address(deps.as_ref(), nym_address.clone())?;
|
||||
ensure_max_aliases_per_nym_address(deps.as_ref(), service.nym_address.clone())?;
|
||||
|
||||
let deposit_required = state::deposit_required(deps.storage)?;
|
||||
let denom = deposit_required.denom.clone();
|
||||
let will_deposit = cw_utils::must_pay(&info, &denom)
|
||||
.map_err(|err| ContractError::DepositRequired { source: err })?;
|
||||
.map_err(|err| SpContractError::DepositRequired { source: err })?;
|
||||
ensure_correct_deposit(will_deposit, deposit_required.amount)?;
|
||||
|
||||
let deposit = Coin::new(will_deposit.u128(), denom);
|
||||
|
||||
// Check that the sender actually owns the service provider by checking the signature
|
||||
verify_announce_signature(
|
||||
deps.as_ref(),
|
||||
info.sender.clone(),
|
||||
deposit.clone(),
|
||||
service.clone(),
|
||||
owner_signature,
|
||||
)?;
|
||||
|
||||
state::increment_signing_nonce(deps.storage, info.sender.clone())?;
|
||||
|
||||
let service_id = state::next_service_id_counter(deps.storage)?;
|
||||
let new_service = Service {
|
||||
nym_address,
|
||||
service_type,
|
||||
service_id,
|
||||
service,
|
||||
announcer: info.sender,
|
||||
block_height: env.block.height,
|
||||
deposit: Coin::new(will_deposit.u128(), denom),
|
||||
deposit,
|
||||
};
|
||||
let service_id = state::services::save(deps.storage, &new_service)?;
|
||||
state::save(deps.storage, &new_service)?;
|
||||
|
||||
Ok(Response::new().add_event(new_announce_event(service_id, new_service)))
|
||||
}
|
||||
@@ -106,15 +161,15 @@ pub fn announce(
|
||||
/// Delete an exsisting service.
|
||||
pub fn delete_id(deps: DepsMut, info: MessageInfo, service_id: ServiceId) -> Result<Response> {
|
||||
ensure_service_exists(deps.as_ref(), service_id)?;
|
||||
let service_to_delete = state::services::load_id(deps.storage, service_id)?;
|
||||
let service_to_delete = state::load_id(deps.storage, service_id)?;
|
||||
ensure_sender_authorized(info, &service_to_delete)?;
|
||||
|
||||
state::services::remove(deps.storage, service_id)?;
|
||||
state::remove(deps.storage, service_id)?;
|
||||
let return_deposit_msg = return_deposit(&service_to_delete);
|
||||
|
||||
Ok(Response::new()
|
||||
.add_message(return_deposit_msg)
|
||||
.add_event(new_delete_id_event(service_id, service_to_delete)))
|
||||
.add_event(new_delete_id_event(service_to_delete)))
|
||||
}
|
||||
|
||||
/// Delete an existing service by nym address. If there are multiple entries for a given nym
|
||||
@@ -128,15 +183,12 @@ pub(crate) fn delete_nym_address(
|
||||
let services_to_delete = query::query_nym_address(deps.as_ref(), nym_address)?.services;
|
||||
|
||||
for service_to_delete in services_to_delete {
|
||||
if info.sender == service_to_delete.service.announcer {
|
||||
state::services::remove(deps.storage, service_to_delete.service_id)?;
|
||||
let return_deposit_msg = return_deposit(&service_to_delete.service);
|
||||
if info.sender == service_to_delete.announcer {
|
||||
state::remove(deps.storage, service_to_delete.service_id)?;
|
||||
let return_deposit_msg = return_deposit(&service_to_delete);
|
||||
response = response
|
||||
.add_message(return_deposit_msg)
|
||||
.add_event(new_delete_id_event(
|
||||
service_to_delete.service_id,
|
||||
service_to_delete.service,
|
||||
));
|
||||
.add_event(new_delete_id_event(service_to_delete));
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
use cosmwasm_std::Deps;
|
||||
use nym_contracts_common::ContractBuildInformation;
|
||||
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
|
||||
use nym_service_provider_directory_common::{
|
||||
response::{ConfigResponse, PagedServicesListResponse, ServicesListResponse},
|
||||
NymAddress, ServiceId, ServiceInfo,
|
||||
NymAddress, Service, ServiceId,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::Result,
|
||||
state::{self, services::PagedLoad},
|
||||
state::{self, PagedLoad},
|
||||
Result,
|
||||
};
|
||||
|
||||
pub fn query_id(deps: Deps, service_id: ServiceId) -> Result<ServiceInfo> {
|
||||
let service = state::services::load_id(deps.storage, service_id)?;
|
||||
Ok(ServiceInfo {
|
||||
service_id,
|
||||
service,
|
||||
})
|
||||
pub fn query_id(deps: Deps, service_id: ServiceId) -> Result<Service> {
|
||||
state::load_id(deps.storage, service_id)
|
||||
}
|
||||
|
||||
pub fn query_announcer(deps: Deps, announcer: String) -> Result<ServicesListResponse> {
|
||||
let announcer = deps.api.addr_validate(&announcer)?;
|
||||
let services = state::services::load_announcer(deps.storage, announcer)?;
|
||||
let services = state::load_announcer(deps.storage, announcer)?;
|
||||
Ok(ServicesListResponse::new(services))
|
||||
}
|
||||
|
||||
pub fn query_nym_address(deps: Deps, nym_address: NymAddress) -> Result<ServicesListResponse> {
|
||||
let services = state::services::load_nym_address(deps.storage, nym_address)?;
|
||||
let services = state::load_nym_address(deps.storage, nym_address)?;
|
||||
Ok(ServicesListResponse::new(services))
|
||||
}
|
||||
|
||||
@@ -38,7 +34,7 @@ pub fn query_all_paged(
|
||||
services,
|
||||
limit,
|
||||
start_next_after,
|
||||
} = state::services::load_all_paged(deps.storage, limit, start_after)?;
|
||||
} = state::load_all_paged(deps.storage, limit, start_after)?;
|
||||
Ok(PagedServicesListResponse::new(
|
||||
services,
|
||||
limit,
|
||||
@@ -46,6 +42,11 @@ pub fn query_all_paged(
|
||||
))
|
||||
}
|
||||
|
||||
pub fn query_current_signing_nonce(deps: Deps<'_>, address: String) -> Result<Nonce> {
|
||||
let address = deps.api.addr_validate(&address)?;
|
||||
state::get_signing_nonce(deps.storage, address)
|
||||
}
|
||||
|
||||
pub fn query_config(deps: Deps) -> Result<ConfigResponse> {
|
||||
let config = state::load_config(deps.storage)?;
|
||||
Ok(config.into())
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
//! Integration tests using cw-multi-test.
|
||||
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_service_provider_directory_common::{
|
||||
response::{ConfigResponse, PagedServicesListResponse},
|
||||
NymAddress, Service, ServiceInfo, ServiceType,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
|
||||
error::ContractError,
|
||||
test_helpers::{fixture::service_info, helpers::nyms, test_setup::TestSetup},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn instantiate_contract() {
|
||||
TestSetup::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_config() {
|
||||
assert_eq!(
|
||||
TestSetup::new().query_config(),
|
||||
ConfigResponse {
|
||||
deposit_required: nyms(100),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn announce_and_query_service() {
|
||||
let mut setup = TestSetup::new();
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: None,
|
||||
}
|
||||
);
|
||||
|
||||
// Announce a first service
|
||||
let announcer = Addr::unchecked("announcer");
|
||||
let nym_address = NymAddress::new("nymAddress");
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert_eq!(setup.balance(&announcer), nyms(250));
|
||||
setup.announce_net_req(nym_address.clone(), announcer.clone());
|
||||
|
||||
// Deposit is deposited to contract and deducted from announcers's balance
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(setup.balance(&announcer), nyms(150));
|
||||
|
||||
// We can query the full service list
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![ServiceInfo {
|
||||
service_id: 1,
|
||||
service: Service {
|
||||
nym_address: nym_address.clone(),
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
announcer: announcer.clone(),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
},
|
||||
}],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(1),
|
||||
}
|
||||
);
|
||||
|
||||
// ... and we can query by id
|
||||
assert_eq!(
|
||||
setup.query_id(1),
|
||||
ServiceInfo {
|
||||
service_id: 1,
|
||||
service: Service {
|
||||
nym_address: nym_address.clone(),
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
announcer: announcer.clone(),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Announce a second service
|
||||
let announcer2 = Addr::unchecked("announcer2");
|
||||
let nym_address2 = NymAddress::new("nymAddress2");
|
||||
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(200));
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
service_info(1, nym_address, announcer),
|
||||
service_info(2, nym_address2, announcer2)
|
||||
],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(2),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_service() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(setup.balance("announcer"), nyms(150));
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
setup.delete(1, Addr::unchecked("announcer"));
|
||||
|
||||
// Deleting the service returns the deposit to the announcer
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert_eq!(setup.balance("announcer"), nyms(250));
|
||||
assert!(setup.query_all().services.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_announcer_can_delete_service() {
|
||||
let mut setup = TestSetup::new();
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
|
||||
let delete_resp: ContractError = setup
|
||||
.try_delete(1, Addr::unchecked("not_announcer"))
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(
|
||||
delete_resp,
|
||||
ContractError::Unauthorized {
|
||||
sender: Addr::unchecked("not_announcer")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cant_delete_service_that_does_not_exist() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
|
||||
let delete_resp: ContractError = setup
|
||||
.try_delete(0, Addr::unchecked("announcer"))
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(delete_resp, ContractError::NotFound { service_id: 0 });
|
||||
|
||||
let delete_resp: ContractError = setup
|
||||
.try_delete(2, Addr::unchecked("announcer"))
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(delete_resp, ContractError::NotFound { service_id: 2 });
|
||||
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
setup.delete(1, Addr::unchecked("announcer"));
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert!(setup.query_all().services.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn announce_multiple_services_and_deleting_by_name() {
|
||||
let mut setup = TestSetup::new();
|
||||
let announcer1 = Addr::unchecked("wealthy_announcer_1");
|
||||
let announcer2 = Addr::unchecked("wealthy_announcer_2");
|
||||
let nym_address1 = NymAddress::new("nymAddress1");
|
||||
let nym_address2 = NymAddress::new("nymAddress2");
|
||||
|
||||
// We announce the same address three times, but with different annoucers
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert_eq!(setup.balance(&announcer1), nyms(1000));
|
||||
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
|
||||
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
|
||||
setup.announce_net_req(nym_address2.clone(), announcer1.clone());
|
||||
setup.announce_net_req(nym_address1.clone(), announcer2.clone());
|
||||
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(500));
|
||||
assert_eq!(setup.balance(&announcer1), nyms(700));
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
service_info(1, nym_address1.clone(), announcer1.clone()),
|
||||
service_info(2, nym_address1.clone(), announcer1.clone()),
|
||||
service_info(3, nym_address2.clone(), announcer1.clone()),
|
||||
service_info(4, nym_address1.clone(), announcer2.clone()),
|
||||
service_info(5, nym_address2.clone(), announcer2.clone()),
|
||||
],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
|
||||
// Even though multiple of them point to the same nym address, we only delete the ones we actually
|
||||
// own.
|
||||
setup.delete_nym_address(nym_address1.clone(), announcer1.clone());
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(300));
|
||||
assert_eq!(setup.balance(&announcer1), nyms(900));
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
service_info(3, nym_address2.clone(), announcer1),
|
||||
service_info(4, nym_address1, announcer2.clone()),
|
||||
service_info(5, nym_address2, announcer2),
|
||||
],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// add multiple services, then query all but with a paging limit less than the number of services
|
||||
// added
|
||||
#[test]
|
||||
fn paging_works() {
|
||||
let mut setup = TestSetup::new();
|
||||
let announcer1 = Addr::unchecked("wealthy_announcer_1");
|
||||
let announcer2 = Addr::unchecked("wealthy_announcer_2");
|
||||
let nym_address1 = NymAddress::new("nymAddress1");
|
||||
let nym_address2 = NymAddress::new("nymAddress2");
|
||||
|
||||
// We announce the same address three times, but with different announcers
|
||||
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
|
||||
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
|
||||
setup.announce_net_req(nym_address2.clone(), announcer1.clone());
|
||||
setup.announce_net_req(nym_address1.clone(), announcer2.clone());
|
||||
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all_with_limit(Some(10), None),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
service_info(1, nym_address1.clone(), announcer1.clone()),
|
||||
service_info(2, nym_address1.clone(), announcer1.clone()),
|
||||
service_info(3, nym_address2.clone(), announcer1.clone()),
|
||||
service_info(4, nym_address1.clone(), announcer2.clone()),
|
||||
service_info(5, nym_address2.clone(), announcer2.clone()),
|
||||
],
|
||||
per_page: 10,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all_with_limit(Some(3), None),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
service_info(1, nym_address1.clone(), announcer1.clone()),
|
||||
service_info(2, nym_address1.clone(), announcer1.clone()),
|
||||
service_info(3, nym_address2.clone(), announcer1),
|
||||
],
|
||||
per_page: 3,
|
||||
start_next_after: Some(3),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
setup.query_all_with_limit(Some(3), Some(3)),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
service_info(4, nym_address1, announcer2.clone()),
|
||||
service_info(5, nym_address2, announcer2),
|
||||
],
|
||||
per_page: 3,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_id_increases_for_new_services() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.announce_net_req(
|
||||
NymAddress::new("nymAddress1"),
|
||||
Addr::unchecked("announcer1"),
|
||||
);
|
||||
setup.announce_net_req(
|
||||
NymAddress::new("nymAddress2"),
|
||||
Addr::unchecked("announcer2"),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
setup
|
||||
.query_all()
|
||||
.services
|
||||
.iter()
|
||||
.map(|s| s.service_id)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![1, 2],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_id_is_not_resused_when_deleting_and_then_adding_a_new_service() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.announce_net_req(
|
||||
NymAddress::new("nymAddress1"),
|
||||
Addr::unchecked("announcer1"),
|
||||
);
|
||||
setup.announce_net_req(
|
||||
NymAddress::new("nymAddress2"),
|
||||
Addr::unchecked("announcer2"),
|
||||
);
|
||||
setup.announce_net_req(
|
||||
NymAddress::new("nymAddress3"),
|
||||
Addr::unchecked("announcer3"),
|
||||
);
|
||||
|
||||
setup.delete(1, Addr::unchecked("announcer1"));
|
||||
setup.delete(3, Addr::unchecked("announcer3"));
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all().services,
|
||||
vec![service_info(
|
||||
2,
|
||||
NymAddress::new("nymAddress2"),
|
||||
Addr::unchecked("announcer2")
|
||||
)]
|
||||
);
|
||||
|
||||
setup.announce_net_req(
|
||||
NymAddress::new("nymAddress4"),
|
||||
Addr::unchecked("announcer4"),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all().services,
|
||||
vec![
|
||||
service_info(
|
||||
2,
|
||||
NymAddress::new("nymAddress2"),
|
||||
Addr::unchecked("announcer2")
|
||||
),
|
||||
service_info(
|
||||
4,
|
||||
NymAddress::new("nymAddress4"),
|
||||
Addr::unchecked("announcer4")
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_service_provider_directory_common::{
|
||||
response::PagedServicesListResponse, NymAddress, Service, ServiceDetails, ServiceType,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::{
|
||||
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
|
||||
test_helpers::{fixture::new_service, helpers::nyms},
|
||||
SpContractError,
|
||||
};
|
||||
|
||||
use super::test_setup::TestSetup;
|
||||
|
||||
#[rstest::fixture]
|
||||
fn setup() -> TestSetup {
|
||||
TestSetup::new()
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn basic_announce(mut setup: TestSetup) {
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: None,
|
||||
}
|
||||
);
|
||||
|
||||
// Announce a first service
|
||||
let announcer = Addr::unchecked("announcer");
|
||||
let nym_address = NymAddress::new("nymAddress");
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert_eq!(setup.balance(&announcer), nyms(250));
|
||||
assert_eq!(setup.query_signing_nonce(announcer.to_string()), 0);
|
||||
|
||||
let service = setup.new_service(&nym_address);
|
||||
let payload = setup.payload_to_sign(&announcer, &nyms(100), &service.service);
|
||||
let service = service.sign(payload);
|
||||
setup.announce_net_req(&service, &announcer);
|
||||
|
||||
// Deposit is deposited to contract and deducted from announcers's balance
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(setup.balance(&announcer), nyms(150));
|
||||
|
||||
// The signing nonce has been incremented
|
||||
assert_eq!(setup.query_signing_nonce(announcer.to_string()), 1);
|
||||
|
||||
// We can query the full service list
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![Service {
|
||||
service_id: 1,
|
||||
service: ServiceDetails {
|
||||
nym_address: nym_address.clone(),
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
identity_key: service.identity_key().to_string(),
|
||||
},
|
||||
announcer: announcer.clone(),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
}],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(1),
|
||||
}
|
||||
);
|
||||
|
||||
// ... and we can query by id
|
||||
assert_eq!(
|
||||
setup.query_id(1),
|
||||
Service {
|
||||
service_id: 1,
|
||||
service: service.details().clone(),
|
||||
announcer: announcer.clone(),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
}
|
||||
);
|
||||
|
||||
// Announce a second service
|
||||
let announcer2 = Addr::unchecked("announcer2");
|
||||
let nym_address2 = NymAddress::new("nymAddress2");
|
||||
let service2 = setup.new_signed_service(&nym_address2, &announcer2, &nyms(100));
|
||||
setup.announce_net_req(&service2, &announcer2);
|
||||
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 1);
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(200));
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
new_service(1, &nym_address, &announcer, service.identity_key()),
|
||||
new_service(2, &nym_address2, &announcer2, service2.identity_key())
|
||||
],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(2),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn announce_fails_when_announcer_mismatch(mut setup: TestSetup) {
|
||||
let announcer = Addr::unchecked("steve");
|
||||
let nym_address = NymAddress::new("foobar");
|
||||
let service = setup.new_signed_service(&nym_address, &announcer, &nyms(100));
|
||||
|
||||
// A difference announcer tries to announce the service
|
||||
let announcer2 = Addr::unchecked("timmy");
|
||||
|
||||
let resp: SpContractError = setup
|
||||
.try_announce_net_req(&service, &announcer2)
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
assert_eq!(resp, SpContractError::InvalidEd25519Signature);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn signing_nonce_is_increased_when_announcing(mut setup: TestSetup) {
|
||||
let announcer1 = Addr::unchecked("announcer1");
|
||||
let announcer2 = Addr::unchecked("announcer2");
|
||||
|
||||
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 0);
|
||||
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 0);
|
||||
|
||||
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress1"), &announcer1, &nyms(100));
|
||||
|
||||
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
|
||||
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 0);
|
||||
|
||||
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress2"), &announcer2, &nyms(100));
|
||||
|
||||
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
|
||||
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 1);
|
||||
|
||||
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress3"), &announcer2, &nyms(100));
|
||||
|
||||
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
|
||||
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 2);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn creating_two_services_in_a_row_without_announcing_fails(mut setup: TestSetup) {
|
||||
let announcer = Addr::unchecked("wealthy_announcer_1");
|
||||
let nym_address1 = NymAddress::new("nymAddress1");
|
||||
let nym_address2 = NymAddress::new("nymAddress2");
|
||||
let deposit = nyms(100);
|
||||
|
||||
let s1 = setup.new_signed_service(&nym_address1, &announcer, &deposit);
|
||||
|
||||
// This second service will be signed with the same nonce
|
||||
let s2 = setup.new_signed_service(&nym_address2, &announcer, &deposit);
|
||||
|
||||
// Announce the first service works, and this increments the nonce
|
||||
setup.announce_net_req(&s1, &announcer);
|
||||
|
||||
// Now the nonce has been incremented, and the signature will not match
|
||||
let resp: SpContractError = setup
|
||||
.try_announce_net_req(&s2, &announcer)
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
assert_eq!(resp, SpContractError::InvalidEd25519Signature,);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn announcing_the_same_service_twice_fails(mut setup: TestSetup) {
|
||||
let announcer = Addr::unchecked("wealthy_announcer_1");
|
||||
let nym_address = NymAddress::new("nymAddress1");
|
||||
|
||||
let s1 = setup.new_signed_service(&nym_address, &announcer, &nyms(100));
|
||||
setup.announce_net_req(&s1, &announcer);
|
||||
|
||||
// Now the nonce has been incremented, and the signature will not match
|
||||
let resp: SpContractError = setup
|
||||
.try_announce_net_req(&s1, &announcer)
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
assert_eq!(resp, SpContractError::InvalidEd25519Signature);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_service_provider_directory_common::{response::PagedServicesListResponse, NymAddress};
|
||||
|
||||
use crate::{
|
||||
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
|
||||
test_helpers::{fixture::new_service, helpers::nyms},
|
||||
SpContractError,
|
||||
};
|
||||
|
||||
use super::test_setup::TestSetup;
|
||||
|
||||
#[test]
|
||||
fn delete_service() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress"),
|
||||
&Addr::unchecked("announcer"),
|
||||
&nyms(100),
|
||||
);
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(setup.balance("announcer"), nyms(150));
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
setup.delete(1, &Addr::unchecked("announcer"));
|
||||
|
||||
// Deleting the service returns the deposit to the announcer
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert_eq!(setup.balance("announcer"), nyms(250));
|
||||
assert!(setup.query_all().services.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_announcer_can_delete_service() {
|
||||
let mut setup = TestSetup::new();
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress"),
|
||||
&Addr::unchecked("announcer"),
|
||||
&nyms(100),
|
||||
);
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
|
||||
let delete_resp: SpContractError = setup
|
||||
.try_delete(1, &Addr::unchecked("not_announcer"))
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(
|
||||
delete_resp,
|
||||
SpContractError::Unauthorized {
|
||||
sender: Addr::unchecked("not_announcer")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cant_delete_service_that_does_not_exist() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress"),
|
||||
&Addr::unchecked("announcer"),
|
||||
&nyms(100),
|
||||
);
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
|
||||
let delete_resp: SpContractError = setup
|
||||
.try_delete(0, &Addr::unchecked("announcer"))
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(delete_resp, SpContractError::NotFound { service_id: 0 });
|
||||
|
||||
let delete_resp: SpContractError = setup
|
||||
.try_delete(2, &Addr::unchecked("announcer"))
|
||||
.unwrap_err()
|
||||
.downcast()
|
||||
.unwrap();
|
||||
assert_eq!(setup.contract_balance(), nyms(100));
|
||||
assert_eq!(delete_resp, SpContractError::NotFound { service_id: 2 });
|
||||
|
||||
assert!(!setup.query_all().services.is_empty());
|
||||
setup.delete(1, &Addr::unchecked("announcer"));
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert!(setup.query_all().services.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn announce_multiple_services_and_deleting_by_name() {
|
||||
let mut setup = TestSetup::new();
|
||||
let announcer1 = Addr::unchecked("wealthy_announcer_1");
|
||||
let announcer2 = Addr::unchecked("wealthy_announcer_2");
|
||||
let nym_address1 = NymAddress::new("nymAddress1");
|
||||
let nym_address2 = NymAddress::new("nymAddress2");
|
||||
let deposit = nyms(100);
|
||||
|
||||
// We announce the same address three times, but with different annoucers
|
||||
assert_eq!(setup.contract_balance(), nyms(0));
|
||||
assert_eq!(setup.balance(&announcer1), nyms(1000));
|
||||
let s1 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
|
||||
let s2 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
|
||||
let s3 = setup.sign_and_announce_net_req(&nym_address2, &announcer1, &deposit);
|
||||
let s4 = setup.sign_and_announce_net_req(&nym_address1, &announcer2, &deposit);
|
||||
let s5 = setup.sign_and_announce_net_req(&nym_address2, &announcer2, &deposit);
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(500));
|
||||
assert_eq!(setup.balance(&announcer1), nyms(700));
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
|
||||
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
|
||||
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
|
||||
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
|
||||
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
|
||||
],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
|
||||
// Even though multiple of them point to the same nym address, we only delete the ones we actually
|
||||
// own.
|
||||
setup.delete_nym_address(&nym_address1, &announcer1);
|
||||
|
||||
assert_eq!(setup.contract_balance(), nyms(300));
|
||||
assert_eq!(setup.balance(&announcer1), nyms(900));
|
||||
assert_eq!(
|
||||
setup.query_all(),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
|
||||
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
|
||||
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
|
||||
],
|
||||
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//! Integration tests using cw-multi-test.
|
||||
|
||||
mod announce;
|
||||
mod delete;
|
||||
mod query;
|
||||
mod service_id;
|
||||
mod test_service;
|
||||
mod test_setup;
|
||||
|
||||
#[test]
|
||||
fn instantiate_contract() {
|
||||
test_setup::TestSetup::new();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_service_provider_directory_common::{
|
||||
response::{ConfigResponse, PagedServicesListResponse},
|
||||
NymAddress,
|
||||
};
|
||||
|
||||
use crate::test_helpers::{fixture::new_service, helpers::nyms};
|
||||
|
||||
use super::test_setup::TestSetup;
|
||||
|
||||
#[test]
|
||||
fn query_config() {
|
||||
assert_eq!(
|
||||
TestSetup::new().query_config(),
|
||||
ConfigResponse {
|
||||
deposit_required: nyms(100),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// add multiple services, then query all but with a paging limit less than the number of services
|
||||
// added
|
||||
#[test]
|
||||
fn paging_works() {
|
||||
let mut setup = TestSetup::new();
|
||||
let announcer1 = Addr::unchecked("wealthy_announcer_1");
|
||||
let announcer2 = Addr::unchecked("wealthy_announcer_2");
|
||||
let nym_address1 = NymAddress::new("nymAddress1");
|
||||
let nym_address2 = NymAddress::new("nymAddress2");
|
||||
let deposit = nyms(100);
|
||||
|
||||
// We announce the same address three times, but with different announcers
|
||||
let s1 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
|
||||
let s2 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
|
||||
let s3 = setup.sign_and_announce_net_req(&nym_address2, &announcer1, &deposit);
|
||||
let s4 = setup.sign_and_announce_net_req(&nym_address1, &announcer2, &deposit);
|
||||
let s5 = setup.sign_and_announce_net_req(&nym_address2, &announcer2, &deposit);
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all_with_limit(Some(10), None),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
|
||||
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
|
||||
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
|
||||
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
|
||||
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
|
||||
],
|
||||
per_page: 10,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all_with_limit(Some(3), None),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
|
||||
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
|
||||
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
|
||||
],
|
||||
per_page: 3,
|
||||
start_next_after: Some(3),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
setup.query_all_with_limit(Some(3), Some(3)),
|
||||
PagedServicesListResponse {
|
||||
services: vec![
|
||||
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
|
||||
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
|
||||
],
|
||||
per_page: 3,
|
||||
start_next_after: Some(5),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_service_provider_directory_common::NymAddress;
|
||||
|
||||
use crate::test_helpers::{fixture::new_service, helpers::nyms};
|
||||
|
||||
use super::test_setup::TestSetup;
|
||||
|
||||
#[test]
|
||||
fn service_id_increases_for_new_services() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress1"),
|
||||
&Addr::unchecked("announcer1"),
|
||||
&nyms(100),
|
||||
);
|
||||
setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress2"),
|
||||
&Addr::unchecked("announcer2"),
|
||||
&nyms(100),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
setup
|
||||
.query_all()
|
||||
.services
|
||||
.iter()
|
||||
.map(|s| s.service_id)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![1, 2],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_id_is_not_resused_when_deleting_and_then_adding_a_new_service() {
|
||||
let mut setup = TestSetup::new();
|
||||
setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress1"),
|
||||
&Addr::unchecked("announcer1"),
|
||||
&nyms(100),
|
||||
);
|
||||
let s2 = setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress2"),
|
||||
&Addr::unchecked("announcer2"),
|
||||
&nyms(100),
|
||||
);
|
||||
setup.sign_and_announce_net_req(
|
||||
&NymAddress::new("nymAddress3"),
|
||||
&Addr::unchecked("announcer3"),
|
||||
&nyms(100),
|
||||
);
|
||||
|
||||
setup.delete(1, &Addr::unchecked("announcer1"));
|
||||
setup.delete(3, &Addr::unchecked("announcer3"));
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all().services,
|
||||
vec![new_service(
|
||||
2,
|
||||
&NymAddress::new("nymAddress2"),
|
||||
&Addr::unchecked("announcer2"),
|
||||
s2.identity_key(),
|
||||
)]
|
||||
);
|
||||
|
||||
let s4 = setup.new_signed_service(
|
||||
&NymAddress::new("nymAddress4"),
|
||||
&Addr::unchecked("announcer4"),
|
||||
&nyms(100),
|
||||
);
|
||||
setup.announce_net_req(&s4, &Addr::unchecked("announcer4"));
|
||||
|
||||
assert_eq!(
|
||||
setup.query_all().services,
|
||||
vec![
|
||||
new_service(
|
||||
2,
|
||||
&NymAddress::new("nymAddress2"),
|
||||
&Addr::unchecked("announcer2"),
|
||||
s2.identity_key(),
|
||||
),
|
||||
new_service(
|
||||
4,
|
||||
&NymAddress::new("nymAddress4"),
|
||||
&Addr::unchecked("announcer4"),
|
||||
s4.identity_key(),
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use nym_contracts_common::{signing::MessageSignature, IdentityKey};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_service_provider_directory_common::{
|
||||
signing_types::SignableServiceProviderAnnounceMsg, NymAddress, ServiceDetails, ServiceType,
|
||||
};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
use crate::test_helpers::signing::ed25519_sign_message;
|
||||
|
||||
pub struct TestService {
|
||||
pub service: ServiceDetails,
|
||||
pub keys: identity::KeyPair,
|
||||
pub rng: ChaCha20Rng,
|
||||
}
|
||||
|
||||
impl TestService {
|
||||
pub fn new(rng: &mut ChaCha20Rng, nym_address: NymAddress) -> Self {
|
||||
let keys = identity::KeyPair::new(rng);
|
||||
let service = ServiceDetails {
|
||||
nym_address,
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
identity_key: keys.public_key().to_base58_string(),
|
||||
};
|
||||
Self {
|
||||
service,
|
||||
keys,
|
||||
rng: rng.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity_key(&self) -> &IdentityKey {
|
||||
&self.service.identity_key
|
||||
}
|
||||
|
||||
pub fn details(&self) -> &ServiceDetails {
|
||||
&self.service
|
||||
}
|
||||
|
||||
pub fn sign(self, payload: SignableServiceProviderAnnounceMsg) -> SignedTestService {
|
||||
let owner_signature = ed25519_sign_message(payload, self.keys.private_key());
|
||||
SignedTestService {
|
||||
service: self.service,
|
||||
keys: self.keys,
|
||||
owner_signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestService> for ServiceDetails {
|
||||
fn from(test_service: TestService) -> Self {
|
||||
test_service.service
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SignedTestService {
|
||||
pub service: ServiceDetails,
|
||||
pub keys: identity::KeyPair,
|
||||
pub owner_signature: MessageSignature,
|
||||
}
|
||||
|
||||
impl SignedTestService {
|
||||
pub fn identity_key(&self) -> &IdentityKey {
|
||||
&self.service.identity_key
|
||||
}
|
||||
|
||||
pub fn details(&self) -> &ServiceDetails {
|
||||
&self.service
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignedTestService> for ServiceDetails {
|
||||
fn from(signed_service: SignedTestService) -> Self {
|
||||
signed_service.service
|
||||
}
|
||||
}
|
||||
+102
-31
@@ -1,14 +1,21 @@
|
||||
use anyhow::Result;
|
||||
use cosmwasm_std::{coins, Addr, Coin, Uint128};
|
||||
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
|
||||
use nym_contracts_common::signing::Nonce;
|
||||
use nym_service_provider_directory_common::{
|
||||
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
|
||||
response::{ConfigResponse, PagedServicesListResponse},
|
||||
NymAddress, ServiceId, ServiceInfo, ServiceType,
|
||||
signing_types::{
|
||||
construct_service_provider_announce_sign_payload, SignableServiceProviderAnnounceMsg,
|
||||
},
|
||||
NymAddress, Service, ServiceDetails, ServiceId,
|
||||
};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::test_helpers::helpers::get_app_attribute;
|
||||
use crate::test_helpers::helpers::{get_app_attribute, test_rng};
|
||||
|
||||
use super::test_service::{SignedTestService, TestService};
|
||||
|
||||
const DENOM: &str = "unym";
|
||||
const ADDRESSES: &[&str] = &[
|
||||
@@ -19,6 +26,8 @@ const ADDRESSES: &[&str] = &[
|
||||
"announcer2",
|
||||
"announcer3",
|
||||
"announcer4",
|
||||
"steve",
|
||||
"timmy",
|
||||
];
|
||||
const WEALTHY_ADDRESSES: &[&str] = &["wealthy_announcer_1", "wealthy_announcer_2"];
|
||||
|
||||
@@ -26,6 +35,7 @@ const WEALTHY_ADDRESSES: &[&str] = &["wealthy_announcer_1", "wealthy_announcer_2
|
||||
pub struct TestSetup {
|
||||
app: App,
|
||||
addr: Addr,
|
||||
rng: ChaCha20Rng,
|
||||
}
|
||||
|
||||
impl Default for TestSetup {
|
||||
@@ -51,7 +61,8 @@ impl TestSetup {
|
||||
let code = ContractWrapper::new(crate::execute, crate::instantiate, crate::query);
|
||||
let code_id = app.store_code(Box::new(code));
|
||||
let addr = Self::instantiate(&mut app, code_id);
|
||||
TestSetup { app, addr }
|
||||
let rng = test_rng();
|
||||
TestSetup { app, addr, rng }
|
||||
}
|
||||
|
||||
fn instantiate(app: &mut App, code_id: u64) -> Addr {
|
||||
@@ -68,11 +79,6 @@ impl TestSetup {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn address(&self) -> &Addr {
|
||||
&self.addr
|
||||
}
|
||||
|
||||
pub fn contract_balance(&self) -> Coin {
|
||||
self.app.wrap().query_balance(&self.addr, DENOM).unwrap()
|
||||
}
|
||||
@@ -88,7 +94,7 @@ impl TestSetup {
|
||||
self.query(&QueryMsg::Config {})
|
||||
}
|
||||
|
||||
pub fn query_id(&self, service_id: ServiceId) -> ServiceInfo {
|
||||
pub fn query_id(&self, service_id: ServiceId) -> Service {
|
||||
self.query(&QueryMsg::ServiceId { service_id })
|
||||
}
|
||||
|
||||
@@ -104,22 +110,69 @@ impl TestSetup {
|
||||
self.query(&QueryMsg::All { limit, start_after })
|
||||
}
|
||||
|
||||
pub fn announce_net_req(&mut self, address: NymAddress, announcer: Addr) -> AppResponse {
|
||||
let resp = self
|
||||
.app
|
||||
.execute_contract(
|
||||
announcer,
|
||||
self.addr.clone(),
|
||||
&ExecuteMsg::Announce {
|
||||
nym_address: address,
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
},
|
||||
&[Coin {
|
||||
denom: DENOM.to_string(),
|
||||
amount: Uint128::new(100),
|
||||
}],
|
||||
)
|
||||
.unwrap();
|
||||
pub fn query_signing_nonce(&self, address: String) -> Nonce {
|
||||
self.query(&QueryMsg::SigningNonce { address })
|
||||
}
|
||||
|
||||
// Create a new service, together with its signing keypair
|
||||
pub fn new_service(&mut self, nym_address: &NymAddress) -> TestService {
|
||||
TestService::new(&mut self.rng, nym_address.clone())
|
||||
}
|
||||
|
||||
// Create payload for the service operator to sign
|
||||
pub fn payload_to_sign(
|
||||
&mut self,
|
||||
announcer: &Addr,
|
||||
deposit: &Coin,
|
||||
service: &ServiceDetails,
|
||||
) -> SignableServiceProviderAnnounceMsg {
|
||||
let nonce = self.query_signing_nonce(announcer.to_string());
|
||||
construct_service_provider_announce_sign_payload(
|
||||
nonce,
|
||||
announcer.clone(),
|
||||
deposit.clone(),
|
||||
service.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
// Convenience function for creating a new service and signing it.
|
||||
pub fn new_signed_service(
|
||||
&mut self,
|
||||
nym_address: &NymAddress,
|
||||
announcer: &Addr,
|
||||
deposit: &Coin,
|
||||
) -> SignedTestService {
|
||||
let service = self.new_service(nym_address);
|
||||
let payload = self.payload_to_sign(announcer, deposit, service.details());
|
||||
service.sign(payload)
|
||||
}
|
||||
|
||||
// Announce a new service
|
||||
pub fn try_announce_net_req(
|
||||
&mut self,
|
||||
service: &SignedTestService,
|
||||
announcer: &Addr,
|
||||
) -> Result<AppResponse> {
|
||||
self.app.execute_contract(
|
||||
announcer.clone(),
|
||||
self.addr.clone(),
|
||||
&ExecuteMsg::Announce {
|
||||
service: service.service.clone(),
|
||||
owner_signature: service.owner_signature.clone(),
|
||||
},
|
||||
&[Coin {
|
||||
denom: DENOM.to_string(),
|
||||
amount: Uint128::new(100),
|
||||
}],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn announce_net_req(
|
||||
&mut self,
|
||||
service: &SignedTestService,
|
||||
announcer: &Addr,
|
||||
) -> AppResponse {
|
||||
let resp = self.try_announce_net_req(service, announcer).unwrap();
|
||||
assert_eq!(
|
||||
get_app_attribute(&resp, "wasm-announce", "action"),
|
||||
"announce"
|
||||
@@ -127,16 +180,28 @@ impl TestSetup {
|
||||
resp
|
||||
}
|
||||
|
||||
pub fn try_delete(&mut self, service_id: ServiceId, announcer: Addr) -> Result<AppResponse> {
|
||||
// Convenience function for create a new signed service and announcing it
|
||||
pub fn sign_and_announce_net_req(
|
||||
&mut self,
|
||||
nym_address: &NymAddress,
|
||||
announcer: &Addr,
|
||||
deposit: &Coin,
|
||||
) -> SignedTestService {
|
||||
let service = self.new_signed_service(nym_address, announcer, deposit);
|
||||
let _ = self.announce_net_req(&service, announcer);
|
||||
service
|
||||
}
|
||||
|
||||
pub fn try_delete(&mut self, service_id: ServiceId, announcer: &Addr) -> Result<AppResponse> {
|
||||
self.app.execute_contract(
|
||||
announcer,
|
||||
announcer.clone(),
|
||||
self.addr.clone(),
|
||||
&ExecuteMsg::DeleteId { service_id },
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, service_id: ServiceId, announcer: Addr) -> AppResponse {
|
||||
pub fn delete(&mut self, service_id: ServiceId, announcer: &Addr) -> AppResponse {
|
||||
let delete_resp = self.try_delete(service_id, announcer).unwrap();
|
||||
assert_eq!(
|
||||
get_app_attribute(&delete_resp, "wasm-delete_id", "action"),
|
||||
@@ -145,12 +210,18 @@ impl TestSetup {
|
||||
delete_resp
|
||||
}
|
||||
|
||||
pub fn delete_nym_address(&mut self, nym_address: NymAddress, announcer: Addr) -> AppResponse {
|
||||
pub fn delete_nym_address(
|
||||
&mut self,
|
||||
nym_address: &NymAddress,
|
||||
announcer: &Addr,
|
||||
) -> AppResponse {
|
||||
self.app
|
||||
.execute_contract(
|
||||
announcer,
|
||||
announcer.clone(),
|
||||
self.addr.clone(),
|
||||
&ExecuteMsg::DeleteNymAddress { nym_address },
|
||||
&ExecuteMsg::DeleteNymAddress {
|
||||
nym_address: nym_address.clone(),
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap()
|
||||
@@ -4,7 +4,8 @@
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use crate::error::Result;
|
||||
pub use nym_service_provider_directory_common::error::{Result, SpContractError};
|
||||
|
||||
use nym_service_provider_directory_common::msg::{
|
||||
ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
|
||||
};
|
||||
@@ -12,14 +13,11 @@ use nym_service_provider_directory_common::msg::{
|
||||
#[cfg(not(feature = "library"))]
|
||||
use cosmwasm_std::entry_point;
|
||||
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
|
||||
use error::ContractError;
|
||||
|
||||
mod constants;
|
||||
mod contract;
|
||||
mod error;
|
||||
mod state;
|
||||
|
||||
pub mod constants;
|
||||
|
||||
#[cfg(test)]
|
||||
mod integration_tests;
|
||||
#[cfg(test)]
|
||||
@@ -38,7 +36,7 @@ pub fn instantiate(
|
||||
|
||||
/// Contract entry point for migrations.
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, SpContractError> {
|
||||
contract::migrate(deps, env, msg)
|
||||
}
|
||||
|
||||
@@ -49,7 +47,7 @@ pub fn execute(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
) -> Result<Response, SpContractError> {
|
||||
contract::execute(deps, env, info, msg)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use cosmwasm_std::{Addr, Deps, DepsMut};
|
||||
use cw_controllers::Admin;
|
||||
|
||||
use crate::{constants::ADMIN_KEY, error::Result};
|
||||
use crate::{constants::ADMIN_KEY, Result};
|
||||
|
||||
const ADMIN: Admin = Admin::new(ADMIN_KEY);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use cw_storage_plus::Item;
|
||||
use nym_service_provider_directory_common::response::ConfigResponse;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{constants::CONFIG_KEY, error::Result};
|
||||
use crate::{constants::CONFIG_KEY, Result};
|
||||
|
||||
const CONFIG: Item<Config> = Item::new(CONFIG_KEY);
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
pub mod admin;
|
||||
pub mod config;
|
||||
pub mod service_id_counter;
|
||||
pub mod services;
|
||||
mod admin;
|
||||
mod config;
|
||||
mod nonce;
|
||||
mod service_id_counter;
|
||||
mod services;
|
||||
|
||||
pub(crate) use admin::{assert_admin, set_admin};
|
||||
pub(crate) use config::{deposit_required, load_config, save_config, Config};
|
||||
pub(crate) use nonce::{get_signing_nonce, increment_signing_nonce};
|
||||
pub(crate) use service_id_counter::next_service_id_counter;
|
||||
pub(crate) use services::{
|
||||
has_service, load_all_paged, load_announcer, load_id, load_nym_address, remove, save, PagedLoad,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{constants::SIGNING_NONCES_NAMESPACE, Result};
|
||||
|
||||
use cosmwasm_std::{Addr, Storage};
|
||||
use cw_storage_plus::Map;
|
||||
use nym_contracts_common::signing::Nonce;
|
||||
|
||||
pub const NONCES: Map<'_, Addr, Nonce> = Map::new(SIGNING_NONCES_NAMESPACE);
|
||||
|
||||
pub fn get_signing_nonce(storage: &dyn Storage, address: Addr) -> Result<Nonce> {
|
||||
let nonce = NONCES.may_load(storage, address)?.unwrap_or(0);
|
||||
Ok(nonce)
|
||||
}
|
||||
|
||||
fn update_signing_nonce(storage: &mut dyn Storage, address: Addr, value: Nonce) -> Result<()> {
|
||||
NONCES
|
||||
.save(storage, address, &value)
|
||||
.map_err(|err| err.into())
|
||||
}
|
||||
|
||||
pub fn increment_signing_nonce(storage: &mut dyn Storage, address: Addr) -> Result<()> {
|
||||
// get the current nonce
|
||||
let nonce = get_signing_nonce(storage, address.clone())?;
|
||||
|
||||
// increment it for the next use
|
||||
update_signing_nonce(storage, address, nonce + 1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_helpers::transactions::instantiate_test_contract;
|
||||
use cosmwasm_std::{
|
||||
testing::{MockApi, MockQuerier},
|
||||
MemoryStorage, OwnedDeps,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
|
||||
|
||||
#[rstest::fixture]
|
||||
fn deps() -> TestDeps {
|
||||
instantiate_test_contract()
|
||||
}
|
||||
|
||||
fn addr(s: &str) -> Addr {
|
||||
Addr::unchecked(s)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn getting_signing_nonce_doesnt_increment_it(deps: TestDeps) {
|
||||
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
|
||||
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn increment_works(mut deps: TestDeps) {
|
||||
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
|
||||
increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap();
|
||||
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn incrementing_is_independent(mut deps: TestDeps) {
|
||||
increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap();
|
||||
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1);
|
||||
assert_eq!(get_signing_nonce(&deps.storage, addr("bjorn")).unwrap(), 0);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use cosmwasm_std::Storage;
|
||||
use cw_storage_plus::Item;
|
||||
use nym_service_provider_directory_common::ServiceId;
|
||||
|
||||
use crate::{constants::SERVICE_ID_COUNTER_KEY, error::Result};
|
||||
use crate::{constants::SERVICE_ID_COUNTER_KEY, Result};
|
||||
|
||||
const SERVICE_ID_COUNTER: Item<ServiceId> = Item::new(SERVICE_ID_COUNTER_KEY);
|
||||
|
||||
@@ -17,29 +17,50 @@ pub(crate) fn next_service_id_counter(store: &mut dyn Storage) -> Result<Service
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use nym_service_provider_directory_common::ServiceInfo;
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_service_provider_directory_common::Service;
|
||||
|
||||
use crate::test_helpers::{
|
||||
assert::assert_services,
|
||||
fixture::service_fixture,
|
||||
helpers::{announce_service, delete_service, instantiate_test_contract},
|
||||
helpers::{nyms, test_rng},
|
||||
transactions::{announce_service, delete_service, instantiate_test_contract},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn get_next_service_id() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
let mut rng = test_rng();
|
||||
|
||||
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 1);
|
||||
assert_services(deps.as_ref(), &[ServiceInfo::new(1, service_fixture())]);
|
||||
|
||||
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 2);
|
||||
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 3);
|
||||
let (id1, service1) = announce_service(deps.as_mut(), &mut rng, "addr1", "timmy");
|
||||
let (id2, service2) = announce_service(deps.as_mut(), &mut rng, "addr2", "timmy");
|
||||
let (id3, service3) = announce_service(deps.as_mut(), &mut rng, "addr3", "timmy");
|
||||
assert_eq!(id1, 1);
|
||||
assert_eq!(id2, 2);
|
||||
assert_eq!(id3, 3);
|
||||
assert_services(
|
||||
deps.as_ref(),
|
||||
&[
|
||||
ServiceInfo::new(1, service_fixture()),
|
||||
ServiceInfo::new(2, service_fixture()),
|
||||
ServiceInfo::new(3, service_fixture()),
|
||||
Service {
|
||||
service_id: 1,
|
||||
service: service1,
|
||||
announcer: Addr::unchecked("timmy"),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
},
|
||||
Service {
|
||||
service_id: 2,
|
||||
service: service2,
|
||||
announcer: Addr::unchecked("timmy"),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
},
|
||||
Service {
|
||||
service_id: 3,
|
||||
service: service3,
|
||||
announcer: Addr::unchecked("timmy"),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -47,31 +68,28 @@ mod tests {
|
||||
#[test]
|
||||
fn deleted_service_id_is_not_reused() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
let mut rng = test_rng();
|
||||
|
||||
// Announce
|
||||
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 1);
|
||||
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 2);
|
||||
let (_, service1) = announce_service(deps.as_mut(), &mut rng, "addr1", "timmy");
|
||||
let _ = announce_service(deps.as_mut(), &mut rng, "addr2", "timmy");
|
||||
|
||||
//// Delete the last entry
|
||||
delete_service(deps.as_mut(), 2, "timmy");
|
||||
assert_services(
|
||||
deps.as_ref(),
|
||||
&[
|
||||
ServiceInfo::new(1, service_fixture()),
|
||||
ServiceInfo::new(2, service_fixture()),
|
||||
],
|
||||
&[Service {
|
||||
service_id: 1,
|
||||
service: service1,
|
||||
announcer: Addr::unchecked("timmy"),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
}],
|
||||
);
|
||||
|
||||
// Delete the last entry
|
||||
delete_service(deps.as_mut(), 2, "steve");
|
||||
assert_services(deps.as_ref(), &[ServiceInfo::new(1, service_fixture())]);
|
||||
|
||||
// Create a third entry. The index should not reuse the previous entry that we just
|
||||
// deleted.
|
||||
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 3);
|
||||
assert_services(
|
||||
deps.as_ref(),
|
||||
&[
|
||||
ServiceInfo::new(1, service_fixture()),
|
||||
ServiceInfo::new(3, service_fixture()),
|
||||
],
|
||||
);
|
||||
let (id3, _) = announce_service(deps.as_mut(), &mut rng, "addr3", "timmy");
|
||||
assert_eq!(id3, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
SERVICES_ANNOUNCER_IDX_NAMESPACE, SERVICES_NYM_ADDRESS_IDX_NAMESPACE,
|
||||
SERVICES_PK_NAMESPACE, SERVICE_DEFAULT_RETRIEVAL_LIMIT, SERVICE_MAX_RETRIEVAL_LIMIT,
|
||||
},
|
||||
error::{ContractError, Result},
|
||||
Result, SpContractError,
|
||||
};
|
||||
|
||||
struct ServiceIndex<'a> {
|
||||
@@ -26,7 +26,7 @@ impl<'a> IndexList<Service> for ServiceIndex<'a> {
|
||||
fn services<'a>() -> IndexedMap<'a, ServiceId, Service, ServiceIndex<'a>> {
|
||||
let indexes = ServiceIndex {
|
||||
nym_address: MultiIndex::new(
|
||||
|d| d.nym_address.to_string(),
|
||||
|d| d.service.nym_address.to_string(),
|
||||
SERVICES_PK_NAMESPACE,
|
||||
SERVICES_NYM_ADDRESS_IDX_NAMESPACE,
|
||||
),
|
||||
@@ -39,10 +39,10 @@ fn services<'a>() -> IndexedMap<'a, ServiceId, Service, ServiceIndex<'a>> {
|
||||
IndexedMap::new(SERVICES_PK_NAMESPACE, indexes)
|
||||
}
|
||||
|
||||
pub fn save(store: &mut dyn Storage, new_service: &Service) -> Result<ServiceId> {
|
||||
let service_id = super::next_service_id_counter(store)?;
|
||||
pub fn save(store: &mut dyn Storage, new_service: &Service) -> Result<()> {
|
||||
let service_id = new_service.service_id;
|
||||
services().save(store, service_id, new_service)?;
|
||||
Ok(service_id)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(store: &mut dyn Storage, service_id: ServiceId) -> Result<()> {
|
||||
@@ -55,39 +55,38 @@ pub fn has_service(store: &dyn Storage, service_id: ServiceId) -> bool {
|
||||
|
||||
pub fn load_id(store: &dyn Storage, service_id: ServiceId) -> Result<Service> {
|
||||
services().load(store, service_id).map_err(|err| match err {
|
||||
StdError::NotFound { .. } => ContractError::NotFound { service_id },
|
||||
StdError::NotFound { .. } => SpContractError::NotFound { service_id },
|
||||
err => err.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_announcer(store: &dyn Storage, announcer: Addr) -> Result<Vec<(ServiceId, Service)>> {
|
||||
pub fn load_announcer(store: &dyn Storage, announcer: Addr) -> Result<Vec<Service>> {
|
||||
let services = services()
|
||||
.idx
|
||||
.announcer
|
||||
.prefix(announcer)
|
||||
.range(store, None, None, Order::Ascending)
|
||||
.take(MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER as usize)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
Ok(services)
|
||||
}
|
||||
|
||||
pub fn load_nym_address(
|
||||
store: &dyn Storage,
|
||||
nym_address: NymAddress,
|
||||
) -> Result<Vec<(ServiceId, Service)>> {
|
||||
pub fn load_nym_address(store: &dyn Storage, nym_address: NymAddress) -> Result<Vec<Service>> {
|
||||
let services = services()
|
||||
.idx
|
||||
.nym_address
|
||||
.prefix(nym_address.to_string())
|
||||
.range(store, None, None, Order::Ascending)
|
||||
.take(MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS as usize)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
Ok(services)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PagedLoad {
|
||||
pub services: Vec<(ServiceId, Service)>,
|
||||
pub services: Vec<Service>,
|
||||
pub limit: usize,
|
||||
pub start_next_after: Option<ServiceId>,
|
||||
}
|
||||
@@ -106,9 +105,10 @@ pub fn load_all_paged(
|
||||
let services = services()
|
||||
.range(store, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<Service>>>()?;
|
||||
|
||||
let start_next_after = services.last().map(|service| service.0);
|
||||
let start_next_after = services.last().map(|service| service.service_id);
|
||||
|
||||
Ok(PagedLoad {
|
||||
services,
|
||||
@@ -119,122 +119,128 @@ pub fn load_all_paged(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use cosmwasm_std::{
|
||||
testing::{MockApi, MockQuerier},
|
||||
MemoryStorage, OwnedDeps,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::{
|
||||
error::ContractError,
|
||||
test_helpers::{
|
||||
fixture::{service_fixture, service_fixture_with_address},
|
||||
helpers::instantiate_test_contract,
|
||||
transactions::instantiate_test_contract,
|
||||
},
|
||||
SpContractError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn save_works() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
|
||||
|
||||
#[rstest::fixture]
|
||||
fn deps() -> TestDeps {
|
||||
instantiate_test_contract()
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn save_works(mut deps: TestDeps) {
|
||||
assert!(!has_service(&deps.storage, 1));
|
||||
save(deps.as_mut().storage, &service_fixture()).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture(1)).unwrap();
|
||||
assert!(has_service(&deps.storage, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_and_check_incorrect_id_fails() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
#[rstest]
|
||||
fn save_and_check_incorrect_id_fails(mut deps: TestDeps) {
|
||||
assert!(!has_service(&deps.storage, 2));
|
||||
save(deps.as_mut().storage, &service_fixture()).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture(1)).unwrap();
|
||||
assert!(!has_service(&deps.storage, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_works() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
|
||||
#[rstest]
|
||||
fn remove_works(mut deps: TestDeps) {
|
||||
let id = 1;
|
||||
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
|
||||
assert!(has_service(&deps.storage, id));
|
||||
remove(deps.as_mut().storage, id).unwrap();
|
||||
assert!(!has_service(&deps.storage, id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_by_id_works() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
|
||||
#[rstest]
|
||||
fn load_by_id_works(mut deps: TestDeps) {
|
||||
let id = 1;
|
||||
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
|
||||
let service = load_id(deps.as_ref().storage, id).unwrap();
|
||||
assert_eq!(service, service_fixture());
|
||||
assert_eq!(service, service_fixture(id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_by_wrong_id_returns_not_found() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
|
||||
#[rstest]
|
||||
fn load_by_wrong_id_returns_not_found(mut deps: TestDeps) {
|
||||
let id = 1;
|
||||
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
|
||||
assert_eq!(
|
||||
load_id(deps.as_ref().storage, id + 1).unwrap_err(),
|
||||
ContractError::NotFound { service_id: id + 1 }
|
||||
SpContractError::NotFound { service_id: id + 1 }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_by_announcer_works() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
|
||||
#[rstest]
|
||||
fn load_by_announcer_works(mut deps: TestDeps) {
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
|
||||
assert_eq!(
|
||||
load_announcer(&deps.storage, Addr::unchecked("steve")).unwrap(),
|
||||
vec![
|
||||
(1, service_fixture_with_address("a")),
|
||||
(2, service_fixture_with_address("b")),
|
||||
(3, service_fixture_with_address("c")),
|
||||
service_fixture_with_address(1, "a"),
|
||||
service_fixture_with_address(2, "b"),
|
||||
service_fixture_with_address(3, "c"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_by_wrong_announcer_returns_empty() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
|
||||
#[rstest]
|
||||
fn load_by_wrong_announcer_returns_empty(mut deps: TestDeps) {
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
|
||||
assert_eq!(
|
||||
load_announcer(&deps.storage, Addr::unchecked("timmy")).unwrap(),
|
||||
vec![]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_by_nym_address_works() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
|
||||
#[rstest]
|
||||
fn load_by_nym_address_works(mut deps: TestDeps) {
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
|
||||
assert_eq!(
|
||||
load_nym_address(&deps.storage, NymAddress::new("b")).unwrap(),
|
||||
vec![(2, service_fixture_with_address("b"))]
|
||||
vec![service_fixture_with_address(2, "b")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_by_wrong_nym_address_returns_empty() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
|
||||
#[rstest]
|
||||
fn load_by_wrong_nym_address_returns_empty(mut deps: TestDeps) {
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
|
||||
assert_eq!(
|
||||
load_nym_address(&deps.storage, NymAddress::new("d")).unwrap(),
|
||||
vec![]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_all_paged_with_no_limit_works() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
|
||||
#[rstest]
|
||||
fn load_all_paged_with_no_limit_works(mut deps: TestDeps) {
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
|
||||
assert_eq!(
|
||||
load_all_paged(&deps.storage, None, None).unwrap(),
|
||||
PagedLoad {
|
||||
services: vec![
|
||||
(1, service_fixture_with_address("a")),
|
||||
(2, service_fixture_with_address("b"))
|
||||
service_fixture_with_address(1, "a"),
|
||||
service_fixture_with_address(2, "b")
|
||||
],
|
||||
start_next_after: Some(2),
|
||||
limit: 100,
|
||||
@@ -242,20 +248,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_all_paged_with_limit_works() {
|
||||
let mut deps = instantiate_test_contract();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("d")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address("e")).unwrap();
|
||||
#[rstest]
|
||||
fn load_all_paged_with_limit_works(mut deps: TestDeps) {
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(4, "d")).unwrap();
|
||||
save(deps.as_mut().storage, &service_fixture_with_address(5, "e")).unwrap();
|
||||
assert_eq!(
|
||||
load_all_paged(&deps.storage, Some(2), None).unwrap(),
|
||||
PagedLoad {
|
||||
services: vec![
|
||||
(1, service_fixture_with_address("a")),
|
||||
(2, service_fixture_with_address("b"))
|
||||
service_fixture_with_address(1, "a"),
|
||||
service_fixture_with_address(2, "b")
|
||||
],
|
||||
limit: 2,
|
||||
start_next_after: Some(2),
|
||||
@@ -265,8 +270,8 @@ mod tests {
|
||||
load_all_paged(&deps.storage, Some(2), Some(2)).unwrap(),
|
||||
PagedLoad {
|
||||
services: vec![
|
||||
(3, service_fixture_with_address("c")),
|
||||
(4, service_fixture_with_address("d"))
|
||||
service_fixture_with_address(3, "c"),
|
||||
service_fixture_with_address(4, "d")
|
||||
],
|
||||
limit: 2,
|
||||
start_next_after: Some(4),
|
||||
@@ -275,7 +280,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
load_all_paged(&deps.storage, Some(2), Some(4)).unwrap(),
|
||||
PagedLoad {
|
||||
services: vec![(5, service_fixture_with_address("e")),],
|
||||
services: vec![service_fixture_with_address(5, "e")],
|
||||
start_next_after: Some(5),
|
||||
limit: 2,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use cosmwasm_std::{from_binary, testing::mock_env, Addr, Coin, Deps};
|
||||
use nym_contracts_common::signing::Nonce;
|
||||
use nym_service_provider_directory_common::{
|
||||
msg::QueryMsg,
|
||||
response::{ConfigResponse, PagedServicesListResponse},
|
||||
ServiceId, ServiceInfo,
|
||||
Service, ServiceId,
|
||||
};
|
||||
|
||||
use crate::{constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT, error::ContractError};
|
||||
use crate::{constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT, SpContractError};
|
||||
|
||||
pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
|
||||
crate::state::assert_admin(deps, admin).unwrap();
|
||||
@@ -14,7 +15,7 @@ pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
|
||||
assert_eq!(config, ConfigResponse { deposit_required });
|
||||
}
|
||||
|
||||
pub fn assert_services(deps: Deps, expected_services: &[ServiceInfo]) {
|
||||
pub fn assert_services(deps: Deps, expected_services: &[Service]) {
|
||||
let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap();
|
||||
let services: PagedServicesListResponse = from_binary(&res).unwrap();
|
||||
let start_next_after = expected_services.iter().last().map(|s| s.service_id);
|
||||
@@ -28,7 +29,7 @@ pub fn assert_services(deps: Deps, expected_services: &[ServiceInfo]) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn assert_service(deps: Deps, expected_service: &ServiceInfo) {
|
||||
pub fn assert_service(deps: Deps, expected_service: &Service) {
|
||||
let res = crate::contract::query(
|
||||
deps,
|
||||
mock_env(),
|
||||
@@ -37,7 +38,7 @@ pub fn assert_service(deps: Deps, expected_service: &ServiceInfo) {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let services: ServiceInfo = from_binary(&res).unwrap();
|
||||
let services: Service = from_binary(&res).unwrap();
|
||||
assert_eq!(&services, expected_service);
|
||||
}
|
||||
|
||||
@@ -58,8 +59,21 @@ pub fn assert_not_found(deps: Deps, expected_id: ServiceId) {
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
res,
|
||||
ContractError::NotFound {
|
||||
SpContractError::NotFound {
|
||||
service_id: _expected_id
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
pub fn assert_current_nonce(deps: Deps, address: &Addr, expected_nonce: Nonce) {
|
||||
let res = crate::contract::query(
|
||||
deps,
|
||||
mock_env(),
|
||||
QueryMsg::SigningNonce {
|
||||
address: address.to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let nonce: Nonce = from_binary(&res).unwrap();
|
||||
assert_eq!(nonce, expected_nonce);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,87 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut};
|
||||
use nym_contracts_common::{signing::MessageSignature, IdentityKeyRef};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_service_provider_directory_common::{
|
||||
NymAddress, Service, ServiceId, ServiceInfo, ServiceType,
|
||||
NymAddress, Service, ServiceDetails, ServiceId, ServiceType,
|
||||
};
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use super::{
|
||||
helpers::nyms,
|
||||
signing::{ed25519_sign_message, service_provider_announce_sign_payload},
|
||||
};
|
||||
|
||||
use super::helpers::nyms;
|
||||
|
||||
pub fn service_fixture() -> Service {
|
||||
Service {
|
||||
nym_address: NymAddress::new("nym"),
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
announcer: Addr::unchecked("steve"),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service_fixture_with_address(nym_address: &str) -> Service {
|
||||
Service {
|
||||
nym_address: NymAddress::new(nym_address),
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
announcer: Addr::unchecked("steve"),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service_info(
|
||||
pub fn new_service(
|
||||
service_id: ServiceId,
|
||||
nym_address: NymAddress,
|
||||
announcer: Addr,
|
||||
) -> ServiceInfo {
|
||||
ServiceInfo {
|
||||
nym_address: &NymAddress,
|
||||
announcer: &Addr,
|
||||
identity_key: IdentityKeyRef,
|
||||
) -> Service {
|
||||
Service {
|
||||
service_id,
|
||||
service: Service {
|
||||
nym_address,
|
||||
service: ServiceDetails {
|
||||
nym_address: nym_address.clone(),
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
announcer,
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
identity_key: identity_key.to_string(),
|
||||
},
|
||||
announcer: announcer.clone(),
|
||||
block_height: 12345,
|
||||
deposit: nyms(100),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service_fixture(service_id: ServiceId) -> Service {
|
||||
new_service(
|
||||
service_id,
|
||||
&NymAddress::new("nym"),
|
||||
&Addr::unchecked("steve"),
|
||||
"identity",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn service_fixture_with_address(service_id: ServiceId, nym_address: &str) -> Service {
|
||||
new_service(
|
||||
service_id,
|
||||
&NymAddress::new(nym_address),
|
||||
&Addr::unchecked("steve"),
|
||||
"identity",
|
||||
)
|
||||
}
|
||||
|
||||
// Create a new service, using a correctly generted identity key
|
||||
pub fn new_service_details<R>(rng: &mut R, nym_address: &str) -> (ServiceDetails, identity::KeyPair)
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let keypair = identity::KeyPair::new(rng);
|
||||
(
|
||||
ServiceDetails {
|
||||
nym_address: NymAddress::new(nym_address),
|
||||
service_type: ServiceType::NetworkRequester,
|
||||
identity_key: keypair.public_key().to_base58_string(),
|
||||
},
|
||||
keypair,
|
||||
)
|
||||
}
|
||||
|
||||
// Create a new service, with a correctly generated identity key, and sign it
|
||||
pub fn new_service_details_with_sign<R>(
|
||||
deps: DepsMut<'_>,
|
||||
rng: &mut R,
|
||||
nym_address: &str,
|
||||
announcer: &str,
|
||||
deposit: Coin,
|
||||
) -> (ServiceDetails, MessageSignature)
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
// Service
|
||||
let (service, keypair) = new_service_details(rng, nym_address);
|
||||
|
||||
// Sign
|
||||
let sign_msg =
|
||||
service_provider_announce_sign_payload(deps.as_ref(), announcer, service.clone(), deposit);
|
||||
let owner_signature = ed25519_sign_message(sign_msg, keypair.private_key());
|
||||
|
||||
(service, owner_signature)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
use cosmwasm_std::{
|
||||
coin, coins,
|
||||
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
|
||||
Coin, DepsMut, Event, MemoryStorage, OwnedDeps, Response,
|
||||
};
|
||||
use cosmwasm_std::{Coin, Event, Response};
|
||||
use cw_multi_test::AppResponse;
|
||||
use nym_service_provider_directory_common::{
|
||||
events::{ServiceProviderEventType, SERVICE_ID},
|
||||
msg::{ExecuteMsg, InstantiateMsg},
|
||||
Service, ServiceId,
|
||||
};
|
||||
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
|
||||
|
||||
pub fn nyms(amount: u64) -> Coin {
|
||||
Coin::new(amount.into(), "unym")
|
||||
}
|
||||
|
||||
pub fn test_rng() -> ChaCha20Rng {
|
||||
let dummy_seed = [42u8; 32];
|
||||
ChaCha20Rng::from_seed(dummy_seed)
|
||||
}
|
||||
|
||||
pub fn get_event_types(response: &Response, event_type: &str) -> Vec<Event> {
|
||||
response
|
||||
.events
|
||||
@@ -55,50 +52,3 @@ pub fn get_app_attribute(response: &AppResponse, event_type: &str, key: &str) ->
|
||||
.value
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_app_attributes(response: &AppResponse, event_type: &str, key: &str) -> Vec<String> {
|
||||
get_app_event_types(response, event_type)
|
||||
.iter()
|
||||
.map(|ev| {
|
||||
ev.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == key)
|
||||
.unwrap()
|
||||
.value
|
||||
.clone()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn instantiate_test_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {
|
||||
deposit_required: coin(100, "unym"),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap();
|
||||
assert_eq!(res.messages.len(), 0);
|
||||
deps
|
||||
}
|
||||
|
||||
pub fn announce_service(deps: DepsMut<'_>, service: &Service) -> ServiceId {
|
||||
let msg: ExecuteMsg = service.clone().into();
|
||||
let info = mock_info(service.announcer.as_str(), &coins(100, "unym"));
|
||||
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
|
||||
let service_id: ServiceId = get_attribute(
|
||||
&res,
|
||||
&ServiceProviderEventType::Announce.to_string(),
|
||||
SERVICE_ID,
|
||||
)
|
||||
.parse()
|
||||
.unwrap();
|
||||
service_id
|
||||
}
|
||||
|
||||
pub fn delete_service(deps: DepsMut<'_>, service_id: ServiceId, announcer: &str) {
|
||||
let msg = ExecuteMsg::DeleteId { service_id };
|
||||
let info = mock_info(announcer, &[]);
|
||||
crate::execute(deps, mock_env(), info, msg).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod assert;
|
||||
pub mod fixture;
|
||||
pub mod helpers;
|
||||
pub mod test_setup;
|
||||
pub mod signing;
|
||||
pub mod transactions;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use cosmwasm_std::{Addr, Coin, Deps};
|
||||
use nym_contracts_common::signing::{
|
||||
MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose,
|
||||
};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_service_provider_directory_common::{
|
||||
signing_types::{
|
||||
construct_service_provider_announce_sign_payload, SignableServiceProviderAnnounceMsg,
|
||||
},
|
||||
ServiceDetails,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::state;
|
||||
|
||||
pub fn service_provider_announce_sign_payload(
|
||||
deps: Deps<'_>,
|
||||
owner: &str,
|
||||
service: ServiceDetails,
|
||||
deposit: Coin,
|
||||
) -> SignableServiceProviderAnnounceMsg {
|
||||
let owner = Addr::unchecked(owner);
|
||||
let nonce = state::get_signing_nonce(deps.storage, owner.clone()).unwrap();
|
||||
construct_service_provider_announce_sign_payload(nonce, owner, deposit, service)
|
||||
}
|
||||
|
||||
pub fn ed25519_sign_message<T: Serialize + SigningPurpose>(
|
||||
message: SignableMessage<T>,
|
||||
private_key: &identity::PrivateKey,
|
||||
) -> MessageSignature {
|
||||
match message.algorithm {
|
||||
SigningAlgorithm::Ed25519 => {
|
||||
let plaintext = message.to_plaintext().unwrap();
|
||||
let signature = private_key.sign(&plaintext);
|
||||
MessageSignature::from(signature.to_bytes().as_ref())
|
||||
}
|
||||
SigningAlgorithm::Secp256k1 => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use cosmwasm_std::{
|
||||
coin,
|
||||
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
|
||||
DepsMut, MemoryStorage, OwnedDeps,
|
||||
};
|
||||
|
||||
use nym_service_provider_directory_common::{
|
||||
events::{ServiceProviderEventType, SERVICE_ID},
|
||||
msg::{ExecuteMsg, InstantiateMsg},
|
||||
ServiceDetails, ServiceId,
|
||||
};
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use super::helpers::{get_attribute, nyms};
|
||||
|
||||
pub fn instantiate_test_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {
|
||||
deposit_required: coin(100, "unym"),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap();
|
||||
assert_eq!(res.messages.len(), 0);
|
||||
deps
|
||||
}
|
||||
|
||||
pub fn announce_service<R>(
|
||||
mut deps: DepsMut<'_>,
|
||||
rng: &mut R,
|
||||
nym_address: &str,
|
||||
announcer: &str,
|
||||
) -> (ServiceId, ServiceDetails)
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let deposit = nyms(100);
|
||||
let (service, owner_signature) = super::fixture::new_service_details_with_sign(
|
||||
deps.branch(),
|
||||
rng,
|
||||
nym_address,
|
||||
announcer,
|
||||
deposit.clone(),
|
||||
);
|
||||
|
||||
// Announce
|
||||
let msg = ExecuteMsg::Announce {
|
||||
service: service.clone(),
|
||||
owner_signature,
|
||||
};
|
||||
let info = mock_info(announcer, &[deposit]);
|
||||
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
|
||||
|
||||
let service_id: ServiceId = get_attribute(
|
||||
&res,
|
||||
&ServiceProviderEventType::Announce.to_string(),
|
||||
SERVICE_ID,
|
||||
)
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
(service_id, service)
|
||||
}
|
||||
|
||||
pub fn delete_service(deps: DepsMut<'_>, service_id: ServiceId, announcer: &str) {
|
||||
let msg = ExecuteMsg::DeleteId { service_id };
|
||||
let info = mock_info(announcer, &[]);
|
||||
crate::execute(deps, mock_env(), info, msg).unwrap();
|
||||
}
|
||||
@@ -20,9 +20,9 @@ name = "vesting_contract"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.4.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.6.0" }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.5.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" }
|
||||
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-derive = { workspace = true }
|
||||
|
||||
@@ -49,10 +49,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
[preprocessor.variables.variables]
|
||||
# code prerequisites versions
|
||||
minimum_rust_version = "1.66"
|
||||
platform_release_version = "v1.1.19"
|
||||
upcoming_platform_release_version = "v1.1.20" # to use when adding 'edit on github' plugin
|
||||
mix_node_release_version = "v1.1.20"
|
||||
#
|
||||
# TODO remove this in place of develop in next release
|
||||
platform_release_version = "v1.1.21"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
renderer = ["html"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[book]
|
||||
title = "Nym Docs v1.1.19"
|
||||
title = "Nym Docs"
|
||||
authors = ["Max Hampshire"]
|
||||
description = "Nym technical documentation"
|
||||
language = "en"
|
||||
@@ -48,9 +48,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
# https://gitlab.com/tglman/mdbook-variables/
|
||||
[preprocessor.variables.variables]
|
||||
minimum_rust_version = "1.66"
|
||||
platform_release_version = "v1.1.19"
|
||||
upcoming_platform_release_version = "v1.1.20" # to use in 'edit page on github' plugin (coming soon)
|
||||
mix_node_release_version = "v1.1.20"
|
||||
|
||||
# old variables - this is still needed for links.. TODO change to develop?
|
||||
platform_release_version = "v1.1.21"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
> The Nym socks5 client was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code on this page, go there first.
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-socks5-client --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
## What is this client for?
|
||||
|
||||
Many existing applications are able to use either the SOCKS4, SOCKS4A, or SOCKS5 proxy protocols. If you want to send such an application's traffic through the mixnet, you can use the `nym-socks5-client` to bounce network traffic through the Nym network, like this:
|
||||
|
||||
```
|
||||
@@ -59,6 +66,7 @@ The `nym-socks5-client` allows you to do the following from your local machine:
|
||||
The `nym-network-requester` then reassembles the original TCP stream using the packets' sequence numbers, and make the intended request. It will then chop up the response into Sphinx packets and send them back through the mixnet to your `nym-socks5-client`. The application will then receive its data, without even noticing that it wasn't talking to a "normal" SOCKS5 proxy!
|
||||
|
||||
## Client setup
|
||||
|
||||
### Viewing command help
|
||||
|
||||
You can check that your binaries are properly compiled with:
|
||||
@@ -80,6 +88,7 @@ You can check the necessary parameters for the available commands by running:
|
||||
```
|
||||
|
||||
### Initialising a new client instance
|
||||
|
||||
Before you can use the client, you need to initalise a new instance of it, which can be done with the following command:
|
||||
|
||||
```
|
||||
@@ -99,6 +108,7 @@ The `--provider` field needs to be filled with the Nym address of a Network Requ
|
||||
Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester-setup.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios.
|
||||
|
||||
#### Choosing a Gateway
|
||||
|
||||
By default - as in the example above - your client will choose a random gateway to connect to.
|
||||
|
||||
However, there are several options for choosing a gateway, if you do not want one that is randomly assigned to your client:
|
||||
@@ -111,6 +121,7 @@ However, there are several options for choosing a gateway, if you do not want on
|
||||
> Note this doesn't mean that your client will pick the closest gateway to you, but it will be far more likely to connect to gateway with a 20ms ping rather than 200ms
|
||||
|
||||
### Configuring your client
|
||||
|
||||
When you initalise a client instance, a configuration directory will be generated and stored in `$HOME_DIR/.nym/socks5-clients/<client-name>/`.
|
||||
|
||||
```
|
||||
@@ -134,14 +145,13 @@ The `config.toml` file contains client configuration options, while the two `pem
|
||||
The generated files contain the client name, public/private keypairs, and gateway address. The name `<client_id>` in the example above is just a local identifier so that you can name your clients.
|
||||
|
||||
#### Configuring your client for Docker
|
||||
|
||||
By default, the native client listens to host `127.0.0.1`. However this can be an issue if you wish to run a client in a Dockerized environment, where it can be convenenient to listen on a different host such as `0.0.0.0`.
|
||||
|
||||
You can set this via the `--host` flag during either the `init` or `run` commands.
|
||||
|
||||
Alternatively, a custom host can be set in the `config.toml` file under the `socket` section. If you do this, remember to restart your client process.
|
||||
|
||||
|
||||
|
||||
### Running the socks5 client
|
||||
|
||||
You can run the initalised client by doing this:
|
||||
@@ -150,6 +160,38 @@ You can run the initalised client by doing this:
|
||||
./nym-socks5-client run --id docs-example
|
||||
```
|
||||
|
||||
## Automating your socks5 client with systemd
|
||||
|
||||
Stop the running process with `CTRL-C`, and create a service file for the socks5 client as we did with our client instance previously at `/etc/systemd/system/nym-socks5-client.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Nym Socks5 Client ({{platform_release_version}})
|
||||
StartLimitInterval=350
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
User=nym # replace this with whatever user you wish
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/home/nym/nym-socks5-client run --id <your_id>
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Now enable and start your socks5 client:
|
||||
|
||||
```
|
||||
systemctl enable nym-socks5-client.service
|
||||
systemctl start nym-socks5-client.service
|
||||
|
||||
# you can always check your socks5 client has succesfully started with:
|
||||
systemctl status nym-socks5-client.service
|
||||
```
|
||||
|
||||
## Using your Socks5 Client
|
||||
|
||||
After completing the steps above, your local Socks5 Client will be listening on `localhost:1080` ready to proxy traffic to the Network Requester set as the `--provider` when initialising.
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
> The Nym Websocket Client was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code on this page, go there first.
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-client --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
## Client setup
|
||||
### Viewing command help
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first.
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-gateway --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
|
||||
## Preliminary steps
|
||||
There are a couple of steps that need completing before starting to set up your gateway:
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
> The Nym mix node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first.
|
||||
|
||||
```admonish info
|
||||
The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases.
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-mixnode --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases.
|
||||
|
||||
## Preliminary steps
|
||||
|
||||
@@ -228,10 +231,10 @@ sudo ufw enable
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
Finally open your mix node's p2p port, as well as ports for ssh, http, and https connections, and ports `8000` and `1790` for verloc and measurement pings:
|
||||
Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings:
|
||||
|
||||
```
|
||||
sudo ufw allow 1789,1790,8000,22,80,443/tcp
|
||||
sudo ufw allow 1789,1790,8000,22/tcp
|
||||
# check the status of the firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
> The Nym network requester was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code on this page, go there first.
|
||||
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-network-requester --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
|
||||
## Network Requester Whitelist
|
||||
If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers.
|
||||
|
||||
> As of `v1.1.10`, the network requester longer requires a separate nym client instance for it to function, as it has a client embedded within the binary running as a single process.
|
||||
>
|
||||
## Network Requester Whitelist
|
||||
The network requester is **not** an open proxy. It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester/<network-requester-id>/`) as a whitelist for outbound requests.
|
||||
By default the network requester is **not** an open proxy (although it can be used as one). It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester/<network-requester-id>/`) as a whitelist for outbound requests.
|
||||
|
||||
Any request to a URL which is not on this list will be blocked.
|
||||
|
||||
@@ -118,6 +123,7 @@ In the previous version of the network-requester, users were required to run a n
|
||||
If you are running an existing network requester registered with nym-connect, upgrading requires you move your old keys over to the new network requester configuration. We suggest following these instructions carefully to ensure a smooth transition.
|
||||
|
||||
Initiate the new network requester:
|
||||
|
||||
```
|
||||
nym-network-requester init --id mynetworkrequester
|
||||
```
|
||||
@@ -190,7 +196,7 @@ sudo ufw status
|
||||
Finally open your requester's p2p port, as well as ports for ssh and incoming traffic connections:
|
||||
|
||||
```
|
||||
sudo ufw allow 1789,22,9000/tcp
|
||||
sudo ufw allow 22,9000/tcp
|
||||
# check the status of the firewall
|
||||
sudo ufw status
|
||||
```
|
||||
@@ -214,7 +220,7 @@ ls $HOME/.nym/service-providers/network-requester/
|
||||
# returns: allowed.list unknown.list
|
||||
```
|
||||
|
||||
We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](../clients/socsk5-client.md) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to restart your network requester!
|
||||
We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](../clients/socks5-client.md) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to restart your network requester!
|
||||
|
||||
> If you are adding custom domains, please note that whilst they may appear in the logs of your network-requester as something like `api-0.core.keybaseapi.com:443`, you **only need** to include the main domain name, in this instance `keybaseapi.com`
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ go version go1.20.4 linux/amd64
|
||||
You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases).
|
||||
|
||||
```admonish caution title=""
|
||||
Binaries for both Mainnet and Sandbox testnet can be found in each release - make sure to download the correct binary to avoid `bech32Prefix` mismatches.
|
||||
There are seperate releases for Mainnet and the Sandbox testnet - make sure to download the correct binary to avoid `bech32Prefix` mismatches.
|
||||
```
|
||||
|
||||
### Manually compiling your validator binary
|
||||
@@ -80,17 +80,27 @@ You should see help text print out.
|
||||
The `nyxd` binary and the `libwasmvm.so` shared object library binary have been compiled. `libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts.
|
||||
|
||||
```admonish caution title=""
|
||||
If you have compiled these files locally you need to upload both of them to the server on which the validator will run. **If you have instead compiled them on the server skip to the step outlining setting `LD_LIBRARY PATH` below.**
|
||||
If you have compiled these files locally and need to upload both of them to the server on which the validator will run, **or** downloaded a pre-compiled binary from Github, you need to locate and link these files in slightly different ways, outlined below. If you have instead compiled them on the server skip to the step outlining setting `LD_LIBRARY PATH` below.
|
||||
```
|
||||
|
||||
To locate these files on your local system run:
|
||||
To locate these files on your system **if you downloaded a pre-compiled binary from Github** run:
|
||||
|
||||
```
|
||||
WASMVM_SO=$(ldd path/to/nyxd/binary | grep libwasmvm.so | awk '{ print $3 }')
|
||||
ls ${WASMVM_SO}
|
||||
```
|
||||
|
||||
e.g. if you downloaded your `nyxd` binary to `/root` then you would replace `ldd path/to/nyxd/binary` with `ldd nyxd` in the above command.
|
||||
|
||||
|
||||
To locate these files on your system **if you uploaded your validator after compiling it on a local machine** run:
|
||||
|
||||
```
|
||||
WASMVM_SO=$(ldd build/nyxd | grep libwasmvm.so | awk '{ print $3 }')
|
||||
ls ${WASMVM_SO}
|
||||
```
|
||||
|
||||
This will output something like:
|
||||
The above commands will output something like:
|
||||
|
||||
```
|
||||
'/home/username/go/pkg/mod/github.com/!cosm!wasm/wasmvm@v0.13.0/api/libwasmvm.so'
|
||||
@@ -158,7 +168,7 @@ You can use the following command to download them for the correct network:
|
||||
wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json
|
||||
|
||||
# Sandbox testnet
|
||||
curl "https://sandbox-validator1.nymtech.net/genesis" | jq .result.genesis > ~/.nyxd/config/genesis.json
|
||||
wget -O $HOME/.nyxd/config/genesis.json https://sandbox-validator1.nymtech.net/snapshots/genesis.json
|
||||
```
|
||||
|
||||
### `config.toml` configuration
|
||||
@@ -181,7 +191,7 @@ laddr = "tcp://0.0.0.0:26656"
|
||||
```
|
||||
|
||||
These affect the following:
|
||||
* `persistent_peers = "<PEER_ADDRESS>@<DOMAIN>.nymtech.net:26656"` allows your validator to start pulling blocks from other validators
|
||||
* `persistent_peers = "<PEER_ADDRESS>@<DOMAIN>.nymtech.net:26666"` allows your validator to start pulling blocks from other validators. **The main sandbox validator listens on `26666` instead of the default `26656` for debugging**. It is recommended you do not change your port from `26656`.
|
||||
* `create_empty_blocks = false` will save space
|
||||
* `laddr = "tcp://0.0.0.0:26656"` is in your p2p configuration options
|
||||
|
||||
@@ -230,6 +240,11 @@ nyxd keys show nyxd-admin -a
|
||||
Type in your keychain **password**, not the mnemonic, when asked.
|
||||
|
||||
## Starting your validator
|
||||
|
||||
```admonish caution title=""
|
||||
If you are running a Sandbox testnet validator, please skip the `validate-genesis` command: it will fail due to the size of the genesis file as this is a fork of an existing chain state.
|
||||
```
|
||||
|
||||
Everything should now be ready to go. You've got the validator set up, all changes made in `config.toml` and `app.toml`, the Nym genesis file copied into place (replacing the initial auto-generated one). Now let's validate the whole setup:
|
||||
|
||||
```
|
||||
@@ -269,16 +284,37 @@ nyxd start
|
||||
|
||||
Once your validator starts, it will start requesting blocks from other validators. This may take several hours. Once it's up to date, you can issue a request to join the validator set with the command below.
|
||||
|
||||
If you wish to sync from a snapshot or via state-sync please check out the Polkachu [mainnet](https://polkachu.com/networks/nym) or [testnet](https://polkachu.com/testnets/nym/) resources.
|
||||
### Syncing from a snapshot
|
||||
If you wish to sync from a snapshot on **mainnet** use Polkachu's [mainnet](https://polkachu.com/networks/nym) resources.
|
||||
|
||||
> If you are having trouble upgrading your validator binary, try replacing (or re-compile) the `libwasmvm.so` file and replace it on your validator server.
|
||||
If you wish to sync from a snapshot on **Sandbox testnet** use the below commands, which are a modified version of Polkachu's excellent resources. These commands assume you are running an OS with `apt` as the package manager:
|
||||
|
||||
```
|
||||
# install lz4 if necessary
|
||||
sudo apt install snapd -y
|
||||
sudo snap install lz4
|
||||
|
||||
# download the snapshot
|
||||
wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://sandbox-validator1.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4
|
||||
|
||||
# reset your validator state
|
||||
nyxd tendermint unsafe-reset-all
|
||||
|
||||
# unpack the snapshot
|
||||
lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd
|
||||
```
|
||||
|
||||
You can then restart `nyxd` - it should start syncing from a block > 2000000.
|
||||
|
||||
### Joining Consensus
|
||||
```admonish caution title=""
|
||||
When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens.
|
||||
|
||||
Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time.
|
||||
```
|
||||
|
||||
Once your validator has synced and you have received tokens, you can join consensus and produce blocks.
|
||||
|
||||
```
|
||||
# Mainnet
|
||||
nyxd tx staking create-validator
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Nym-CLI
|
||||
|
||||
## What is this tool for?
|
||||
This is a CLI tool for interacting with:
|
||||
|
||||
* the Nyx blockchain (account management, querying the chain state, etc)
|
||||
@@ -7,20 +8,20 @@ This is a CLI tool for interacting with:
|
||||
|
||||
It provides a convenient wrapper around the `nymd` client, and has similar functionality to the `nyxd` binary for querying the chain or executing smart contract methods.
|
||||
|
||||
## Building
|
||||
The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory.
|
||||
## Building
|
||||
The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory.
|
||||
|
||||
### Useage
|
||||
You can see all available commands with:
|
||||
### Useage
|
||||
You can see all available commands with:
|
||||
|
||||
```
|
||||
./nym-cli --help
|
||||
./nym-cli --help
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
|
||||
nym-cli
|
||||
nym-cli
|
||||
A client for interacting with Nym smart contracts and the Nyx blockchain
|
||||
|
||||
USAGE:
|
||||
@@ -67,13 +68,13 @@ subcommands:
|
||||
```
|
||||
~~~
|
||||
|
||||
## Example Usage
|
||||
Below we have listed some example commands for some of the features listed above.
|
||||
## Example Usage
|
||||
Below we have listed some example commands for some of the features listed above.
|
||||
|
||||
If ever in doubt what you need to type, or if you want to see alternative parameters for a command, use the `nym-cli <subcommand_name> --help` to view all available options.
|
||||
|
||||
```
|
||||
./nym-cli account create --help
|
||||
./nym-cli account create --help
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
@@ -120,7 +121,7 @@ OPTIONS:
|
||||
Creates an account with a random Mnemonic and a new address.
|
||||
|
||||
```
|
||||
./nym-cli account create
|
||||
./nym-cli account create
|
||||
|
||||
# Result:
|
||||
# 1. Mnemonic
|
||||
@@ -136,7 +137,7 @@ Queries the existing balance of an account.
|
||||
|
||||
```
|
||||
# Using adddress below for example purposes.
|
||||
./nym-cli account balance n1hzn28p2c6pzr98r85jp3h53fy8mju5w7ndd5vh
|
||||
./nym-cli account balance n1hzn28p2c6pzr98r85jp3h53fy8mju5w7ndd5vh
|
||||
|
||||
# Result:
|
||||
2022-11-10T10:28:54.009Z INFO nym_cli_commands::validator::account::balance > Getting balance for n1hzn28p2c6pzr98r85jp3h53fy8mju5w7ndd5vh...
|
||||
@@ -149,7 +150,7 @@ Queries the existing balance of an account.
|
||||
You can also query an accounts balance by using its mnemonic:
|
||||
|
||||
```
|
||||
./nym-cli account balance --mnemonic <mnemonic>
|
||||
./nym-cli account balance --mnemonic <mnemonic>
|
||||
```
|
||||
|
||||
### Send tokens to an account
|
||||
@@ -167,7 +168,7 @@ Queries the specified blockchain (Nyx chain by default) for the current block he
|
||||
```
|
||||
./nym-cli block current-height --mnemonic <mnemonic>
|
||||
|
||||
# Result:
|
||||
# Result:
|
||||
Current block height:
|
||||
<BLOCK_HEIGHT>
|
||||
```
|
||||
@@ -183,50 +184,50 @@ Query a mix node on the mixnet.
|
||||
|
||||
### Bond a mix node
|
||||
|
||||
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
|
||||
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
|
||||
|
||||
* generate a signature payload:
|
||||
* generate a signature payload:
|
||||
```
|
||||
./nym-cli mixnet operators mixnode create-mixnode-bonding-sign-payload
|
||||
./nym-cli mixnet operators mixnode create-mixnode-bonding-sign-payload
|
||||
|
||||
# returns something like
|
||||
# returns something like
|
||||
97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
|
||||
```
|
||||
|
||||
* sign this payload:
|
||||
* sign this payload:
|
||||
```
|
||||
./nym-mixnode sign --id upgrade_test --contract-msg 97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
|
||||
```
|
||||
|
||||
* bond the node using the signature:
|
||||
* bond the node using the signature:
|
||||
```
|
||||
./nym-cli --mnemonic <mnemonic> mixnet operators mixnode bond --amount 100000000 --mix-port 1789 --version "1.1.13" --host "85.163.111.99" --identity-key "B6pWscxYb8sPAdKTci8zPy5AgMzn5Zx8KpWwQNCyUSU7" --location "nym-town" --sphinx-key "o6MmKHzRewpNzVwaV37ZX9G3BfK4AmfYvsQfyoyAFRk" --signature "2TujBZfer8r5QM639Yb8coD9xH6f5eXzjAT5dD7wMom9fH8D1u36d7UpPdVaaZrWsCynmYpobwMWqiMKr5kM6CprD"
|
||||
```
|
||||
|
||||
### Bond a gateway
|
||||
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
|
||||
### Bond a gateway
|
||||
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
|
||||
|
||||
* generate a signature payload:
|
||||
* generate a signature payload:
|
||||
```
|
||||
./nym-cli mixnet operators gateway create-gateway-bonding-sign-payload
|
||||
./nym-cli mixnet operators gateway create-gateway-bonding-sign-payload
|
||||
|
||||
# returns something like
|
||||
# returns something like
|
||||
97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
|
||||
```
|
||||
|
||||
* sign this payload:
|
||||
* sign this payload:
|
||||
```
|
||||
./nym-gateway sign --id upgrade_test --contract-msg 97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
|
||||
```
|
||||
|
||||
* bond the node using this signature:
|
||||
* bond the node using this signature:
|
||||
```
|
||||
./nym-cli --mnemonic <mnemonic> mixnet operators gateway bond --amount 100000000 --mix-port 1789 --version "1.1.13" --host "85.163.111.99" --identity-key "B6pWscxYb8sPAdKTci8zPy5AgMzn5Zx8KpWwQNCyUSU7" --location "nym-town" --sphinx-key "o6MmKHzRewpNzVwaV37ZX9G3BfK4AmfYvsQfyoyAFRk" --signature "2TujBZfer8r5QM639Yb8coD9xH6f5eXzjAT5dD7wMom9fH8D1u36d7UpPdVaaZrWsCynmYpobwMWqiMKr5kM6CprD"
|
||||
```
|
||||
|
||||
### Unbond a node
|
||||
|
||||
Unbond a mix node or gateway.
|
||||
Unbond a mix node or gateway.
|
||||
```
|
||||
./nym-cli mixnet operators gateway unbound --mnemonic <mnemonic>
|
||||
```
|
||||
@@ -235,7 +236,7 @@ Unbond a mix node or gateway.
|
||||
|
||||
### Upgrade a mix node
|
||||
|
||||
Upgrade your node config.
|
||||
Upgrade your node config.
|
||||
```
|
||||
./nym-cli mixnet operators mixnode settings update-config --version <new_version>
|
||||
```
|
||||
@@ -298,7 +299,7 @@ Sign a message.
|
||||
|
||||
Verify a signature.
|
||||
```
|
||||
./nym-cli signature verify --mnemonic <mnemonic> <PUBLIC_KEY_OR_ADDRESS> <SIGNATURE_AS_HEX> <MESSAGE>
|
||||
./nym-cli signature verify --mnemonic <mnemonic> <PUBLIC_KEY_OR_ADDRESS> <SIGNATURE_AS_HEX> <MESSAGE>
|
||||
```
|
||||
|
||||
### Create a Vesting Schedule
|
||||
@@ -336,12 +337,3 @@ Query for staking on behlaf of someone else
|
||||
```
|
||||
./nym-cli --mnemonic <staking address mnemonic> mixnet delegators delegate --mix-id <input> --identity-key <input> --amount <input>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+672
@@ -2340,6 +2340,678 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1439px) {
|
||||
:root{
|
||||
--content-max-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
|
||||
.ayu {
|
||||
|
||||
Vendored
+168
@@ -762,6 +762,174 @@
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
<!-- Page table of contents -->
|
||||
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
||||
|
||||
{{{ content }}}
|
||||
</main>
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm2
|
||||
MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1qsn2655eflc0nx2uwqtwyv5kad5dwm4c0gn72yr4q4de5r3jaz2slvqjgt
|
||||
NAME_SERVICE_CONTRACT_ADDRESS=n1cm2u5vfjd3zalfw0p65xyh4tcrw3hjlm0960gzhewga449h4mgas77mjkl
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYXD="https://qwerty-validator.qa.nymte.ch/"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.19"
|
||||
version = "1.1.21"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,26 +1,93 @@
|
||||
use crate::service_providers::models::DirectoryService;
|
||||
use crate::service_providers::models::{
|
||||
DirectoryService, DirectorySpDetailed, HarbourMasterService, PagedResult,
|
||||
};
|
||||
use okapi::openapi3::OpenApi;
|
||||
use reqwest::Error as ReqwestError;
|
||||
use rocket::{serde::json::Json, Route};
|
||||
use reqwest::{Client, Error as ReqwestError};
|
||||
use rocket::{http::Status, serde::json::Json, Route};
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
const SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
"https://nymtech.net/.wellknown/connect/service-providers.json";
|
||||
|
||||
const HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services";
|
||||
const HM_SINCE_MIN: u32 = 120;
|
||||
const HM_SIZE: u8 = 100;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GetSpError {
|
||||
ReqwestError(ReqwestError),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl From<ReqwestError> for GetSpError {
|
||||
fn from(error: ReqwestError) -> Self {
|
||||
GetSpError::ReqwestError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for GetSpError {
|
||||
fn from(error: &str) -> Self {
|
||||
GetSpError::Error(String::from(error))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service_providers_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: get_service_providers]
|
||||
}
|
||||
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>, ReqwestError> {
|
||||
reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
pub async fn get_services() -> Result<Vec<DirectorySpDetailed>, GetSpError> {
|
||||
let reqw = Client::new();
|
||||
|
||||
let services_res = reqw
|
||||
.get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
.send()
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await
|
||||
.await?;
|
||||
|
||||
let directory_sp = services_res
|
||||
.iter()
|
||||
.find(|item| item.id == "all")
|
||||
.ok_or("NymConnect network requesters data not found in response")?;
|
||||
|
||||
let hm_services = reqw
|
||||
.get(format!(
|
||||
"{HARBOUR_MASTER_URL}?since_min={HM_SINCE_MIN}&size={HM_SIZE}"
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.json::<PagedResult<HarbourMasterService>>()
|
||||
.await?;
|
||||
|
||||
let sp_list: Vec<_> = directory_sp
|
||||
.items
|
||||
.iter()
|
||||
.map(|sp| {
|
||||
let directory_sp = hm_services
|
||||
.items
|
||||
.iter()
|
||||
.find(|item| item.service_provider_client_id == sp.address);
|
||||
DirectorySpDetailed {
|
||||
id: sp.id.clone(),
|
||||
description: sp.description.clone(),
|
||||
address: sp.address.clone(),
|
||||
routing_score: directory_sp.map(|sp| sp.routing_score),
|
||||
service_type: "Network requester".into(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(sp_list)
|
||||
}
|
||||
|
||||
#[openapi(tag = "service_providers")]
|
||||
#[get("/")]
|
||||
pub(crate) async fn get_service_providers() -> Json<Vec<DirectoryService>> {
|
||||
let result = get_services().await.unwrap();
|
||||
Json(result)
|
||||
pub(crate) async fn get_service_providers() -> Result<Json<Vec<DirectorySpDetailed>>, Status> {
|
||||
match get_services().await {
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Err(err) => {
|
||||
log::error!("{:?}", err);
|
||||
Err(Status::InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,36 @@ pub struct DirectoryServiceProvider {
|
||||
pub gateway: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DirectorySpDetailed {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub address: String,
|
||||
pub routing_score: Option<f32>,
|
||||
pub service_type: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DirectoryService {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub items: Vec<DirectoryServiceProvider>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct HarbourMasterService {
|
||||
pub service_provider_client_id: String,
|
||||
pub gateway_identity_key: String,
|
||||
pub ip_address: String,
|
||||
pub last_successful_ping_utc: String,
|
||||
pub last_updated_utc: String,
|
||||
pub routing_score: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PagedResult<T> {
|
||||
pub page: u32,
|
||||
pub size: u32,
|
||||
pub total: i32,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
Environment,
|
||||
GatewayBondAnnotated,
|
||||
GatewayBond,
|
||||
DirectoryService,
|
||||
DirectoryServiceProvider,
|
||||
} from '../typeDefs/explorer-api';
|
||||
|
||||
function getFromCache(key: string) {
|
||||
@@ -153,7 +153,7 @@ export class Api {
|
||||
static fetchUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> =>
|
||||
(await fetch(`${UPTIME_STORY_API}/${id}/history`)).json();
|
||||
|
||||
static fetchServiceProviders = async (): Promise<DirectoryService[]> => {
|
||||
static fetchServiceProviders = async (): Promise<DirectoryServiceProvider[]> => {
|
||||
const res = await fetch(SERVICE_PROVIDERS);
|
||||
const json = await res.json();
|
||||
return json;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user