Compare commits

..

2 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacșu f25f76c1df Merge remote-tracking branch 'origin/incoming' into base 2023-06-29 17:02:11 +03:00
Bogdan-Ștefan Neacşu 29f95febe9 Feature/ephemera compile (#3437)
* Include ephemera node code in repo

* Upgrade deps

* Bump minor version of cosmwasm-std

* Include ephemera in nym-api dep and downgrade rusqlite

* Fix clippy and ephemera docs code

* More clippy on ephemera

---------

Co-authored-by: Andrus Salumets <andrus@nymtech.net>
2023-05-25 11:24:49 +03:00
283 changed files with 16308 additions and 5442 deletions
@@ -63,7 +63,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: 1.69.0
toolchain: stable
- 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: 1.69.0
toolchain: stable
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
@@ -107,8 +107,6 @@ 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
+138
View File
@@ -0,0 +1,138 @@
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: ???
-102
View File
@@ -1,102 +0,0 @@
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
-48
View File
@@ -4,54 +4,6 @@ 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)
- nym-name-service endpoint in nym-api ([#3403])
Generated
+15 -50
View File
@@ -378,15 +378,6 @@ 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"
@@ -1252,20 +1243,6 @@ 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"
@@ -1655,7 +1632,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.21"
version = "1.1.19"
dependencies = [
"chrono",
"clap 4.2.7",
@@ -3199,7 +3176,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.22"
version = "1.1.20"
dependencies = [
"anyhow",
"async-trait",
@@ -3333,7 +3310,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.21"
version = "1.1.19"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -3382,7 +3359,6 @@ 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",
@@ -3397,7 +3373,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.21"
version = "1.1.19"
dependencies = [
"clap 4.2.7",
"dirs",
@@ -3555,7 +3531,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.5.0"
version = "0.4.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3611,7 +3587,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.4.0"
version = "0.3.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -3668,7 +3644,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.21"
version = "1.1.19"
dependencies = [
"anyhow",
"async-trait",
@@ -3800,7 +3776,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.6.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3819,7 +3795,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.22"
version = "1.1.20"
dependencies = [
"anyhow",
"bs58",
@@ -3929,12 +3905,10 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.21"
version = "1.1.19"
dependencies = [
"anyhow",
"async-file-watcher",
"async-trait",
"bs58",
"clap 4.2.7",
"dirs",
"futures",
@@ -3949,6 +3923,7 @@ dependencies = [
"nym-credential-storage",
"nym-crypto",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-sdk",
"nym-service-providers-common",
"nym-socks5-proxy-helpers",
@@ -3956,13 +3931,11 @@ 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",
@@ -3974,7 +3947,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.21"
version = "1.1.19"
dependencies = [
"dirs",
"log",
@@ -4049,7 +4022,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.3.0"
version = "0.2.0"
dependencies = [
"pem",
]
@@ -4090,12 +4063,8 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -4107,7 +4076,6 @@ dependencies = [
"log",
"nym-bin-common",
"nym-sdk",
"nym-socks5-requests",
"nym-sphinx-anonymous-replies",
"serde",
"serde_json",
@@ -4117,7 +4085,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.21"
version = "1.1.19"
dependencies = [
"clap 4.2.7",
"lazy_static",
@@ -4212,13 +4180,10 @@ dependencies = [
name = "nym-socks5-requests"
version = "0.1.0"
dependencies = [
"bincode",
"log",
"nym-service-providers-common",
"nym-sphinx-addressing",
"serde",
"serde_json",
"tap",
"thiserror",
]
@@ -4516,7 +4481,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.7.0"
version = "0.6.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
+13 -11
View File
@@ -116,19 +116,21 @@ async-trait = "0.1.64"
anyhow = "1.0.71"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
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" }
cw4 = { version = "=0.13.4" }
cosmwasm-derive = "=1.2.5"
cosmwasm-schema = "=1.2.5"
cosmwasm-std = "=1.2.5"
cosmwasm-storage = "=1.2.5"
cosmrs = "=0.8.0"
cw-utils = "=1.0.1"
cw-storage-plus = "=1.0.1"
cw2 = { version = "=1.0.1" }
cw3 = { version = "=1.0.1" }
cw3-fixed-multisig = { version = "=1.0.1" }
cw4 = { version = "=1.0.1" }
cw-controllers = { version = "=1.0.1" }
dotenvy = "0.15.6"
generic-array = "0.14.7"
k256 = "0.11"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
+3 -7
View File
@@ -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/build-nym.html).
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/wallet/desktop-wallet.html).
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).
### Developing
@@ -32,11 +32,7 @@ For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
> 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)
You can chat with us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes place in the **#dev** channel. Node operators should be in the **#node-operators** channel.
### Rewards
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.21"
version = "1.1.19"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.21"
version = "1.1.19"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+5 -23
View File
@@ -865,20 +865,6 @@ 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"
@@ -2381,7 +2367,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.5.0"
version = "0.4.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -2416,7 +2402,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.4.0"
version = "0.3.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -2523,7 +2509,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.6.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -2623,7 +2609,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.3.0"
version = "0.2.0"
dependencies = [
"pem",
]
@@ -2633,12 +2619,8 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -2891,7 +2873,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.7.0"
version = "0.6.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_eq!(
debug_assert_ne!(
fragments.len(),
reply_surbs.len(),
"attempted to send {} fragments with {} reply surbs",
+4 -13
View File
@@ -155,7 +155,7 @@ pub async fn get_registered_gateway<S>(
key_store: &S::KeyStore,
setup: GatewaySetup,
overwrite_keys: bool,
) -> Result<(GatewayEndpointConfig, ManagedKeys), ClientCoreError>
) -> Result<GatewayEndpointConfig, 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(loaded_keys) => {
Ok(_) => {
// 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, loaded_keys));
return Ok(config);
} 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, managed_keys))
Ok(gateway_details)
}
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
@@ -299,15 +299,6 @@ 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>(
@@ -40,7 +40,7 @@ nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
async-trait = { workspace = true, optional = true }
bip39 = { workspace = true, features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { workspace = true, features = ["rpc", "bip32", "cosmwasm"], optional = true }
# note that this has the same version as used by cosmrs
eyre = { version = "0.6", optional = true }
cw3 = { workspace = true, optional = true }
@@ -54,7 +54,7 @@ cosmwasm-std = { workspace = true, optional = true }
[dev-dependencies]
bip39 = { workspace = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32"] }
cosmrs = { workspace = true, features = ["rpc", "bip32"] }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
ts-rs = "6.1.2"
@@ -127,7 +127,7 @@ impl GasAdjustable for Gas {
mod sealed {
use cosmrs::tx::{self, Gas};
use cosmrs::Coin as CosmosCoin;
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom};
use cosmrs::{AccountId, Denom as CosmosDenom};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
@@ -144,29 +144,11 @@ mod sealed {
}
}
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
val.to_string().parse().unwrap()
}
// at the time of writing it the current cosmrs' Decimal is extremely limited...
#[derive(Serialize, Deserialize)]
#[serde(remote = "CosmosDecimal")]
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
impl From<Decimal> for CosmosDecimal {
fn from(val: Decimal) -> Self {
val.0.into()
}
}
#[derive(Serialize, Deserialize, Clone)]
struct Coin {
#[serde(with = "Denom")]
denom: CosmosDenom,
#[serde(with = "Decimal")]
amount: CosmosDecimal,
amount: u128,
}
impl From<Coin> for CosmosCoin {
@@ -39,7 +39,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{self, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
pub use cosmrs::{bip32, AccountId, Denom};
use cosmwasm_std::Addr;
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
@@ -1,12 +1,12 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
use nym_contracts_common::ContractBuildInformation;
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, Service, ServiceId,
NymAddress, ServiceId, ServiceInfo,
};
use serde::Deserialize;
@@ -63,14 +63,17 @@ pub trait SpDirectoryQueryClient {
.await
}
async fn get_all_services(&self) -> Result<Vec<Service>, NyxdError> {
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, 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) = paged_response.start_next_after {
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
@@ -79,13 +82,6 @@ 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,9 +2,8 @@
// 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, ServiceDetails, ServiceId,
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
};
use crate::nyxd::{
@@ -23,16 +22,16 @@ pub trait SpDirectorySigningClient {
async fn announce_service_provider(
&self,
service: ServiceDetails,
owner_signature: MessageSignature,
nym_address: NymAddress,
service_type: ServiceType,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
service,
owner_signature,
nym_address,
service_type,
},
vec![deposit],
)
+2 -3
View File
@@ -14,7 +14,7 @@ clap = { version = "4.0", features = ["derive"] }
cw-utils = { workspace = true }
handlebars = "3.0.1"
humantime-serde = "1.0"
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
log = { workspace = true }
rand = {version = "0.6", features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
@@ -25,7 +25,7 @@ toml = "0.5.6"
url = "2.2"
tap = "1"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmrs = { workspace = true }
cosmwasm-std = { workspace = true }
nym-validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
@@ -40,4 +40,3 @@ 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" }
@@ -56,6 +56,8 @@ pub async fn generate(args: Args) {
.expect("threshold can't be converted to Decimal"),
},
max_voting_period: Duration::Time(args.max_voting_period),
executor: None,
proposal_deposit: None,
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
};
@@ -14,7 +14,6 @@ pub struct Mixnet {
pub command: MixnetCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetCommands {
/// Query the mixnet directory
@@ -15,7 +15,6 @@ pub struct MixnetOperators {
pub command: MixnetOperatorsCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsCommands {
/// Manage your mixnode
@@ -1,7 +1,6 @@
use clap::Parser;
use log::info;
use nym_contracts_common::signing::MessageSignature;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceDetails, ServiceType};
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
@@ -11,15 +10,9 @@ 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) {
@@ -27,17 +20,12 @@ 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(service, args.signature, deposit.into(), None)
.announce_service_provider(nym_address, service_type, deposit.into(), None)
.await
.expect("Failed to announce service provider");
@@ -1,61 +0,0 @@
// 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,7 +1,6 @@
use clap::{Args, Subcommand};
pub mod announce;
pub mod announce_sign_payload;
pub mod delete;
#[derive(Debug, Args)]
@@ -11,13 +10,10 @@ 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.announcer.to_string(),
service.service.announcer.to_string(),
service.service.service_type.to_string(),
service.service.nym_address.to_string(),
]);
@@ -14,7 +14,7 @@ pub struct InstantiateMsg {
pub mix_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
DepositFunds { data: DepositData },
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use crate::msg::ExecuteMsg;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct SpendCredentialData {
funds: Coin,
blinded_serial_number: String,
@@ -43,7 +43,7 @@ pub enum SpendCredentialStatus {
Spent,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct SpendCredential {
funds: Coin,
blinded_serial_number: String,
@@ -74,7 +74,7 @@ impl SpendCredential {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedSpendCredentialResponse {
pub spend_credentials: Vec<SpendCredential>,
pub per_page: usize,
@@ -95,7 +95,7 @@ impl PagedSpendCredentialResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct SpendCredentialResponse {
pub spend_credential: Option<SpendCredential>,
}
@@ -1,6 +1,6 @@
[package]
name = "nym-contracts-common"
version = "0.5.0"
version = "0.4.0"
description = "Common library for Nym cosmwasm contracts"
edition = { workspace = true }
authors = { workspace = true }
@@ -15,7 +15,7 @@ pub type Nonce = u32;
// define this type explicitly for [hopefully] better usability
// (so you wouldn't need to worry about whether you should use bytes, bs58, etc.)
#[derive(Clone, Debug, PartialEq, JsonSchema)]
#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct MessageSignature(Vec<u8>);
impl MessageSignature {
@@ -11,9 +11,6 @@ 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)
}
@@ -6,6 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-schema = { workspace = true }
cw4 = { workspace = true }
cw-controllers = { workspace = true }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -1,13 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_schema::{cw_serde, QueryResponses};
use cw4::Member;
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub struct InstantiateMsg {
/// The admin is the only account that can update the group state.
/// Omit it to make the group immutable.
@@ -15,8 +9,7 @@ pub struct InstantiateMsg {
pub members: Vec<Member>,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub enum ExecuteMsg {
/// Change the admin
UpdateAdmin { admin: Option<String> },
@@ -32,23 +25,24 @@ pub enum ExecuteMsg {
RemoveHook { addr: String },
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
/// Return AdminResponse
#[returns(cw_controllers::AdminResponse)]
Admin {},
/// Return TotalWeightResponse
TotalWeight {},
/// Returns MembersListResponse
#[returns(cw4::TotalWeightResponse)]
TotalWeight { at_height: Option<u64> },
#[returns(cw4::MemberListResponse)]
ListMembers {
start_after: Option<String>,
limit: Option<u32>,
},
/// Returns MemberResponse
#[returns(cw4::MemberResponse)]
Member {
addr: String,
at_height: Option<u64>,
},
/// Shows all registered hooks. Returns HooksResponse.
/// Shows all registered hooks.
#[returns(cw_controllers::HooksResponse)]
Hooks {},
}
@@ -1,6 +1,6 @@
[package]
name = "nym-mixnet-contract-common"
version = "0.6.0"
version = "0.5.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.5.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.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"
@@ -37,7 +37,7 @@ pub fn generate_owner_storage_subkey(
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct Delegation {
/// Address of the owner of this delegation.
pub owner: Addr,
@@ -114,7 +114,7 @@ impl Delegation {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixNodeDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<OwnerProxySubKey>,
@@ -129,7 +129,7 @@ impl PagedMixNodeDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(MixId, OwnerProxySubKey)>,
@@ -147,7 +147,7 @@ impl PagedDelegatorDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeDelegationResponse {
pub delegation: Option<Delegation>,
pub mixnode_still_bonded: bool,
@@ -162,7 +162,7 @@ impl MixNodeDelegationResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<StorageKey>,
@@ -23,7 +23,7 @@ pub struct Gateway {
pub version: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayBond {
pub pledge_amount: Coin,
pub owner: Addr,
@@ -132,7 +132,7 @@ impl GatewayConfigUpdate {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedGatewayResponse {
pub nodes: Vec<GatewayBond>,
pub per_page: usize,
@@ -153,13 +153,13 @@ impl PagedGatewayResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayOwnershipResponse {
pub address: Addr,
pub gateway: Option<GatewayBond>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayBondResponse {
pub identity: IdentityKey,
pub gateway: Option<GatewayBond>,
@@ -489,7 +489,7 @@ impl CurrentIntervalResponse {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct PendingEpochEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingEpochEvent>,
@@ -510,7 +510,7 @@ impl PendingEpochEventsResponse {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct PendingIntervalEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingIntervalEvent>,
@@ -33,7 +33,7 @@ impl RewardedSetNodeStatus {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding,
@@ -86,7 +86,7 @@ impl MixNodeDetails {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeRewarding {
/// Information provided by the operator that influence the cost function.
pub cost_params: MixNodeCostParams,
@@ -465,7 +465,7 @@ impl MixNodeRewarding {
}
// operator information + data assigned by the contract(s)
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeBond {
/// Unique id assigned to the bonded mixnode.
pub mix_id: MixId,
@@ -559,7 +559,7 @@ pub struct MixNode {
pub version: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeCostParams {
pub profit_margin_percent: Percent,
@@ -686,7 +686,7 @@ impl MixNodeConfigUpdate {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixnodeBondsResponse {
pub nodes: Vec<MixNodeBond>,
pub per_page: usize,
@@ -703,7 +703,7 @@ impl PagedMixnodeBondsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixnodesDetailsResponse {
pub nodes: Vec<MixNodeDetails>,
pub per_page: usize,
@@ -745,19 +745,19 @@ impl PagedUnbondedMixnodesResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixOwnershipResponse {
pub address: Addr,
pub mixnode_details: Option<MixNodeDetails>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixnodeDetailsResponse {
pub mix_id: MixId,
pub mixnode_details: Option<MixNodeDetails>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixnodeRewardingDetailsResponse {
pub mix_id: MixId,
pub rewarding_details: Option<MixNodeRewarding>,
@@ -7,19 +7,19 @@ use crate::{BlockHeight, EpochEventId, IntervalEventId, MixId};
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingEpochEvent {
pub id: EpochEventId,
pub event: PendingEpochEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingEpochEventData {
pub created_at: BlockHeight,
pub kind: PendingEpochEventKind,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PendingEpochEventKind {
// can't just pass the `Delegation` struct here as it's impossible to determine
// `cumulative_reward_ratio` ahead of time
@@ -68,19 +68,19 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingIntervalEvent {
pub id: IntervalEventId,
pub event: PendingIntervalEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingIntervalEventData {
pub created_at: BlockHeight,
pub kind: PendingIntervalEventKind,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PendingIntervalEventKind {
ChangeMixCostParams {
mix_id: MixId,
@@ -35,7 +35,7 @@ pub struct RewardDistribution {
pub delegates: Decimal,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct PendingRewardResponse {
pub amount_staked: Option<Coin>,
pub amount_earned: Option<Coin>,
@@ -46,7 +46,7 @@ pub struct PendingRewardResponse {
pub mixnode_still_fully_bonded: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct EstimatedCurrentEpochRewardResponse {
pub original_stake: Option<Coin>,
@@ -4,7 +4,6 @@
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;
@@ -12,6 +11,8 @@ 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;
@@ -22,7 +23,7 @@ pub type EpochEventId = u32;
pub type IntervalEventId = u32;
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq, Eq)]
pub struct LayerAssignment {
mix_id: MixId,
layer: Layer,
@@ -118,7 +119,7 @@ impl Index<Layer> for LayerDistribution {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContractState {
pub owner: Addr, // only the owner account can update state
pub rewarding_validator_address: Addr,
@@ -130,7 +131,7 @@ pub struct ContractState {
pub params: ContractStateParams,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContractStateParams {
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
pub minimum_mixnode_delegation: Option<Coin>,
@@ -9,6 +9,8 @@ edition = "2021"
cw-utils = { workspace = true }
cw3 = { workspace = true }
cw4 = { workspace= true }
cw-storage-plus = { workspace = true }
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::StdError;
use cw_utils::ThresholdError;
use cw3::DepositError;
use cw_utils::{PaymentError, ThresholdError};
use thiserror::Error;
@@ -17,9 +18,6 @@ pub enum ContractError {
#[error("Group contract invalid address '{addr}'")]
InvalidGroup { addr: String },
#[error("Coconut bandwidth contract address not found")]
InvalidCoconutBandwidth {},
#[error("Unauthorized")]
Unauthorized {},
@@ -43,4 +41,10 @@ pub enum ContractError {
#[error("Cannot close completed or passed proposals")]
WrongCloseStatus {},
#[error("{0}")]
Payment(#[from] PaymentError),
#[error("{0}")]
Deposit(#[from] DepositError),
}
@@ -1,2 +1,3 @@
pub mod error;
pub mod msg;
pub mod state;
@@ -1,15 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{CosmosMsg, Empty};
use cw3::Vote;
use cw3::{UncheckedDepositInfo, Vote};
use cw4::MemberChangedHookMsg;
use cw_utils::{Duration, Expiration, Threshold};
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
use crate::state::Executor;
#[cw_serde]
pub struct InstantiateMsg {
// this is the group contract that contains the member list
pub group_addr: String,
@@ -17,11 +17,15 @@ pub struct InstantiateMsg {
pub coconut_dkg_contract_address: String,
pub threshold: Threshold,
pub max_voting_period: Duration,
// who is able to execute passed proposals
// None means that anyone can execute
pub executor: Option<Executor>,
/// The cost of creating a proposal (if any).
pub proposal_deposit: Option<UncheckedDepositInfo>,
}
// TODO: add some T variants? Maybe good enough as fixed Empty for now
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub enum ExecuteMsg {
Propose {
title: String,
@@ -45,41 +49,44 @@ pub enum ExecuteMsg {
}
// We can also add this as a cw3 extension
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
/// Return ThresholdResponse
#[returns(cw_utils::ThresholdResponse)]
Threshold {},
/// Returns ProposalResponse
#[returns(cw3::ProposalResponse)]
Proposal { proposal_id: u64 },
/// Returns ProposalListResponse
#[returns(cw3::ProposalListResponse)]
ListProposals {
start_after: Option<u64>,
limit: Option<u32>,
},
/// Returns ProposalListResponse
#[returns(cw3::ProposalListResponse)]
ReverseProposals {
start_before: Option<u64>,
limit: Option<u32>,
},
/// Returns VoteResponse
#[returns(cw3::VoteResponse)]
Vote { proposal_id: u64, voter: String },
/// Returns VoteListResponse
#[returns(cw3::VoteListResponse)]
ListVotes {
proposal_id: u64,
start_after: Option<String>,
limit: Option<u32>,
},
/// Returns VoterInfo
#[returns(cw3::VoterResponse)]
Voter { address: String },
/// Returns VoterListResponse
#[returns(cw3::VoterListResponse)]
ListVoters {
start_after: Option<String>,
limit: Option<u32>,
},
/// Gets the current configuration.
#[returns(crate::state::Config)]
Config {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[cw_serde]
pub struct MigrateMsg {
pub coconut_bandwidth_address: String,
pub coconut_dkg_address: String,
@@ -0,0 +1,59 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, QuerierWrapper};
use cw3::DepositInfo;
use cw4::Cw4Contract;
use cw_storage_plus::Item;
use cw_utils::{Duration, Threshold};
use crate::error::ContractError;
/// Defines who is able to execute proposals once passed
#[cw_serde]
pub enum Executor {
/// Any member of the voting group, even with 0 points
Member,
/// Only the given address
Only(Addr),
}
#[cw_serde]
pub struct Config {
pub threshold: Threshold,
pub max_voting_period: Duration,
// Total weight and voters are queried from this contract
pub group_addr: Cw4Contract,
pub coconut_bandwidth_addr: Addr,
pub coconut_dkg_addr: Addr,
// who is able to execute passed proposals
// None means that anyone can execute
pub executor: Option<Executor>,
/// The price, if any, of creating a new proposal.
pub proposal_deposit: Option<DepositInfo>,
}
impl Config {
// Executor can be set in 3 ways:
// - Member: any member of the voting group is authorized
// - Only: only passed address is authorized
// - None: Everyone are authorized
pub fn authorize(&self, querier: &QuerierWrapper, sender: &Addr) -> Result<(), ContractError> {
if let Some(executor) = &self.executor {
match executor {
Executor::Member => {
self.group_addr
.is_member(querier, sender, None)?
.ok_or(ContractError::Unauthorized {})?;
}
Executor::Only(addr) => {
if addr != sender {
return Err(ContractError::Unauthorized {});
}
}
}
}
Ok(())
}
}
// unique items
pub const CONFIG: Item<Config> = Item::new("config");
@@ -7,9 +7,5 @@ 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 }
@@ -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.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
.add_attribute(SERVICE_TYPE, service.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
.add_attribute(OWNER, service.announcer.to_string())
}
pub fn new_delete_id_event(service: Service) -> Event {
pub fn new_delete_id_event(service_id: ServiceId, service: Service) -> Event {
Event::new(ServiceProviderEventType::DeleteId)
.add_attribute(ACTION, ServiceProviderEventType::DeleteId)
.add_attribute(SERVICE_ID, service.service_id.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
.add_attribute(SERVICE_ID, service_id.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
}
pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event {
@@ -1,8 +1,6 @@
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,6 +1,5 @@
use crate::{NymAddress, ServiceDetails, ServiceId};
use crate::{NymAddress, ServiceId, ServiceType};
use cosmwasm_std::Coin;
use nym_contracts_common::signing::MessageSignature;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -23,8 +22,8 @@ pub struct MigrateMsg {}
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Announce {
service: ServiceDetails,
owner_signature: MessageSignature,
nym_address: NymAddress,
service_type: ServiceType,
},
DeleteId {
service_id: ServiceId,
@@ -45,12 +44,9 @@ impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
service,
owner_signature: _,
} => format!(
"announcing {} as type {}",
service.nym_address, service.service_type
),
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
@@ -80,9 +76,6 @@ 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::{Service, ServiceId};
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -13,17 +13,22 @@ pub struct ServiceInfoResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServicesListResponse {
pub services: Vec<Service>,
pub services: Vec<ServiceInfo>,
}
impl ServicesListResponse {
pub fn new(services: Vec<Service>) -> ServicesListResponse {
ServicesListResponse { services }
pub fn new(services: Vec<(ServiceId, Service)>) -> ServicesListResponse {
ServicesListResponse {
services: services
.into_iter()
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
.collect(),
}
}
}
impl From<&[Service]> for ServicesListResponse {
fn from(services: &[Service]) -> Self {
impl From<&[ServiceInfo]> for ServicesListResponse {
fn from(services: &[ServiceInfo]) -> Self {
Self {
services: services.to_vec(),
}
@@ -33,17 +38,21 @@ impl From<&[Service]> for ServicesListResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedServicesListResponse {
pub services: Vec<Service>,
pub services: Vec<ServiceInfo>,
pub per_page: usize,
pub start_next_after: Option<ServiceId>,
}
impl PagedServicesListResponse {
pub fn new(
services: Vec<Service>,
services: Vec<(ServiceId, 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,
@@ -57,3 +66,12 @@ 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,
}
}
}
@@ -1,33 +0,0 @@
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,7 +1,6 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use nym_contracts_common::IdentityKey;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -10,11 +9,11 @@ pub type ServiceId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct Service {
/// Unique id assigned to the anounced service.
pub service_id: ServiceId,
/// The announced service.
pub service: ServiceDetails,
/// Address of the service owner.
/// The address of the service.
pub nym_address: NymAddress,
/// The service type.
pub service_type: ServiceType,
/// Service owner.
pub announcer: Addr,
/// Block height at which the service was added.
pub block_height: u64,
@@ -22,16 +21,6 @@ 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")]
@@ -39,7 +28,7 @@ pub enum NymAddress {
/// String representation of a nym address, which is of the form
/// client_id.client_enc@gateway_id.
Address(String),
// String name that can looked up in the nym-name-service contract (once it exists)
// For the future when we have a nym-dns contract
//Name(String),
}
@@ -52,7 +41,6 @@ impl NymAddress {
pub fn as_str(&self) -> &str {
match self {
NymAddress::Address(address) => address,
//NymAddress::Name(name) => name,
}
}
}
@@ -78,3 +66,19 @@ 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.7.0"
version = "0.6.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.6.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
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" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
ts-rs = {version = "6.1.2", optional = true}
@@ -28,7 +28,7 @@ pub enum Period {
After,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct PledgeData {
pub amount: Coin,
pub block_time: Timestamp,
@@ -49,7 +49,7 @@ impl PledgeData {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum PledgeCap {
Percent(Percent),
Absolute(Uint128), // This has to be in unym
@@ -77,7 +77,7 @@ impl Default for PledgeCap {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct OriginalVestingResponse {
pub amount: Coin,
pub number_of_periods: usize,
@@ -55,7 +55,7 @@ impl VestingSpecification {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// Families
+1 -1
View File
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmrs = { workspace = true }
thiserror = "1.0"
# I guess temporarily until we get serde support in coconut up and running
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-crypto"
version = "0.4.0"
version = "0.3.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.3.0" }
nym-pemstore = { path = "../../common/pemstore", version = "0.2.0" }
[dev-dependencies]
rand_chacha = "0.2"
+2 -2
View File
@@ -6,8 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip32 = "0.3.0"
k256 = "0.10.4"
bip32 = "0.4.0"
k256 = { workspace = true }
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
+2 -2
View File
@@ -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.4.0" }
nym-crypto = { path = "../crypto", version = "0.3.0" }
nym-topology = { path = "../topology" }
[dev-dependencies]
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-crypto = { path = "../crypto", version = "0.4.0", features = ["asymmetric"] }
nym-crypto = { path = "../crypto", version = "0.3.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 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-pemstore"
description = "Store private-public keypairs in PEM format"
version = "0.3.0"
version = "0.2.0"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
@@ -428,14 +428,18 @@ impl SocksClient {
Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(),
)
.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);
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Socks5Request::new_send(
request_version.provider_protocol,
conn_id,
read_data,
socket_closed,
);
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,20 +1,19 @@
// 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::{ControllerCommand, ControllerSender};
use nym_socks5_proxy_helpers::connection_controller::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,
@@ -80,20 +79,12 @@ impl MixnetResponseListener {
);
Err(err_response.into())
}
Socks5ResponseContent::NetworkData { content } => {
Socks5ResponseContent::NetworkData(response) => {
self.controller_sender
.unbounded_send(ControllerCommand::new_send(content))
.unbounded_send(response.into())
.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(())
}
}
}
+142 -172
View File
@@ -1,21 +1,6 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::message::OrderedMessage;
use log::*;
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 },
}
use std::collections::HashMap;
/// Stores messages and emits them in order.
///
@@ -24,58 +9,36 @@ pub enum OrderedMessageError {
/// to fill up with the full sequence.
#[derive(Debug)]
pub struct OrderedMessageBuffer {
next_sequence: u64,
messages: BTreeMap<u64, Vec<u8>>,
next_index: u64,
messages: HashMap<u64, OrderedMessage>,
}
/// 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_sequence: u64,
pub last_index: u64,
}
const MAX_REASONABLE_OFFSET: u64 = 1000;
impl OrderedMessageBuffer {
pub fn new() -> OrderedMessageBuffer {
OrderedMessageBuffer {
next_sequence: 0,
messages: BTreeMap::new(),
next_index: 0,
messages: HashMap::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, 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,
});
}
pub fn write(&mut self, message: OrderedMessage) {
trace!(
"Writing message index: {} length {} to OrderedMessageBuffer.",
sequence,
data.len()
"Writing message index: {} length {:?} to OrderedMessageBuffer.",
message.index,
message.data.len()
);
self.messages.insert(sequence, data);
Ok(())
self.messages.insert(message.index, message);
}
/// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless
@@ -86,31 +49,33 @@ 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_sequence) {
if !self.messages.contains_key(&self.next_index) {
return None;
}
let mut contiguous_messages = Vec::new();
let mut seq = self.next_sequence;
let mut index = self.next_index;
while let Some(mut data) = self.messages.remove(&seq) {
contiguous_messages.append(&mut data);
seq += 1;
while let Some(ordered_message) = self.messages.remove(&index) {
contiguous_messages.push(ordered_message);
index += 1;
}
let high_water = seq;
self.next_sequence = high_water;
trace!("Next high water mark is: {high_water}");
let high_water = index;
self.next_index = high_water;
trace!("Next high water mark is: {}", high_water);
trace!(
"Returning {} bytes from ordered message buffer",
contiguous_messages.len()
);
// 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());
Some(ReadContiguousData {
data: contiguous_messages,
last_sequence: seq,
data,
last_index: index,
})
}
}
@@ -125,64 +90,6 @@ 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::*;
@@ -195,14 +102,20 @@ mod test_chunking_and_reassembling {
fn read_returns_ordered_bytes_and_resets_buffer() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
let first_message = OrderedMessage {
data: vec![1, 2, 3, 4],
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
buffer.write(0, first_message).unwrap();
buffer.write(first_message);
let first_read = buffer.read().unwrap().data;
assert_eq!(vec![1, 2, 3, 4], first_read);
buffer.write(1, second_message).unwrap();
buffer.write(second_message);
let second_read = buffer.read().unwrap().data;
assert_eq!(vec![5, 6, 7, 8], second_read);
@@ -213,11 +126,17 @@ mod test_chunking_and_reassembling {
fn test_multiple_adds_stacks_up_bytes_in_the_buffer() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
let first_message = OrderedMessage {
data: vec![1, 2, 3, 4],
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
buffer.write(0, first_message).unwrap();
buffer.write(1, second_message).unwrap();
buffer.write(first_message);
buffer.write(second_message);
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
@@ -227,11 +146,17 @@ mod test_chunking_and_reassembling {
fn out_of_order_adds_results_in_ordered_byte_vector() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
let first_message = OrderedMessage {
data: vec![1, 2, 3, 4],
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
buffer.write(1, second_message).unwrap();
buffer.write(0, first_message).unwrap();
buffer.write(second_message);
buffer.write(first_message);
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
@@ -245,13 +170,23 @@ mod test_chunking_and_reassembling {
fn setup() -> OrderedMessageBuffer {
let mut buffer = OrderedMessageBuffer::new();
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 zero_message = OrderedMessage {
data: vec![0, 0, 0, 0],
index: 0,
};
let one_message = OrderedMessage {
data: vec![1, 1, 1, 1],
index: 1,
};
buffer.write(0, zero_message).unwrap();
buffer.write(1, one_message).unwrap();
buffer.write(3, three_message).unwrap();
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
}
#[test]
@@ -264,31 +199,43 @@ 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 = vec![5, 5, 5, 5];
buffer.write(5, five_message).unwrap();
let five_message = OrderedMessage {
data: vec![5, 5, 5, 5],
index: 5,
};
buffer.write(five_message);
assert_eq!(None, buffer.read());
}
#[test]
fn filling_the_gap_allows_us_to_get_everything() {
let mut buffer = setup();
let _ = buffer.read(); // that burns the first two. We still have a gap before the 3s.
buffer.read(); // that burns the first two. We still have a gap before the 3s.
let two_message = vec![2, 2, 2, 2];
buffer.write(2, two_message).unwrap();
let two_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 2,
};
buffer.write(two_message);
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 = vec![5, 5, 5, 5];
buffer.write(5, five_message).unwrap();
let five_message = OrderedMessage {
data: vec![5, 5, 5, 5],
index: 5,
};
buffer.write(five_message);
assert_eq!(None, buffer.read());
// let's fill in the gap of 4s now and read again
let four_message = vec![4, 4, 4, 4];
buffer.write(4, four_message).unwrap();
let four_message = OrderedMessage {
data: vec![4, 4, 4, 4],
index: 4,
};
buffer.write(four_message);
assert_eq!(
[4, 4, 4, 4, 5, 5, 5, 5].to_vec(),
@@ -302,47 +249,70 @@ 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 = vec![0, 0, 0, 0];
let one_message = vec![2, 2, 2, 2];
let two_message = vec![];
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,
};
buffer.write(0, zero_message).unwrap();
buffer.write(zero_message);
assert!(buffer.read().is_some()); // burn the buffer
buffer.write(2, two_message).unwrap();
buffer.write(1, one_message).unwrap();
buffer.write(two_message);
buffer.write(one_message);
assert!(buffer.read().is_some());
assert_eq!(buffer.next_sequence, 3);
assert_eq!(buffer.next_index, 3);
}
#[test]
fn works_with_gaps_bigger_than_one() {
let mut buffer = OrderedMessageBuffer::new();
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();
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);
assert!(buffer.read().is_some());
assert_eq!(buffer.next_sequence, 1);
assert_eq!(buffer.next_index, 1);
buffer.write(4, four_message).unwrap();
buffer.write(four_message);
assert!(buffer.read().is_none());
assert_eq!(buffer.next_sequence, 1);
assert_eq!(buffer.next_index, 1);
buffer.write(3, three_message).unwrap();
buffer.write(three_message);
assert!(buffer.read().is_none());
assert_eq!(buffer.next_sequence, 1);
assert_eq!(buffer.next_index, 1);
buffer.write(2, two_message).unwrap();
buffer.write(two_message);
assert!(buffer.read().is_none());
assert_eq!(buffer.next_sequence, 1);
assert_eq!(buffer.next_index, 1);
buffer.write(1, one_message).unwrap();
buffer.write(one_message);
assert!(buffer.read().is_some());
assert_eq!(buffer.next_sequence, 5)
assert_eq!(buffer.next_index, 5)
}
}
}
+6 -1
View File
@@ -1,3 +1,8 @@
mod buffer;
mod message;
mod sender;
pub use buffer::{OrderedMessageBuffer, OrderedMessageError, ReadContiguousData};
pub use buffer::{OrderedMessageBuffer, ReadContiguousData};
pub use message::MessageError;
pub use message::OrderedMessage;
pub use sender::OrderedMessageSender;
+143
View File
@@ -0,0 +1,143 @@
// 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);
}
@@ -0,0 +1,57 @@
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-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - 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::{OrderedMessageBuffer, ReadContiguousData};
use nym_socks5_requests::{ConnectionId, SocketData};
use nym_ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData};
use nym_socks5_requests::{ConnectionId, NetworkData, SendRequest};
use nym_task::connections::{ConnectionCommand, ConnectionCommandSender};
use nym_task::TaskClient;
use std::collections::{HashMap, HashSet};
@@ -40,13 +40,29 @@ pub enum ControllerCommand {
connection_id: ConnectionId,
},
Send {
data: SocketData,
connection_id: ConnectionId,
data: Vec<u8>,
is_closed: bool,
},
}
impl ControllerCommand {
pub fn new_send(data: SocketData) -> Self {
ControllerCommand::Send { data }
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,
}
}
}
@@ -58,13 +74,18 @@ struct ActiveConnection {
}
impl ActiveConnection {
fn write_to_buf(&mut self, seq: u64, payload: Vec<u8>, is_closed: bool) {
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;
}
};
if is_closed {
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.closed_at_index = Some(ordered_message.index);
}
self.ordered_buffer.write(ordered_message);
}
fn read_from_buf(&mut self) -> Option<ReadContiguousData> {
@@ -96,7 +117,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<SocketData>>,
pending_messages: HashMap<ConnectionId, Vec<(Vec<u8>, bool)>>,
shutdown: TaskClient,
}
@@ -133,8 +154,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 data in pending {
self.send_to_connection(data)
for (payload, is_closed) in pending {
self.send_to_connection(conn_id, payload, is_closed)
}
}
}
@@ -163,25 +184,20 @@ impl Controller {
}
}
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);
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");
}
if let Some(payload) = active_connection.read_from_buf() {
if let Some(closed_at_index) = active_connection.closed_at_index {
if payload.last_sequence > closed_at_index {
if payload.last_index > 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()
@@ -191,26 +207,34 @@ impl Controller {
socket_closed: active_connection.is_closed,
})
{
error!("failed to send on the active connection channel: {err}");
error!("WTF IS THIS: {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(&hdr.connection_id) {
} else if !self.recently_closed.contains(&conn_id) {
debug!("Received a 'Send' before 'Connect' - going to buffer the data");
let pending = self
.pending_messages
.entry(hdr.connection_id)
.entry(conn_id)
.or_insert_with(Vec::new);
pending.push(message);
} else if !hdr.local_socket_closed {
pending.push((payload, is_closed));
} else if !is_closed {
error!(
"Tried to write to closed connection {} ({} bytes were 'lost)",
hdr.connection_id,
message.data.len()
conn_id,
payload.len()
);
} else {
debug!(
"Tried to write to closed connection {}, but remote is already closed",
hdr.connection_id
conn_id
)
}
}
@@ -219,8 +243,8 @@ impl Controller {
loop {
tokio::select! {
command = self.receiver.next() => match command {
Some(ControllerCommand::Send{data}) => {
self.send_to_connection(data)
Some(ControllerCommand::Send{connection_id, data, is_closed}) => {
self.send_to_connection(connection_id, data, is_closed)
}
Some(ControllerCommand::Insert{connection_id, connection_sender}) => {
self.insert_connection(connection_id, connection_sender)
-1
View File
@@ -3,5 +3,4 @@
pub mod available_reader;
pub mod connection_controller;
pub mod ordered_sender;
pub mod proxy_runner;
@@ -1,116 +0,0 @@
// 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,22 +1,106 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - 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_socks5_requests::{ConnectionId, SocketData};
use nym_ordered_buffer::OrderedMessageSender;
use nym_socks5_requests::ConnectionId;
use nym_task::connections::LaneQueueLengths;
use nym_task::connections::TransmissionLane;
use nym_task::TaskClient;
use std::sync::Arc;
use std::fmt::Debug;
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(
@@ -74,21 +158,27 @@ async fn wait_for_lane(
}
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn run_inbound<F, S>(
mut reader: OwnedReadHalf,
mut message_sender: OrderedMessageSender<F, S>,
local_destination_address: String, // addresses are provided for better logging
remote_source_address: String,
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(SocketData) -> S + Send + 'static,
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static,
S: Debug,
{
// 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));
@@ -127,7 +217,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
message_sender.send_empty_close().await;
send_empty_close(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await;
break;
}
_ = shutdown_listener.recv() => {
@@ -143,7 +233,7 @@ where
break;
}
_ = keepalive_timer.tick() => {
message_sender.send_empty_keepalive().await;
send_empty_keepalive(connection_id, &mut message_sender, &mix_sender, &adapter_fn).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
@@ -151,12 +241,15 @@ where
read_data = wait_until_lane_almost_empty(&lane_queue_lengths, connection_id)
.then(|_| available_reader.next()), if !we_are_closed =>
{
let processed = message_sender.process_data(read_data);
let is_done = processed.is_done;
message_sender.send_data(processed).await;
if is_done {
if deal_with_data(
read_data,
&local_destination_address,
&remote_source_address,
connection_id,
&mut message_sender,
&mix_sender,
&adapter_fn,
).await {
// 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,8 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::connection_controller::ConnectionReceiver;
use crate::ordered_sender::OrderedMessageSender;
use nym_socks5_requests::{ConnectionId, SocketData};
use nym_socks5_requests::ConnectionId;
use nym_task::connections::LaneQueueLengths;
use nym_task::TaskClient;
use std::fmt::Debug;
@@ -14,7 +13,7 @@ mod inbound;
mod outbound;
// TODO: make this configurable
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(30);
// Send empty keepalive messages regurarly to keep the connection alive. This should be smaller
// than [`MIX_TTL`].
@@ -93,24 +92,20 @@ 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(SocketData) -> S + Send + Sync + 'static,
F: Fn(ConnectionId, Vec<u8>, bool) -> 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 ordered_sender = OrderedMessageSender::new(
let inbound_future = inbound::run_inbound(
read_half,
self.local_destination_address.clone(),
self.remote_source_address.clone(),
self.connection_id,
self.mix_sender.clone(),
adapter_fn,
);
let inbound_future = inbound::run_inbound(
read_half,
ordered_sender,
self.connection_id,
self.available_plaintext_per_mix_packet,
adapter_fn,
Arc::clone(&shutdown_notify),
self.lane_queue_lengths.clone(),
self.shutdown_listener.clone(),
+4 -6
View File
@@ -7,11 +7,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bincode = "1.3.3"
log = { workspace = true }
nym-service-providers-common = { path = "../../../service-providers/common" }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tap = { workspace = true }
thiserror = { workspace = true }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
nym-service-providers-common = { path = "../../../service-providers/common" }
+3 -293
View File
@@ -3,7 +3,6 @@
use nym_service_providers_common::interface;
use nym_service_providers_common::interface::ServiceProviderMessagingError;
use std::mem;
use thiserror::Error;
pub use request::*;
@@ -17,185 +16,6 @@ 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}")]
@@ -212,18 +32,6 @@ 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)]
@@ -231,103 +39,6 @@ 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::*;
@@ -376,10 +87,9 @@ mod tests {
match new_deserialized.content {
RequestContent::ProviderData(req) => match req.content {
Socks5RequestContent::Send(send_req) => {
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);
assert_eq!(send_req.conn_id, 7810961472501196273);
assert_eq!(send_req.data.len(), 111);
assert!(!send_req.local_closed);
}
_ => panic!("unexpected request"),
},
+57 -151
View File
@@ -1,15 +1,10 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
make_bincode_serializer, InsufficientSocketDataError, SocketData, Socks5ProtocolVersion,
Socks5RequestError, Socks5Response,
};
use crate::{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;
@@ -20,7 +15,6 @@ pub type RemoteAddress = String;
pub enum RequestFlag {
Connect = 0,
Send = 1,
Query = 2,
}
impl TryFrom<u8> for RequestFlag {
@@ -30,7 +24,6 @@ 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 }),
}
}
@@ -58,15 +51,6 @@ 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 {
@@ -75,7 +59,7 @@ impl RequestDeserializationError {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub struct ConnectRequest {
// TODO: is connection_id redundant now?
pub conn_id: ConnectionId,
@@ -83,15 +67,11 @@ pub struct ConnectRequest {
pub return_address: Option<Recipient>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub struct SendRequest {
pub data: SocketData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryRequest {
OpenProxy,
Description,
pub conn_id: ConnectionId,
pub data: Vec<u8>,
pub local_closed: bool,
}
#[derive(Debug, Clone)]
@@ -128,13 +108,6 @@ 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..])?,
@@ -182,27 +155,22 @@ impl Socks5Request {
}
}
pub fn new_send(protocol_version: Socks5ProtocolVersion, data: SocketData) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::new_send(data),
}
}
pub fn new_query(
pub fn new_send(
protocol_version: Socks5ProtocolVersion,
query: QueryRequest,
conn_id: ConnectionId,
data: Vec<u8>,
local_closed: bool,
) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::Query(query),
content: Socks5RequestContent::new_send(conn_id, data, local_closed),
}
}
}
/// 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, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub enum Socks5RequestContent {
/// Start a new TCP connection to the specified `RemoteAddress` and send
/// the request data up the connection.
@@ -211,8 +179,6 @@ pub enum Socks5RequestContent {
/// Re-use an existing TCP connection, sending more request data up it.
Send(SendRequest),
Query(QueryRequest),
}
impl Socks5RequestContent {
@@ -230,8 +196,16 @@ impl Socks5RequestContent {
}
/// Construct a new Request::Send instance
pub fn new_send(data: SocketData) -> Socks5RequestContent {
Socks5RequestContent::Send(SendRequest { data })
pub fn new_send(
conn_id: ConnectionId,
data: Vec<u8>,
local_closed: bool,
) -> Socks5RequestContent {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
})
}
/// Deserialize the request type, connection id, destination address and port,
@@ -248,27 +222,18 @@ 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
@@ -313,13 +278,15 @@ impl Socks5RequestContent {
return_address,
))
}
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))
RequestFlag::Send => {
let local_closed = b[9] != 0;
let data = b[10..].to_vec();
Ok(Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}))
}
}
}
@@ -346,21 +313,10 @@ impl Socks5RequestContent {
}
}
Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8)
.chain(req.data.into_request_bytes_iter())
.chain(req.conn_id.to_be_bytes().into_iter())
.chain(std::iter::once(req.local_closed as u8))
.chain(req.data.into_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()
}
}
}
}
@@ -603,38 +559,18 @@ 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, 8 bytes of sequence and 0 bytes request data
let request_bytes = [
RequestFlag::Send as u8,
1,
2,
3,
4,
5,
6,
7,
8,
0,
0,
0,
0,
0,
0,
0,
0,
1,
]
.to_vec();
// 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 { 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());
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!(),
}
@@ -642,7 +578,7 @@ mod request_deserialization_tests {
#[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)
// correct 8 bytes of connection_id, 1 byte of local_closed and 3 bytes request data (all 255)
let request_bytes = [
RequestFlag::Send as u8,
1,
@@ -653,15 +589,7 @@ mod request_deserialization_tests {
6,
7,
8,
1,
0,
0,
0,
0,
0,
0,
0,
1,
255,
255,
255,
@@ -670,39 +598,17 @@ mod request_deserialization_tests {
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_eq!(vec![255, 255, 255], data.data);
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)
}
_ => 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);
}
}
}
+147 -97
View File
@@ -1,13 +1,8 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
make_bincode_serializer, ConnectionId, InsufficientSocketDataError, SocketData,
Socks5ProtocolVersion, Socks5RequestError,
};
use crate::{ConnectionId, 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`
@@ -18,7 +13,6 @@ use thiserror::Error;
pub enum ResponseFlag {
NetworkData = 1,
ConnectionError = 2,
Query = 3,
}
impl TryFrom<u8> for ResponseFlag {
@@ -28,20 +22,13 @@ 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)]
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ResponseDeserializationError {
#[error("the network data was malformed: {source}")]
MalformedNetworkData {
#[from]
source: InsufficientSocketDataError,
},
#[error("not enough bytes to recover the connection id")]
ConnectionIdTooShort,
@@ -56,12 +43,6 @@ pub enum ResponseDeserializationError {
#[from]
source: std::string::FromUtf8Error,
},
#[error("failed to deserialize query response: {source}")]
QueryDeserializationError {
#[from]
source: bincode::Error,
},
}
#[derive(Debug)]
@@ -124,14 +105,23 @@ 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(seq, connection_id, data, is_closed),
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),
}
}
@@ -145,35 +135,25 @@ 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(Clone, Debug, PartialEq, Eq)]
#[derive(Debug)]
pub enum Socks5ResponseContent {
NetworkData { content: SocketData },
NetworkData(NetworkData),
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 {
content: SocketData::new(seq, connection_id, is_closed, data),
}
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))
}
pub fn new_connection_error(
@@ -185,9 +165,9 @@ impl Socks5ResponseContent {
pub fn into_bytes(self) -> Vec<u8> {
match self {
Socks5ResponseContent::NetworkData { content } => {
Socks5ResponseContent::NetworkData(res) => {
std::iter::once(ResponseFlag::NetworkData as u8)
.chain(content.into_response_bytes_iter())
.chain(res.into_bytes().into_iter())
.collect()
}
Socks5ResponseContent::ConnectionError(res) => {
@@ -195,18 +175,6 @@ 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()
}
}
}
@@ -219,22 +187,84 @@ impl Socks5ResponseContent {
let response_flag = ResponseFlag::try_from(b[0])?;
match response_flag {
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData {
content: SocketData::try_from_response_bytes(&b[1..])?,
}),
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData(
NetworkData::try_from_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))
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
/// 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)]
pub struct ConnectionError {
pub connection_id: ConnectionId,
pub network_requester_error: String,
@@ -288,16 +318,62 @@ 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::*;
@@ -320,14 +396,11 @@ mod tests {
#[test]
fn deserialization_errors() {
let err = ConnectionError::try_from_bytes(&[]).err().unwrap();
assert!(matches!(err, ResponseDeserializationError::NoData));
assert_eq!(err, ResponseDeserializationError::NoData);
let bytes: [u8; 5] = [1, 2, 3, 4, 5];
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
assert!(matches!(
err,
ResponseDeserializationError::ConnectionIdTooShort
));
assert_eq!(err, ResponseDeserializationError::ConnectionIdTooShort);
let bytes: Vec<u8> = 42u64
.to_be_bytes()
@@ -341,27 +414,4 @@ 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);
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ url = "2.2"
ts-rs = "6.1.2"
cosmwasm-std = { workspace = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmrs = { workspace = true }
nym-validator-client = { path = "../../common/client-libs/validator-client", features = [
"nyxd-client",
+240 -116
View File
@@ -241,8 +241,8 @@ dependencies = [
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"cw3",
"cw3-flex-multisig",
"cw4",
@@ -260,9 +260,9 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.7.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
[[package]]
name = "constant_time_eq"
@@ -272,11 +272,11 @@ checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b"
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd"
checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340"
dependencies = [
"digest 0.9.0",
"digest 0.10.7",
"ed25519-zebra",
"k256",
"rand_core 0.6.4",
@@ -285,45 +285,62 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "1.0.0"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4"
checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773"
dependencies = [
"syn 1.0.109",
]
[[package]]
name = "cosmwasm-schema"
version = "1.0.0"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da"
checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc"
dependencies = [
"cosmwasm-schema-derive",
"schemars",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "cosmwasm-schema-derive"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "cosmwasm-std"
version = "1.0.0"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195"
checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb"
dependencies = [
"base64 0.13.1",
"cosmwasm-crypto",
"cosmwasm-derive",
"derivative",
"forward_ref",
"hex",
"schemars",
"serde",
"serde-json-wasm",
"serde-json-wasm 0.5.1",
"sha2 0.10.6",
"thiserror",
"uint",
]
[[package]]
name = "cosmwasm-storage"
version = "1.0.0"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5"
checksum = "a3737a3aac48f5ed883b5b73bfb731e77feebd8fc6b43419844ec2971072164d"
dependencies = [
"cosmwasm-std",
"serde",
@@ -389,9 +406,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-bigint"
version = "0.3.2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21"
checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
dependencies = [
"generic-array 0.14.6",
"rand_core 0.6.4",
@@ -454,13 +471,14 @@ dependencies = [
[[package]]
name = "cw-controllers"
version = "0.13.4"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"schemars",
"serde",
"thiserror",
@@ -468,17 +486,17 @@ dependencies = [
[[package]]
name = "cw-multi-test"
version = "0.13.4"
version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e"
checksum = "2a18afd2e201221c6d72a57f0886ef2a22151bbc9e6db7af276fde8a91081042"
dependencies = [
"anyhow",
"cosmwasm-std",
"cosmwasm-storage",
"cw-storage-plus",
"cw-utils",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"derivative",
"itertools",
"k256",
"prost",
"schemars",
"serde",
@@ -487,9 +505,20 @@ dependencies = [
[[package]]
name = "cw-storage-plus"
version = "0.13.4"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a"
checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "cw-storage-plus"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa"
dependencies = [
"cosmwasm-std",
"schemars",
@@ -498,50 +527,117 @@ dependencies = [
[[package]]
name = "cw-utils"
version = "0.13.4"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b"
checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw2 0.16.0",
"schemars",
"semver",
"serde",
"thiserror",
]
[[package]]
name = "cw-utils"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw2 1.0.1",
"schemars",
"semver",
"serde",
"thiserror",
]
[[package]]
name = "cw2"
version = "0.13.4"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1"
checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-storage-plus",
"cw-storage-plus 0.16.0",
"schemars",
"serde",
]
[[package]]
name = "cw2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-storage-plus 1.0.1",
"schemars",
"serde",
]
[[package]]
name = "cw20"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils 1.0.1",
"schemars",
"serde",
]
[[package]]
name = "cw20-base"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79314264ffc46b7658ee30caccc1540f14b9119568264bc02817f79c6f989a9"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-storage-plus 0.16.0",
"cw-utils 0.16.0",
"cw2 1.0.1",
"cw20",
"schemars",
"semver",
"serde",
"thiserror",
]
[[package]]
name = "cw3"
version = "0.13.4"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b"
checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw-utils 1.0.1",
"cw20",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw3-fixed-multisig"
version = "0.13.4"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706"
checksum = "e9e2415adb201e5e89dab34edf59d7dc166bc558526de009a49ae66276c9119a"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"cw2",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"cw2 1.0.1",
"cw3",
"schemars",
"serde",
@@ -550,14 +646,15 @@ dependencies = [
[[package]]
name = "cw3-flex-multisig"
version = "0.13.1"
version = "1.0.0"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw2",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"cw2 1.0.1",
"cw20",
"cw20-base",
"cw3",
"cw3-fixed-multisig",
"cw4",
@@ -565,31 +662,31 @@ dependencies = [
"nym-group-contract-common",
"nym-multisig-contract-common",
"schemars",
"serde",
]
[[package]]
name = "cw4"
version = "0.13.4"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd"
checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-storage-plus",
"cw-storage-plus 1.0.1",
"schemars",
"serde",
]
[[package]]
name = "cw4-group"
version = "0.13.4"
version = "1.0.0"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-controllers",
"cw-storage-plus",
"cw-utils",
"cw2",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"cw2 1.0.1",
"cw4",
"nym-group-contract-common",
"schemars",
@@ -599,11 +696,12 @@ dependencies = [
[[package]]
name = "der"
version = "0.5.1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c"
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
@@ -654,9 +752,9 @@ checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
[[package]]
name = "ecdsa"
version = "0.13.4"
version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9"
checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
dependencies = [
"der",
"elliptic-curve",
@@ -683,7 +781,7 @@ dependencies = [
"ed25519",
"rand 0.7.3",
"serde",
"sha2",
"sha2 0.9.9",
"zeroize",
]
@@ -698,7 +796,7 @@ dependencies = [
"hex",
"rand_core 0.6.4",
"serde",
"sha2",
"sha2 0.9.9",
"zeroize",
]
@@ -710,16 +808,18 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "elliptic-curve"
version = "0.11.12"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6"
checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
dependencies = [
"base16ct",
"crypto-bigint",
"der",
"digest 0.10.7",
"ff",
"generic-array 0.14.6",
"group",
"pkcs8",
"rand_core 0.6.4",
"sec1",
"subtle 2.4.1",
@@ -778,9 +878,9 @@ dependencies = [
[[package]]
name = "ff"
version = "0.11.1"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924"
checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
dependencies = [
"rand_core 0.6.4",
"subtle 2.4.1",
@@ -974,9 +1074,9 @@ dependencies = [
[[package]]
name = "group"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89"
checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
dependencies = [
"ff",
"rand_core 0.6.4",
@@ -1020,7 +1120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
dependencies = [
"digest 0.9.0",
"hmac",
"hmac 0.11.0",
]
[[package]]
@@ -1033,6 +1133,15 @@ dependencies = [
"digest 0.9.0",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest 0.10.7",
]
[[package]]
name = "humantime"
version = "2.1.0"
@@ -1123,15 +1232,14 @@ dependencies = [
[[package]]
name = "k256"
version = "0.10.4"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d"
checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
dependencies = [
"cfg-if",
"ecdsa",
"elliptic-curve",
"sec1",
"sha2",
"sha2 0.10.6",
]
[[package]]
@@ -1267,7 +1375,7 @@ dependencies = [
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-storage-plus",
"cw-storage-plus 1.0.1",
"nym-coconut-bandwidth-contract-common",
"nym-multisig-contract-common",
"schemars",
@@ -1293,7 +1401,7 @@ dependencies = [
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-storage-plus 1.0.1",
"cw4",
"cw4-group",
"lazy_static",
@@ -1310,7 +1418,7 @@ name = "nym-coconut-dkg-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-utils",
"cw-utils 1.0.1",
"nym-contracts-common",
"nym-multisig-contract-common",
"schemars",
@@ -1319,7 +1427,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.5.0"
version = "0.4.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -1330,7 +1438,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.4.0"
version = "0.3.0"
dependencies = [
"bs58",
"ed25519-dalek",
@@ -1347,6 +1455,8 @@ dependencies = [
name = "nym-group-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-schema",
"cw-controllers",
"cw4",
"schemars",
"serde",
@@ -1361,8 +1471,8 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cosmwasm-storage",
"cw-storage-plus",
"cw2",
"cw-storage-plus 1.0.1",
"cw2 1.0.1",
"nym-contracts-common",
"nym-crypto",
"nym-mixnet-contract-common",
@@ -1378,7 +1488,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.6.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -1387,7 +1497,7 @@ dependencies = [
"nym-contracts-common",
"schemars",
"serde",
"serde-json-wasm",
"serde-json-wasm 0.4.1",
"serde_repr",
"thiserror",
"time",
@@ -1397,8 +1507,10 @@ dependencies = [
name = "nym-multisig-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"cw3",
"cw4",
"schemars",
@@ -1414,9 +1526,9 @@ dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw2",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"cw2 1.0.1",
"nym-contracts-common",
"nym-name-service-common",
"rand 0.8.5",
@@ -1455,7 +1567,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.3.0"
version = "0.2.0"
dependencies = [
"pem",
]
@@ -1465,18 +1577,14 @@ name = "nym-service-provider-directory"
version = "0.1.0"
dependencies = [
"anyhow",
"bs58",
"cosmwasm-std",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw2",
"cw-storage-plus 1.0.1",
"cw-utils 1.0.1",
"cw2 1.0.1",
"nym-contracts-common",
"nym-crypto",
"nym-service-provider-directory-common",
"rand_chacha 0.2.2",
"rstest",
"semver",
"serde",
"thiserror",
@@ -1488,12 +1596,8 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -1513,8 +1617,8 @@ dependencies = [
"cosmwasm-crypto",
"cosmwasm-derive",
"cosmwasm-std",
"cw-storage-plus",
"cw2",
"cw-storage-plus 1.0.1",
"cw2 1.0.1",
"hex",
"nym-contracts-common",
"nym-mixnet-contract-common",
@@ -1530,7 +1634,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.7.0"
version = "0.6.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -1588,13 +1692,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0"
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
dependencies = [
"der",
"spki",
"zeroize",
]
[[package]]
@@ -1820,12 +1923,12 @@ checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "rfc6979"
version = "0.1.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525"
checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
dependencies = [
"crypto-bigint",
"hmac",
"hmac 0.12.1",
"zeroize",
]
@@ -1934,10 +2037,11 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sec1"
version = "0.2.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1"
checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
dependencies = [
"base16ct",
"der",
"generic-array 0.14.6",
"pkcs8",
@@ -1969,6 +2073,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde-json-wasm"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
@@ -2027,12 +2140,23 @@ dependencies = [
]
[[package]]
name = "signature"
version = "1.4.0"
name = "sha2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"digest 0.9.0",
"cfg-if",
"cpufeatures",
"digest 0.10.7",
]
[[package]]
name = "signature"
version = "1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
dependencies = [
"digest 0.10.7",
"rand_core 0.6.4",
]
@@ -2060,20 +2184,20 @@ dependencies = [
"curve25519-dalek",
"digest 0.9.0",
"hkdf",
"hmac",
"hmac 0.11.0",
"lioness",
"log",
"rand 0.7.3",
"rand_distr",
"sha2",
"sha2 0.9.9",
"subtle 2.4.1",
]
[[package]]
name = "spki"
version = "0.5.4"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27"
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
dependencies = [
"base64ct",
"der",
+14 -13
View File
@@ -32,16 +32,17 @@ incremental = false
overflow-checks = true
[workspace.dependencies]
cosmwasm-crypto = "=1.0.0"
cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
cw-controllers = "=0.13.4"
cw-multi-test = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw-utils = "=0.13.4"
cw2 = "=0.13.4"
cw3 = "=0.13.4"
cw3-fixed-multisig = "=0.13.4"
cw4 = "=0.13.4"
cosmwasm-crypto = "=1.2.5"
cosmwasm-derive = "=1.2.5"
cosmwasm-schema = "=1.2.5"
cosmwasm-std = "=1.2.5"
cosmwasm-storage = "=1.2.5"
cw-controllers = "=1.0.1"
cw-multi-test = "=0.16.4"
cw-storage-plus = "=1.0.1"
cw-utils = "=1.0.1"
cw2 = "=1.0.1"
cw3 = "=1.0.1"
cw3-fixed-multisig = "=1.0.1"
cw4 = "=1.0.1"
cw20 = "=1.0.1"
@@ -30,7 +30,7 @@ impl<'a> IndexList<ContractVKShare> for VkShareIndex<'a> {
pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare, VkShareIndex<'a>> {
let indexes = VkShareIndex {
epoch_id: MultiIndex::new(
|d| d.epoch_id,
|_pk, d| d.epoch_id,
VK_SHARES_PK_NAMESPACE,
VK_SHARES_EPOCH_ID_IDX_NAMESPACE,
),
+1 -1
View File
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
use cw3_flex_multisig::state::CONFIG;
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
use nym_multisig_contract_common::error::ContractError;
use nym_multisig_contract_common::state::CONFIG;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -45,6 +45,8 @@ fn spend_credential_creates_proposal() {
threshold: Threshold::AbsolutePercentage {
percentage: Decimal::from_ratio(2u128, 3u128),
},
executor: None,
proposal_deposit: None,
max_voting_period: Duration::Height(1000),
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(),
@@ -52,6 +52,8 @@ fn dkg_proposal() {
threshold: Threshold::AbsolutePercentage {
percentage: Decimal::from_ratio(1u128, 1u128),
},
executor: None,
proposal_deposit: None,
max_voting_period: Duration::Time(1000),
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(),
+3 -3
View File
@@ -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.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" }
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" }
cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
+1 -1
View File
@@ -520,7 +520,7 @@ mod tests {
.delegations
.iter()
.filter(|d| d.proxy.is_some())
.all(|d| d.proxy.as_ref().unwrap() == &vesting_contract));
.all(|d| d.proxy.as_ref().unwrap() == vesting_contract));
// now make sure that if we do it in paged manner, we'll get exactly the same result
let per_page = Some(15);
+2 -2
View File
@@ -27,12 +27,12 @@ impl<'a> IndexList<Delegation> for DelegationIndex<'a> {
pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
let indexes = DelegationIndex {
owner: MultiIndex::new(
|d| d.owner.clone(),
|_pk, d| d.owner.clone(),
DELEGATION_PK_NAMESPACE,
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d| d.mix_id,
|_pk, d| d.mix_id,
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
+1 -1
View File
@@ -14,7 +14,7 @@ mod mixnet_contract_settings;
mod mixnodes;
mod queued_migrations;
mod rewards;
pub mod signing;
mod signing;
mod support;
#[cfg(feature = "contract-testing")]
+2 -2
View File
@@ -39,12 +39,12 @@ pub(crate) fn unbonded_mixnodes<'a>(
) -> IndexedMap<'a, MixId, UnbondedMixnode, UnbondedMixnodeIndex<'a>> {
let indexes = UnbondedMixnodeIndex {
owner: MultiIndex::new(
|d| d.owner.clone(),
|_pk, d| d.owner.clone(),
UNBONDED_MIXNODES_PK_NAMESPACE,
UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE,
),
identity_key: MultiIndex::new(
|d| d.identity_key.clone(),
|_pk, d| d.identity_key.clone(),
UNBONDED_MIXNODES_PK_NAMESPACE,
UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE,
),
+2 -2
View File
@@ -185,7 +185,7 @@ pub(crate) fn _try_withdraw_operator_reward(
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
// otherwise, we don't care
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy == &vesting_contract {
if proxy == vesting_contract {
let msg = VestingContractExecuteMsg::TrackReward {
amount: reward.clone(),
address: owner.clone().into_string(),
@@ -271,7 +271,7 @@ pub(crate) fn _try_withdraw_delegator_reward(
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
// otherwise, we don't care
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy == &vesting_contract {
if proxy == vesting_contract {
let msg = VestingContractExecuteMsg::TrackReward {
amount: reward.clone(),
address: owner.clone().into_string(),
+2 -2
View File
@@ -298,7 +298,7 @@ pub(crate) fn ensure_is_authorized(
sender: &Addr,
storage: &dyn Storage,
) -> Result<(), MixnetContractError> {
if sender != &crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? {
if sender != crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? {
return Err(MixnetContractError::Unauthorized);
}
Ok(())
@@ -309,7 +309,7 @@ pub(crate) fn ensure_can_advance_epoch(
storage: &dyn Storage,
) -> Result<EpochStatus, MixnetContractError> {
let epoch_status = crate::interval::storage::current_epoch_status(storage)?;
if sender != &epoch_status.being_advanced_by {
if sender != epoch_status.being_advanced_by {
// well, we know we're going to throw an error now,
// but we might as well also check if we're even a validator
// to return a possibly better error message
@@ -1,8 +1,8 @@
[package]
name = "cw3-flex-multisig"
version = "0.13.1"
version = "1.0.0"
authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"]
edition = "2018"
edition = "2021"
description = "Implementing cw3 with multiple voting patterns and dynamic groups"
license = "Apache-2.0"
repository = "https://github.com/CosmWasm/cw-plus"
@@ -13,6 +13,7 @@ documentation = "https://docs.cosmwasm.com"
crate-type = ["cdylib", "rlib"]
[features]
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all instantiate/execute/query exports
library = []
@@ -22,15 +23,15 @@ cw2 = { workspace = true }
cw3 = { workspace = true }
cw3-fixed-multisig = { workspace = true, features = ["library"] }
cw4 = { workspace = true }
cw20 = { workspace = true }
cw-storage-plus = { workspace = true }
cosmwasm-std = { workspace = true }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
nym-multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" }
[dev-dependencies]
cosmwasm-schema = { version = "1.0.0" }
cw4-group = { path = "../cw4-group", version = "0.13.4" }
cw4-group = { path = "../cw4-group", version = "1.0.0" }
cw-multi-test = { workspace = true }
cw20-base = "1.0.0"
File diff suppressed because it is too large Load Diff
@@ -1,2 +1,25 @@
/*!
This builds on [`cw3_fixed_multisig`] with a more
powerful implementation of the [cw3 spec](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw3/README.md).
It is a multisig contract that is backed by a
[cw4 (group)](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw4/README.md) contract, which independently
maintains the voter set.
This provides 2 main advantages:
* You can create two different multisigs with different voting thresholds
backed by the same group. Thus, you can have a 50% vote, and a 67% vote
that always use the same voter set, but can take other actions.
* TODO: It allows dynamic multisig groups.
In addition to the dynamic voting set, the main difference with the native
Cosmos SDK multisig, is that it aggregates the signatures on chain, with
visible proposals (like `x/gov` in the Cosmos SDK), rather than requiring
signers to share signatures off chain.
For more information on this contract, please check out the
[README](https://github.com/CosmWasm/cw-plus/blob/main/contracts/cw3-flex-multisig/README.md).
*/
pub mod contract;
pub mod state;
@@ -1,20 +0,0 @@
use cosmwasm_std::Addr;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cw4::Cw4Contract;
use cw_storage_plus::Item;
use cw_utils::{Duration, Threshold};
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Config {
pub threshold: Threshold,
pub max_voting_period: Duration,
// Total weight and voters are queried from this contract
pub group_addr: Cw4Contract,
pub coconut_bandwidth_addr: Addr,
pub coconut_dkg_addr: Addr,
}
// unique items
pub const CONFIG: Item<Config> = Item::new("config");
+3 -3
View File
@@ -1,5 +1,5 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
wasm = "build --release --lib --target wasm32-unknown-unknown"
wasm-debug = "build --lib --target wasm32-unknown-unknown"
unit-test = "test --lib"
schema = "run --example schema"
schema = "run --bin schema"
+5 -5
View File
@@ -1,8 +1,8 @@
[package]
name = "cw4-group"
version = "0.13.4"
version = "1.0.0"
authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"]
edition = "2018"
edition = "2021"
description = "Simple cw4 implementation of group membership controlled by admin "
license = "Apache-2.0"
repository = "https://github.com/CosmWasm/cw-plus"
@@ -20,6 +20,8 @@ exclude = [
crate-type = ["cdylib", "rlib"]
[features]
# for more explicit tests, cargo test --features=backtraces
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all instantiate/execute/query exports
library = []
@@ -31,10 +33,8 @@ cw2 = { workspace = true }
cw4 = { workspace = true }
cw-controllers = { workspace = true }
cw-storage-plus = { workspace = true }
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
[dev-dependencies]
cosmwasm-schema = { workspace = true }
+32 -367
View File
@@ -2,7 +2,7 @@
use cosmwasm_std::entry_point;
use cosmwasm_std::{
attr, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult,
SubMsg,
SubMsg, Uint64,
};
use cw2::set_contract_version;
use cw4::{
@@ -13,6 +13,7 @@ use cw_storage_plus::Bound;
use cw_utils::maybe_addr;
use crate::error::ContractError;
use crate::helpers::validate_unique_members;
use crate::state::{ADMIN, HOOKS, MEMBERS, TOTAL};
use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
@@ -39,21 +40,25 @@ pub fn instantiate(
pub fn create(
mut deps: DepsMut,
admin: Option<String>,
members: Vec<Member>,
mut members: Vec<Member>,
height: u64,
) -> Result<(), ContractError> {
validate_unique_members(&mut members)?;
let members = members; // let go of mutability
let admin_addr = admin
.map(|admin| deps.api.addr_validate(&admin))
.transpose()?;
ADMIN.set(deps.branch(), admin_addr)?;
let mut total = 0u64;
let mut total = Uint64::zero();
for member in members.into_iter() {
total += member.weight;
let member_weight = Uint64::from(member.weight);
total = total.checked_add(member_weight)?;
let member_addr = deps.api.addr_validate(&member.addr)?;
MEMBERS.save(deps.storage, &member_addr, &member.weight, height)?;
MEMBERS.save(deps.storage, &member_addr, &member_weight.u64(), height)?;
}
TOTAL.save(deps.storage, &total)?;
TOTAL.save(deps.storage, &total.u64(), height)?;
Ok(())
}
@@ -115,20 +120,23 @@ pub fn update_members(
deps: DepsMut,
height: u64,
sender: Addr,
to_add: Vec<Member>,
mut to_add: Vec<Member>,
to_remove: Vec<String>,
) -> Result<MemberChangedHookMsg, ContractError> {
validate_unique_members(&mut to_add)?;
let to_add = to_add; // let go of mutability
ADMIN.assert_admin(deps.as_ref(), &sender)?;
let mut total = TOTAL.load(deps.storage)?;
let mut total = Uint64::from(TOTAL.load(deps.storage)?);
let mut diffs: Vec<MemberDiff> = vec![];
// add all new members and update total
for add in to_add.into_iter() {
let add_addr = deps.api.addr_validate(&add.addr)?;
MEMBERS.update(deps.storage, &add_addr, height, |old| -> StdResult<_> {
total -= old.unwrap_or_default();
total += add.weight;
total = total.checked_sub(Uint64::from(old.unwrap_or_default()))?;
total = total.checked_add(Uint64::from(add.weight))?;
diffs.push(MemberDiff::new(add.addr, old, Some(add.weight)));
Ok(add.weight)
})?;
@@ -140,12 +148,12 @@ pub fn update_members(
// Only process this if they were actually in the list before
if let Some(weight) = old {
diffs.push(MemberDiff::new(remove, Some(weight), None));
total -= weight;
total = total.checked_sub(Uint64::from(weight))?;
MEMBERS.remove(deps.storage, &remove_addr, height)?;
}
}
TOTAL.save(deps.storage, &total)?;
TOTAL.save(deps.storage, &total.u64(), height)?;
Ok(MemberChangedHookMsg { diffs })
}
@@ -157,20 +165,26 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
at_height: height,
} => to_binary(&query_member(deps, addr, height)?),
QueryMsg::ListMembers { start_after, limit } => {
to_binary(&list_members(deps, start_after, limit)?)
to_binary(&query_list_members(deps, start_after, limit)?)
}
QueryMsg::TotalWeight { at_height: height } => {
to_binary(&query_total_weight(deps, height)?)
}
QueryMsg::TotalWeight {} => to_binary(&query_total_weight(deps)?),
QueryMsg::Admin {} => to_binary(&ADMIN.query_admin(deps)?),
QueryMsg::Hooks {} => to_binary(&HOOKS.query_hooks(deps)?),
}
}
fn query_total_weight(deps: Deps) -> StdResult<TotalWeightResponse> {
let weight = TOTAL.load(deps.storage)?;
pub fn query_total_weight(deps: Deps, height: Option<u64>) -> StdResult<TotalWeightResponse> {
let weight = match height {
Some(h) => TOTAL.may_load_at_height(deps.storage, h),
None => TOTAL.may_load(deps.storage),
}?
.unwrap_or_default();
Ok(TotalWeightResponse { weight })
}
fn query_member(deps: Deps, addr: String, height: Option<u64>) -> StdResult<MemberResponse> {
pub fn query_member(deps: Deps, addr: String, height: Option<u64>) -> StdResult<MemberResponse> {
let addr = deps.api.addr_validate(&addr)?;
let weight = match height {
Some(h) => MEMBERS.may_load_at_height(deps.storage, &addr, h),
@@ -183,7 +197,7 @@ fn query_member(deps: Deps, addr: String, height: Option<u64>) -> StdResult<Memb
const MAX_LIMIT: u32 = 30;
const DEFAULT_LIMIT: u32 = 10;
fn list_members(
pub fn query_list_members(
deps: Deps,
start_after: Option<String>,
limit: Option<u32>,
@@ -205,352 +219,3 @@ fn list_members(
Ok(MemberListResponse { members })
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{from_slice, Api, OwnedDeps, Querier, Storage};
use cw4::{member_key, TOTAL_KEY};
use cw_controllers::{AdminError, HookError};
const INIT_ADMIN: &str = "juan";
const USER1: &str = "somebody";
const USER2: &str = "else";
const USER3: &str = "funny";
fn do_instantiate(deps: DepsMut) {
let msg = InstantiateMsg {
admin: Some(INIT_ADMIN.into()),
members: vec![
Member {
addr: USER1.into(),
weight: 11,
},
Member {
addr: USER2.into(),
weight: 6,
},
],
};
let info = mock_info("creator", &[]);
instantiate(deps, mock_env(), info, msg).unwrap();
}
#[test]
fn proper_instantiation() {
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
// it worked, let's query the state
let res = ADMIN.query_admin(deps.as_ref()).unwrap();
assert_eq!(Some(INIT_ADMIN.into()), res.admin);
let res = query_total_weight(deps.as_ref()).unwrap();
assert_eq!(17, res.weight);
}
#[test]
fn try_member_queries() {
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
let member1 = query_member(deps.as_ref(), USER1.into(), None).unwrap();
assert_eq!(member1.weight, Some(11));
let member2 = query_member(deps.as_ref(), USER2.into(), None).unwrap();
assert_eq!(member2.weight, Some(6));
let member3 = query_member(deps.as_ref(), USER3.into(), None).unwrap();
assert_eq!(member3.weight, None);
let members = list_members(deps.as_ref(), None, None).unwrap();
assert_eq!(members.members.len(), 2);
// TODO: assert the set is proper
}
fn assert_users<S: Storage, A: Api, Q: Querier>(
deps: &OwnedDeps<S, A, Q>,
user1_weight: Option<u64>,
user2_weight: Option<u64>,
user3_weight: Option<u64>,
height: Option<u64>,
) {
let member1 = query_member(deps.as_ref(), USER1.into(), height).unwrap();
assert_eq!(member1.weight, user1_weight);
let member2 = query_member(deps.as_ref(), USER2.into(), height).unwrap();
assert_eq!(member2.weight, user2_weight);
let member3 = query_member(deps.as_ref(), USER3.into(), height).unwrap();
assert_eq!(member3.weight, user3_weight);
// this is only valid if we are not doing a historical query
if height.is_none() {
// compute expected metrics
let weights = vec![user1_weight, user2_weight, user3_weight];
let sum: u64 = weights.iter().map(|x| x.unwrap_or_default()).sum();
let count = weights.iter().filter(|x| x.is_some()).count();
// TODO: more detailed compare?
let members = list_members(deps.as_ref(), None, None).unwrap();
assert_eq!(count, members.members.len());
let total = query_total_weight(deps.as_ref()).unwrap();
assert_eq!(sum, total.weight); // 17 - 11 + 15 = 21
}
}
#[test]
fn add_new_remove_old_member() {
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
// add a new one and remove existing one
let add = vec![Member {
addr: USER3.into(),
weight: 15,
}];
let remove = vec![USER1.into()];
// non-admin cannot update
let height = mock_env().block.height;
let err = update_members(
deps.as_mut(),
height + 5,
Addr::unchecked(USER1),
add.clone(),
remove.clone(),
)
.unwrap_err();
assert_eq!(err, AdminError::NotAdmin {}.into());
// Test the values from instantiate
assert_users(&deps, Some(11), Some(6), None, None);
// Note all values were set at height, the beginning of that block was all None
assert_users(&deps, None, None, None, Some(height));
// This will get us the values at the start of the block after instantiate (expected initial values)
assert_users(&deps, Some(11), Some(6), None, Some(height + 1));
// admin updates properly
update_members(
deps.as_mut(),
height + 10,
Addr::unchecked(INIT_ADMIN),
add,
remove,
)
.unwrap();
// updated properly
assert_users(&deps, None, Some(6), Some(15), None);
// snapshot still shows old value
assert_users(&deps, Some(11), Some(6), None, Some(height + 1));
}
#[test]
fn add_old_remove_new_member() {
// add will over-write and remove have no effect
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
// add a new one and remove existing one
let add = vec![Member {
addr: USER1.into(),
weight: 4,
}];
let remove = vec![USER3.into()];
// admin updates properly
let height = mock_env().block.height;
update_members(
deps.as_mut(),
height,
Addr::unchecked(INIT_ADMIN),
add,
remove,
)
.unwrap();
assert_users(&deps, Some(4), Some(6), None, None);
}
#[test]
fn add_and_remove_same_member() {
// add will over-write and remove have no effect
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
// USER1 is updated and remove in the same call, we should remove this an add member3
let add = vec![
Member {
addr: USER1.into(),
weight: 20,
},
Member {
addr: USER3.into(),
weight: 5,
},
];
let remove = vec![USER1.into()];
// admin updates properly
let height = mock_env().block.height;
update_members(
deps.as_mut(),
height,
Addr::unchecked(INIT_ADMIN),
add,
remove,
)
.unwrap();
assert_users(&deps, None, Some(6), Some(5), None);
}
#[test]
fn add_remove_hooks() {
// add will over-write and remove have no effect
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
assert!(hooks.hooks.is_empty());
let contract1 = String::from("hook1");
let contract2 = String::from("hook2");
let add_msg = ExecuteMsg::AddHook {
addr: contract1.clone(),
};
// non-admin cannot add hook
let user_info = mock_info(USER1, &[]);
let err = execute(
deps.as_mut(),
mock_env(),
user_info.clone(),
add_msg.clone(),
)
.unwrap_err();
assert_eq!(err, HookError::Admin(AdminError::NotAdmin {}).into());
// admin can add it, and it appears in the query
let admin_info = mock_info(INIT_ADMIN, &[]);
let _ = execute(
deps.as_mut(),
mock_env(),
admin_info.clone(),
add_msg.clone(),
)
.unwrap();
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
assert_eq!(hooks.hooks, vec![contract1.clone()]);
// cannot remove a non-registered contract
let remove_msg = ExecuteMsg::RemoveHook {
addr: contract2.clone(),
};
let err = execute(deps.as_mut(), mock_env(), admin_info.clone(), remove_msg).unwrap_err();
assert_eq!(err, HookError::HookNotRegistered {}.into());
// add second contract
let add_msg2 = ExecuteMsg::AddHook {
addr: contract2.clone(),
};
let _ = execute(deps.as_mut(), mock_env(), admin_info.clone(), add_msg2).unwrap();
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
assert_eq!(hooks.hooks, vec![contract1.clone(), contract2.clone()]);
// cannot re-add an existing contract
let err = execute(deps.as_mut(), mock_env(), admin_info.clone(), add_msg).unwrap_err();
assert_eq!(err, HookError::HookAlreadyRegistered {}.into());
// non-admin cannot remove
let remove_msg = ExecuteMsg::RemoveHook { addr: contract1 };
let err = execute(deps.as_mut(), mock_env(), user_info, remove_msg.clone()).unwrap_err();
assert_eq!(err, HookError::Admin(AdminError::NotAdmin {}).into());
// remove the original
let _ = execute(deps.as_mut(), mock_env(), admin_info, remove_msg).unwrap();
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
assert_eq!(hooks.hooks, vec![contract2]);
}
#[test]
fn hooks_fire() {
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
assert!(hooks.hooks.is_empty());
let contract1 = String::from("hook1");
let contract2 = String::from("hook2");
// register 2 hooks
let admin_info = mock_info(INIT_ADMIN, &[]);
let add_msg = ExecuteMsg::AddHook {
addr: contract1.clone(),
};
let add_msg2 = ExecuteMsg::AddHook {
addr: contract2.clone(),
};
for msg in vec![add_msg, add_msg2] {
let _ = execute(deps.as_mut(), mock_env(), admin_info.clone(), msg).unwrap();
}
// make some changes - add 3, remove 2, and update 1
// USER1 is updated and remove in the same call, we should remove this an add member3
let add = vec![
Member {
addr: USER1.into(),
weight: 20,
},
Member {
addr: USER3.into(),
weight: 5,
},
];
let remove = vec![USER2.into()];
let msg = ExecuteMsg::UpdateMembers { remove, add };
// admin updates properly
assert_users(&deps, Some(11), Some(6), None, None);
let res = execute(deps.as_mut(), mock_env(), admin_info, msg).unwrap();
assert_users(&deps, Some(20), None, Some(5), None);
// ensure 2 messages for the 2 hooks
assert_eq!(res.messages.len(), 2);
// same order as in the message (adds first, then remove)
let diffs = vec![
MemberDiff::new(USER1, Some(11), Some(20)),
MemberDiff::new(USER3, None, Some(5)),
MemberDiff::new(USER2, Some(6), None),
];
let hook_msg = MemberChangedHookMsg { diffs };
let msg1 = SubMsg::new(hook_msg.clone().into_cosmos_msg(contract1).unwrap());
let msg2 = SubMsg::new(hook_msg.into_cosmos_msg(contract2).unwrap());
assert_eq!(res.messages, vec![msg1, msg2]);
}
#[test]
fn raw_queries_work() {
// add will over-write and remove have no effect
let mut deps = mock_dependencies();
do_instantiate(deps.as_mut());
// get total from raw key
let total_raw = deps.storage.get(TOTAL_KEY.as_bytes()).unwrap();
let total: u64 = from_slice(&total_raw).unwrap();
assert_eq!(17, total);
// get member votes from raw key
let member2_raw = deps.storage.get(&member_key(USER2)).unwrap();
let member2: u64 = from_slice(&member2_raw).unwrap();
assert_eq!(6, member2);
// and execute misses
let member3_raw = deps.storage.get(&member_key(USER3));
assert_eq!(None, member3_raw);
}
}
+10 -4
View File
@@ -1,19 +1,25 @@
use cosmwasm_std::StdError;
use cosmwasm_std::{OverflowError, StdError};
use thiserror::Error;
use cw_controllers::{AdminError, HookError};
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error(transparent)]
#[error("{0}")]
Std(#[from] StdError),
#[error(transparent)]
#[error("{0}")]
Hook(#[from] HookError),
#[error(transparent)]
#[error("{0}")]
Admin(#[from] AdminError),
#[error("{0}")]
Overflow(#[from] OverflowError),
#[error("Unauthorized")]
Unauthorized {},
#[error("Message contained duplicate member: {member}")]
DuplicateMember { member: String },
}
+17 -3
View File
@@ -1,17 +1,17 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg};
use cw4::{Cw4Contract, Member};
use crate::ContractError;
use nym_group_contract_common::msg::ExecuteMsg;
/// Cw4GroupContract is a wrapper around Cw4Contract that provides a lot of helpers
/// for working with cw4-group contracts.
///
/// It extends Cw4Contract to add the extra calls from cw4-group.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[cw_serde]
pub struct Cw4GroupContract(pub Cw4Contract);
impl Deref for Cw4GroupContract {
@@ -41,3 +41,17 @@ impl Cw4GroupContract {
self.encode_msg(msg)
}
}
/// Sorts the slice and verifies all member addresses are unique.
pub fn validate_unique_members(members: &mut [Member]) -> Result<(), ContractError> {
members.sort_by(|a, b| a.addr.cmp(&b.addr));
for (a, b) in members.iter().zip(members.iter().skip(1)) {
if a.addr == b.addr {
return Err(ContractError::DuplicateMember {
member: a.addr.clone(),
});
}
}
Ok(())
}
+19
View File
@@ -1,6 +1,25 @@
/*!
This is a basic implementation of the [cw4 spec](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw4/README.md).
It fulfills all elements of the spec, including the raw query lookups,
and it designed to be used as a backing storage for
[cw3 compliant contracts](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw3/README.md).
It stores a set of members along with an admin, and allows the admin to
update the state. Raw queries (intended for cross-contract queries)
can check a given member address and the total weight. Smart queries (designed
for client API) can do the same, and also query the admin address as well as
paginate over all members.
For more information on this contract, please check out the
[README](https://github.com/CosmWasm/cw-plus/blob/main/contracts/cw4-group/README.md).
*/
pub mod contract;
pub mod error;
pub mod helpers;
pub mod state;
pub use crate::error::ContractError;
#[cfg(test)]
mod tests;
+16 -8
View File
@@ -1,16 +1,24 @@
use cosmwasm_std::Addr;
use cw4::TOTAL_KEY;
use cw4::{
MEMBERS_CHANGELOG, MEMBERS_CHECKPOINTS, MEMBERS_KEY, TOTAL_KEY, TOTAL_KEY_CHANGELOG,
TOTAL_KEY_CHECKPOINTS,
};
use cw_controllers::{Admin, Hooks};
use cw_storage_plus::{Item, SnapshotMap, Strategy};
use cw_storage_plus::{SnapshotItem, SnapshotMap, Strategy};
pub const ADMIN: Admin = Admin::new("admin");
pub const HOOKS: Hooks = Hooks::new("cw4-hooks");
pub const TOTAL: Item<u64> = Item::new(TOTAL_KEY);
pub const MEMBERS: SnapshotMap<&Addr, u64> = SnapshotMap::new(
cw4::MEMBERS_KEY,
cw4::MEMBERS_CHECKPOINTS,
cw4::MEMBERS_CHANGELOG,
pub const TOTAL: SnapshotItem<u64> = SnapshotItem::new(
TOTAL_KEY,
TOTAL_KEY_CHECKPOINTS,
TOTAL_KEY_CHANGELOG,
Strategy::EveryBlock,
);
pub const MEMBERS: SnapshotMap<&Addr, u64> = SnapshotMap::new(
MEMBERS_KEY,
MEMBERS_CHECKPOINTS,
MEMBERS_CHANGELOG,
Strategy::EveryBlock,
);

Some files were not shown because too many files have changed in this diff Show More