Compare commits

...

28 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu b5213f3908 Hide coconut runtime flags (#2990) 2023-02-08 13:00:58 +02:00
Bogdan-Ștefan Neacşu 590a4644e2 Skip errors on blind sign within threshold (#2976) 2023-02-08 11:54:12 +02:00
Bogdan-Ștefan Neacşu f8345e03c6 Feature/dkg reshare (#2936)
* Add resharing parameter

* Fix equality of dealers and members

* Contract resharing handling

* Dealer verification unit test

* Dealing commit unit test

* Epoch state unit tests

* Fix clippy

* Fmt

* Query initial dealer data

* Resharing nym-api changes

* Implement the mockups for nym-api dkg tests

* Dealing test

* Vk unit test

* Fix skipping vk submission

* Fix clippy

* Missing dealing for noninitial resharing dealer

* Check master vk holds after resharing on nym-apis

* Update changelog

* Fix clippy
2023-02-07 12:56:08 +02:00
Jon Häggblad 6675ea0249 Remove all the .DS_Store files and add to gitignore (#2974) 2023-02-07 11:45:40 +01:00
Jędrzej Stuczyński 56f48e6e41 Feature/service provider interface (#2934)
* added additional workspace-wide dependencies

* Added conditional serialization on 'BinaryBuildInformationOwned'

* initial framework for service provider messages

* updated request/response tags to account for existing variants of Socks5Message

* handling legacy deserialization

* another serialization revamp to account for legacy version

* legacy client working with versioned network requester

* socks5 client deserializing responses into updated structures

* using new structures for sending in socks5 client

* SendRequest wrapper for Request::Send variant

* created named fields for all variants of 'ControllerCommand'

* Versioning socks5 requests + moving to proper Socks5Request struct

* Updated backwards compatible Socks5Response

* unused imports

* poc ServiceProvider trait

* wip

* implemented 'ServiceProvider' trait on the Network Requester

* Socks5RequestError

* added properly serialized ErrorResponse to ControlResponse

* fixed version serialization + feature selection

* handling of version control requests

* improved SocksProxyError by providing more concrete variants and removing generic case

* got rid of ServiceProviderClient trait and wrote simple example showing control requests

* tests for serialisation backwards compatibility

* post-merge fixes due to method renaming

* enum boxing to make clippy happier

* making sure to not drop buffer channel when starting `DirectClient`

* Using nym-sdk in the example

* Replaced printing version to stdout with proper log call
2023-02-07 09:49:19 +00:00
Pierre Dommerc 1733aaa4cc NC Android - setup APK publish (#2967)
* ci(nc-android): init workflow apk build & release

* ci(nc-android): setup nc android project for APK publish

* docs(nc-android): update readme

* ci(nc-android): add note in the workflow
2023-02-06 10:56:39 +01:00
Mark Sinclair 78aa07360c Update build-and-upload-binaries-ci.yml 2023-02-03 17:10:13 +00:00
Mark Sinclair 4e52478c7a Update build-and-upload-binaries-ci.yml 2023-02-03 16:54:49 +00:00
Mark Sinclair 7c77665a37 Update build-and-upload-binaries-ci.yml 2023-02-03 16:36:44 +00:00
Mark Sinclair 72880b9764 Update build-and-upload-binaries-ci.yml 2023-02-03 16:14:29 +00:00
Mark Sinclair fd1c0ae62b Update build-and-upload-binaries-ci.yml 2023-02-03 12:03:13 +00:00
Mark Sinclair bd7399091b Update build-and-upload-binaries-ci.yml 2023-02-03 11:57:32 +00:00
Mark Sinclair 00fad44e2e Update build-and-upload-binaries-ci.yml 2023-02-03 11:46:07 +00:00
Mark Sinclair ea33c332ee GitHub Actions: add action to build and upload binaries to CI server 2023-02-03 11:35:03 +00:00
Bogdan-Ștefan Neacșu b39f8af8d0 Fix flaky dkg test 2023-02-03 12:29:21 +02:00
dependabot[bot] a400463b7e build(deps): bump ua-parser-js in /nym-wallet/webdriver (#2909)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.28 to 0.7.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.28...0.7.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 10:00:09 +00:00
dependabot[bot] 82928af64c build(deps): bump http-cache-semantics in /nym-wallet/webdriver (#2960)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 09:59:08 +00:00
dependabot[bot] c5891f546c build(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 (#2961)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 09:58:34 +00:00
Tommy Verrall 580b677489 Binary checker, introduction 2023-02-03 08:46:40 -01:00
Jędrzej Stuczyński 2093789c5c Added an option to set custom 'host' for the native client (#2939)
* Added an option to set custom 'host' for the native client

* Changelog entry
2023-02-02 16:38:39 +00:00
Pierre Dommerc d09864b99f build(nc-android): prepare for apk release (#2943)
* chore(nc-android): prepare for production build

* refactor(nc-android): remove dead code

* feat(nc-android): update native color theme

* feat(nc-android): update native color theme

* build(nc-android): fix rfd version issue

* build(nc-android): fix dist dir no such file error

* fix(nc-android): post rebase changes
2023-02-02 15:18:58 +01:00
Bogdan-Ștefan Neacşu f6e8278592 Fix typo during merge back to develop (#2956) 2023-02-02 13:55:58 +02:00
cgi-bin/ b085a8a636 typo: electrum (#2954) 2023-02-02 09:34:08 +00:00
farbanas e6ce531aeb fix: formatting 2023-02-01 17:16:53 +01:00
farbanas 1a860eb3f5 fix: wrong parameter type for addresses in generator commands (should be AccountId instead of String) 2023-02-01 12:45:51 +01:00
Fran Arbanas 09cfd9ff05 Update CHANGELOG.md 2023-01-31 14:42:16 +00:00
Bogdan-Ștefan Neacșu 2c88ca137a Resolve merge conflict 2023-01-31 16:19:34 +02:00
farbanas ca3bfc859a merge conflicts 2023-01-31 15:09:35 +01:00
293 changed files with 6008 additions and 4234 deletions
@@ -0,0 +1,113 @@
name: Build and upload binaries to CI
on:
workflow_dispatch:
push:
paths:
- 'clients/**'
- 'common/**'
- 'contracts/**'
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
pull_request:
paths:
- 'clients/**'
- 'common/**'
- 'contracts/**'
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Prepare build output directory
shell: bash
env:
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
run: |
rm -rf ci-builds || true
mkdir -p $OUTPUT_DIR
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- name: Build release contracts
run: make wasm
- name: Prepare build output
shell: bash
env:
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
run: |
cp target/release/nym-client $OUTPUT_DIR
cp target/release/nym-gateway $OUTPUT_DIR
cp target/release/nym-mixnode $OUTPUT_DIR
cp target/release/nym-socks5-client $OUTPUT_DIR
cp target/release/nym-api $OUTPUT_DIR
cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
- name: Deploy branch to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "ci-builds/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
EXCLUDE: "/dist/, /node_modules/"
+127
View File
@@ -0,0 +1,127 @@
name: NC Android APK Release
on:
workflow_dispatch:
push:
branches:
- "release/nc-android-v[0-9].[0-9].[0-9]*"
jobs:
build:
name: Build APK
runs-on: ubuntu-latest
env:
ANDROID_HOME: ${{ github.workspace }}/android-sdk
NDK_VERSION: 25.1.8937393
NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/${{ env.NDK_VERSION }}
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 \
libwebkit2gtk-4.0-dev \
build-essential \
curl \
wget \
libssl-dev \
libgtk-3-dev \
squashfs-tools \
libayatana-appindicator3-dev \
librsvg2-dev
- 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 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: Checkout
uses: actions/checkout@v3
- name: Build frontend code
run: |
yarn install --frozen-lockfile
yarn build
yarn workspace @nym/nym-connect-android webpack:prod
- name: Build APK
working-directory: nym-connect-android
env:
WRY_ANDROID_PACKAGE: net.nymtech.nym_connect_android
WRY_ANDROID_LIBRARY: nym_connect_android
# TODO build with release profile (--release), it will requires
# to sign the APK. For now build with debug profile to avoid that
run: cargo tauri android build --debug --apk
# TODO add the version number to APK name
- name: Rename APK artifact
run: |
mv nym-connect-android/src-tauri/gen/android/nym_connect_android/app/build/outputs/apk/universal/debug/app-universal-debug.apk \
nym-connect-debug.apk
- name: Upload APK artifact
uses: actions/upload-artifact@v3
with:
name: nc-apk-debug
path: nym-connect-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: ???
+1 -1
View File
@@ -42,4 +42,4 @@ envs/qwerty.env
Cargo.lock Cargo.lock
nym-connect/Cargo.lock nym-connect/Cargo.lock
.parcel-cache .parcel-cache
.DS_Store **/.DS_Store
+25 -8
View File
@@ -4,18 +4,35 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
# [Unreleased] # [Unreleased]
### Added
- remove coconut feature and unify builds ([#2890])
- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2939]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section.
- dkg resharing mode ([#2936])
[#2890]: https://github.com/nymtech/nym/pull/2890
[#2939]: https://github.com/nymtech/nym/pull/2939
[#2936]: https://github.com/nymtech/nym/pull/2936
# [v1.1.8] (2023-01-31)
### Added ### Added
- dkg rerun from scratch and dkg-specific epochs ([#2839]) - Rust SDK - Support SURBS (anonymous send + storage) ([#2754])
- nym-sdk: add support for surb storage ([#2870]) - dkg rerun from scratch and dkg-specific epochs ([#2810])
- nym-sdk: enable reply-SURBs by default ([#2874]) - Rename `'initial_supply'` field to `'total_supply'` in the circulating supply endpoint ([#2931])
- remove coconut feature and unify builds ([#2890]) - Circulating supply api endpoint (read the note inside before testing/deploying) ([#1902])
### Changed
- nym-api: an `--id` flag is now always explicitly required ([#2873])
[#2754]: https://github.com/nymtech/nym/issues/2754
[#2810]: https://github.com/nymtech/nym/issues/2810
[#2931]: https://github.com/nymtech/nym/issues/2931
[#1902]: https://github.com/nymtech/nym/issues/1902
[#2873]: https://github.com/nymtech/nym/issues/2873
[#2839]: https://github.com/nymtech/nym/pull/2839
[#2870]: https://github.com/nymtech/nym/pull/2870
[#2874]: https://github.com/nymtech/nym/pull/2874
[#2890]: https://github.com/nymtech/nym/pull/2890
# [v1.1.7] (2023-01-24) # [v1.1.7] (2023-01-24)
Generated
+34 -10
View File
@@ -458,6 +458,7 @@ dependencies = [
name = "build-information" name = "build-information"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"serde",
"vergen 7.5.0", "vergen 7.5.0",
] ]
@@ -701,7 +702,7 @@ dependencies = [
[[package]] [[package]]
name = "client-core" name = "client-core"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"client-connections", "client-connections",
@@ -1891,7 +1892,7 @@ dependencies = [
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 4.1.3", "clap 4.1.3",
@@ -3314,6 +3315,7 @@ dependencies = [
"cw4", "cw4",
"schemars", "schemars",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]
@@ -3437,7 +3439,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-api" name = "nym-api"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3535,7 +3537,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.13.1", "base64 0.13.1",
@@ -3593,7 +3595,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"build-information", "build-information",
"clap 4.1.3", "clap 4.1.3",
@@ -3633,7 +3635,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-gateway" name = "nym-gateway"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3685,7 +3687,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.1.8" version = "1.1.9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atty", "atty",
@@ -3730,9 +3732,10 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"build-information",
"clap 4.1.3", "clap 4.1.3",
"client-connections", "client-connections",
"completions", "completions",
@@ -3750,6 +3753,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"reqwest", "reqwest",
"serde", "serde",
"service-providers-common",
"socks5-requests", "socks5-requests",
"sqlx 0.6.2", "sqlx 0.6.2",
"statistics-common", "statistics-common",
@@ -3762,7 +3766,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-statistics" name = "nym-network-statistics"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"dirs", "dirs",
"log", "log",
@@ -3819,7 +3823,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.7" version = "1.1.8"
dependencies = [ dependencies = [
"build-information", "build-information",
"clap 4.1.3", "clap 4.1.3",
@@ -3849,6 +3853,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
"service-providers-common",
"socks5-requests", "socks5-requests",
"tap", "tap",
"task", "task",
@@ -5492,6 +5497,22 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "service-providers-common"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"build-information",
"log",
"nym-sdk",
"nymsphinx-anonymous-replies",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]] [[package]]
name = "sha-1" name = "sha-1"
version = "0.9.8" version = "0.9.8"
@@ -5673,6 +5694,9 @@ name = "socks5-requests"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"nymsphinx-addressing", "nymsphinx-addressing",
"serde",
"serde_json",
"service-providers-common",
"thiserror", "thiserror",
] ]
+6
View File
@@ -76,6 +76,7 @@ members = [
"integrations/bity", "integrations/bity",
"mixnode", "mixnode",
"sdk/rust/nym-sdk", "sdk/rust/nym-sdk",
"service-providers/common",
"service-providers/network-requester", "service-providers/network-requester",
"service-providers/network-statistics", "service-providers/network-statistics",
"nym-api", "nym-api",
@@ -105,4 +106,9 @@ homepage = "https://nymtech.net"
edition = "2021" edition = "2021"
[workspace.dependencies] [workspace.dependencies]
async-trait = "0.1.63"
log = "0.4" log = "0.4"
thiserror = "1.0.38"
serde = "1.0.152"
serde_json = "1.0.91"
tokio = "1.24.1"
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "client-core" name = "client-core"
version = "1.1.7" version = "1.1.8"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2021"
rust-version = "1.66" rust-version = "1.66"
+14 -9
View File
@@ -171,10 +171,15 @@ impl<T> Config<T> {
self self
} }
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) { pub fn set_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
self.client.gateway_endpoint = gateway_endpoint; self.client.gateway_endpoint = gateway_endpoint;
} }
pub fn with_gateway_endpoint(mut self, gateway_endpoint: GatewayEndpointConfig) -> Self {
self.client.gateway_endpoint = gateway_endpoint;
self
}
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) { pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
self.client.gateway_endpoint.gateway_id = id.into(); self.client.gateway_endpoint.gateway_id = id.into();
} }
@@ -546,35 +551,35 @@ impl<T: NymConfig> Default for Client<T> {
impl<T: NymConfig> Client<T> { impl<T: NymConfig> Client<T> {
fn default_private_identity_key_file(id: &str) -> PathBuf { fn default_private_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("private_identity.pem") T::default_data_directory(id).join("private_identity.pem")
} }
fn default_public_identity_key_file(id: &str) -> PathBuf { fn default_public_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("public_identity.pem") T::default_data_directory(id).join("public_identity.pem")
} }
fn default_private_encryption_key_file(id: &str) -> PathBuf { fn default_private_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("private_encryption.pem") T::default_data_directory(id).join("private_encryption.pem")
} }
fn default_public_encryption_key_file(id: &str) -> PathBuf { fn default_public_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("public_encryption.pem") T::default_data_directory(id).join("public_encryption.pem")
} }
fn default_gateway_shared_key_file(id: &str) -> PathBuf { fn default_gateway_shared_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("gateway_shared.pem") T::default_data_directory(id).join("gateway_shared.pem")
} }
fn default_ack_key_file(id: &str) -> PathBuf { fn default_ack_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("ack_key.pem") T::default_data_directory(id).join("ack_key.pem")
} }
fn default_reply_surb_database_path(id: &str) -> PathBuf { fn default_reply_surb_database_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("persistent_reply_store.sqlite") T::default_data_directory(id).join("persistent_reply_store.sqlite")
} }
fn default_database_path(id: &str) -> PathBuf { fn default_database_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join(DB_FILE_NAME) T::default_data_directory(id).join(DB_FILE_NAME)
} }
} }
+1 -1
View File
@@ -149,7 +149,7 @@ pub fn load_existing_gateway_config<T>(id: &str) -> Result<GatewayEndpointConfig
where where
T: NymConfig + ClientCoreConfigTrait, T: NymConfig + ClientCoreConfigTrait,
{ {
T::load_from_file(Some(id)) T::load_from_file(id)
.map(|existing_config| existing_config.get_gateway_endpoint().clone()) .map(|existing_config| existing_config.get_gateway_endpoint().clone())
.map_err(|err| { .map_err(|err| {
log::error!( log::error!(
+6
View File
@@ -82,6 +82,11 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
let config = Config::try_from_nym_network_details(&network_details)?; let config = Config::try_from_nym_network_details(&network_details)?;
let client = validator_client::Client::new_query(config)?; let client = validator_client::Client::new_query(config)?;
let epoch_id = client.nyxd.get_current_epoch().await?.epoch_id; let epoch_id = client.nyxd.get_current_epoch().await?.epoch_id;
let threshold = client
.nyxd
.get_current_epoch_threshold()
.await?
.ok_or(CredentialClientError::NoThreshold)?;
let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client, epoch_id).await?; let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client, epoch_id).await?;
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap(); let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
@@ -98,6 +103,7 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
&params, &params,
&bandwidth_credential_attributes, &bandwidth_credential_attributes,
&coconut_api_clients, &coconut_api_clients,
threshold,
) )
.await?; .await?;
println!("Signature: {:?}", signature.to_bs58()); println!("Signature: {:?}", signature.to_bs58());
+3
View File
@@ -34,4 +34,7 @@ pub enum CredentialClientError {
#[error("Could not use shared storage")] #[error("Could not use shared storage")]
SharedStorageError(#[from] StorageError), SharedStorageError(#[from] StorageError),
#[error("Threshold not set yet")]
NoThreshold,
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "1.1.7" version = "1.1.8"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" edition = "2021"
+18 -4
View File
@@ -2,16 +2,19 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::template::config_template; use crate::client::config::template::config_template;
pub use client_core::config::Config as BaseConfig; use client_core::config::ClientCoreConfigTrait;
pub use client_core::config::MISSING_VALUE;
use client_core::config::{ClientCoreConfigTrait, DebugConfig};
use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use config::{NymConfig, OptionalSet}; use config::{NymConfig, OptionalSet};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
pub use client_core::config::Config as BaseConfig;
pub use client_core::config::MISSING_VALUE;
pub use client_core::config::{DebugConfig, GatewayEndpointConfig};
mod template; mod template;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)] #[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
@@ -104,6 +107,11 @@ impl Config {
self self
} }
pub fn with_host(mut self, host: IpAddr) -> Self {
self.socket.host = host;
self
}
pub fn with_port(mut self, port: u16) -> Self { pub fn with_port(mut self, port: u16) -> Self {
self.socket.listening_port = port; self.socket.listening_port = port;
self self
@@ -130,6 +138,10 @@ impl Config {
self.socket.socket_type self.socket.socket_type
} }
pub fn get_listening_ip(&self) -> IpAddr {
self.socket.host
}
pub fn get_listening_port(&self) -> u16 { pub fn get_listening_port(&self) -> u16 {
self.socket.listening_port self.socket.listening_port
} }
@@ -180,9 +192,10 @@ impl Config {
} }
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct Socket { pub struct Socket {
socket_type: SocketType, socket_type: SocketType,
host: IpAddr,
listening_port: u16, listening_port: u16,
} }
@@ -190,6 +203,7 @@ impl Default for Socket {
fn default() -> Self { fn default() -> Self {
Socket { Socket {
socket_type: SocketType::WebSocket, socket_type: SocketType::WebSocket,
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT, listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
} }
} }
@@ -93,6 +93,9 @@ socket_type = '{{ socket.socket_type }}'
# will be listening for incoming requests # will be listening for incoming requests
listening_port = {{ socket.listening_port }} listening_port = {{ socket.listening_port }}
# if applicable (for the case of 'WebSocket'), the ip address on which the client
# will be listening for incoming requests
host = '{{ socket.host }}'
##### logging configuration options ##### ##### logging configuration options #####
+54 -10
View File
@@ -11,19 +11,22 @@ use client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
}; };
use client_core::client::inbound_messages::InputMessage; use client_core::client::inbound_messages::InputMessage;
use client_core::client::key_manager::KeyManager; use client_core::client::received_buffer::{
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver}; ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use futures::channel::mpsc; use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController; use gateway_client::bandwidth::BandwidthController;
use log::*; use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
use nymsphinx::receiver::ReconstructedMessage;
use task::TaskManager; use task::TaskManager;
use tokio::sync::watch::error::SendError;
use validator_client::nyxd::QueryNyxdClient; use validator_client::nyxd::QueryNyxdClient;
pub(crate) mod config; pub use client_core::client::key_manager::KeyManager;
pub use nymsphinx::addressing::clients::Recipient;
pub use nymsphinx::receiver::ReconstructedMessage;
pub mod config;
pub struct SocketClient { pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates, /// Client configuration options, including, among other things, packet sending rates,
@@ -45,6 +48,13 @@ impl SocketClient {
} }
} }
pub fn new_with_keys(config: Config, key_manager: KeyManager) -> Self {
SocketClient {
config,
key_manager,
}
}
async fn create_bandwidth_controller(config: &Config) -> BandwidthController<QueryNyxdClient> { async fn create_bandwidth_controller(config: &Config) -> BandwidthController<QueryNyxdClient> {
let details = network_defaults::NymNetworkDetails::new_from_env(); let details = network_defaults::NymNetworkDetails::new_from_env();
let mut client_config = validator_client::Config::try_from_nym_network_details(&details) let mut client_config = validator_client::Config::try_from_nym_network_details(&details)
@@ -102,7 +112,8 @@ impl SocketClient {
reply_controller_sender, reply_controller_sender,
); );
websocket::Listener::new(config.get_listening_port()).start(websocket_handler, shutdown); websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
.start(websocket_handler, shutdown);
} }
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent) /// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
@@ -119,10 +130,17 @@ impl SocketClient {
return Err(ClientError::InvalidSocketMode); return Err(ClientError::InvalidSocketMode);
} }
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None
} else {
Some(Self::create_bandwidth_controller(&self.config).await)
};
let base_builder = BaseClientBuilder::new_from_base_config( let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(), self.config.get_base(),
self.key_manager, self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await), bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend( non_wasm_helpers::setup_fs_reply_surb_backend(
Some(self.config.get_base().get_reply_surb_database_path()), Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(), self.config.get_debug_settings(),
@@ -156,10 +174,17 @@ impl SocketClient {
return Err(ClientError::InvalidSocketMode); return Err(ClientError::InvalidSocketMode);
} }
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None
} else {
Some(Self::create_bandwidth_controller(&self.config).await)
};
let base_client = BaseClientBuilder::new_from_base_config( let base_client = BaseClientBuilder::new_from_base_config(
self.config.get_base(), self.config.get_base(),
self.key_manager, self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await), bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend( non_wasm_helpers::setup_fs_reply_surb_backend(
Some(self.config.get_base().get_reply_surb_database_path()), Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(), self.config.get_debug_settings(),
@@ -167,6 +192,8 @@ impl SocketClient {
.await?, .await?,
); );
let address = base_client.as_mix_recipient();
let mut started_client = base_client.start_base().await?; let mut started_client = base_client.start_base().await?;
let client_input = started_client.client_input.register_producer(); let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer(); let client_output = started_client.client_output.register_consumer();
@@ -184,21 +211,38 @@ impl SocketClient {
Ok(DirectClient { Ok(DirectClient {
client_input, client_input,
_received_buffer_request_sender: client_output.received_buffer_request_sender,
reconstructed_receiver, reconstructed_receiver,
_shutdown_notifier: started_client.task_manager, address,
shutdown_notifier: started_client.task_manager,
}) })
} }
} }
pub struct DirectClient { pub struct DirectClient {
client_input: ClientInput, client_input: ClientInput,
// make sure to not drop the channel
_received_buffer_request_sender: ReceivedBufferRequestSender,
reconstructed_receiver: ReconstructedMessagesReceiver, reconstructed_receiver: ReconstructedMessagesReceiver,
address: Recipient,
// we need to keep reference to this guy otherwise things will start dropping // we need to keep reference to this guy otherwise things will start dropping
_shutdown_notifier: TaskManager, shutdown_notifier: TaskManager,
} }
impl DirectClient { impl DirectClient {
pub fn address(&self) -> &Recipient {
&self.address
}
pub fn signal_shutdown(&self) -> Result<(), SendError<()>> {
self.shutdown_notifier.signal_shutdown()
}
pub async fn wait_for_shutdown(&mut self) {
self.shutdown_notifier.wait_for_shutdown().await
}
/// EXPERIMENTAL DIRECT RUST API /// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked /// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests) /// well enough in local tests)
+10 -4
View File
@@ -12,6 +12,7 @@ use crypto::asymmetric::identity;
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use serde::Serialize; use serde::Serialize;
use std::fmt::Display; use std::fmt::Display;
use std::net::IpAddr;
use tap::TapFallible; use tap::TapFallible;
#[derive(Args, Clone)] #[derive(Args, Clone)]
@@ -30,7 +31,7 @@ pub(crate) struct Init {
force_register_gateway: bool, force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators /// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',')] #[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>, nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators /// Comma separated list of rest endpoints of the API validators
@@ -46,6 +47,10 @@ pub(crate) struct Init {
#[clap(short, long)] #[clap(short, long)]
port: Option<u16>, port: Option<u16>,
/// Ip for the socket (if applicable) to listen for requests.
#[clap(long)]
host: Option<IpAddr>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to /// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init /// modify config post init
#[clap(long, hide = true)] #[clap(long, hide = true)]
@@ -57,7 +62,7 @@ pub(crate) struct Init {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway /// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. /// with bandwidth credential requirement.
#[clap(long)] #[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>, enabled_credentials_mode: Option<bool>,
/// Save a summary of the initialization to a json file /// Save a summary of the initialization to a json file
@@ -71,6 +76,7 @@ impl From<Init> for OverrideConfig {
nym_apis: init_config.nym_apis, nym_apis: init_config.nym_apis,
disable_socket: init_config.disable_socket, disable_socket: init_config.disable_socket,
port: init_config.port, port: init_config.port,
host: init_config.host,
fastmode: init_config.fastmode, fastmode: init_config.fastmode,
no_cover: init_config.no_cover, no_cover: init_config.no_cover,
@@ -108,7 +114,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
let id = &args.id; let id = &args.id;
let already_init = Config::default_config_file_path(Some(id)).exists(); let already_init = Config::default_config_file_path(id).exists();
if already_init { if already_init {
println!("Client \"{id}\" was already initialised before"); println!("Client \"{id}\" was already initialised before");
} }
@@ -141,7 +147,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
.await .await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.get_base_mut().with_gateway_endpoint(gateway); config.get_base_mut().set_gateway_endpoint(gateway);
config.save_to_file(None).tap_err(|_| { config.save_to_file(None).tap_err(|_| {
log::error!("Failed to save the config file"); log::error!("Failed to save the config file");
+3
View File
@@ -9,6 +9,7 @@ use completions::{fig_generate, ArgShell};
use config::OptionalSet; use config::OptionalSet;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::error::Error; use std::error::Error;
use std::net::IpAddr;
pub(crate) mod init; pub(crate) mod init;
pub(crate) mod run; pub(crate) mod run;
@@ -56,6 +57,7 @@ pub(crate) struct OverrideConfig {
nym_apis: Option<Vec<url::Url>>, nym_apis: Option<Vec<url::Url>>,
disable_socket: Option<bool>, disable_socket: Option<bool>,
port: Option<u16>, port: Option<u16>,
host: Option<IpAddr>,
fastmode: bool, fastmode: bool,
no_cover: bool, no_cover: bool,
nyxd_urls: Option<Vec<url::Url>>, nyxd_urls: Option<Vec<url::Url>>,
@@ -81,6 +83,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) .with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) .with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
.with_optional(Config::with_port, args.port) .with_optional(Config::with_port, args.port)
.with_optional(Config::with_host, args.host)
.with_optional_custom_env_ext( .with_optional_custom_env_ext(
BaseConfig::with_custom_nym_apis, BaseConfig::with_custom_nym_apis,
args.nym_apis, args.nym_apis,
+9 -3
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::error::Error; use std::error::Error;
use std::net::IpAddr;
use crate::{ use crate::{
client::{config::Config, SocketClient}, client::{config::Config, SocketClient},
@@ -22,7 +23,7 @@ pub(crate) struct Run {
id: String, id: String,
/// Comma separated list of rest endpoints of the nyxd validators /// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',')] #[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>, nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators /// Comma separated list of rest endpoints of the API validators
@@ -43,6 +44,10 @@ pub(crate) struct Run {
#[clap(short, long)] #[clap(short, long)]
port: Option<u16>, port: Option<u16>,
/// Ip for the socket (if applicable) to listen for requests.
#[clap(long)]
host: Option<IpAddr>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to /// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init /// modify config post init
#[clap(long, hide = true)] #[clap(long, hide = true)]
@@ -54,7 +59,7 @@ pub(crate) struct Run {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway /// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. /// with bandwidth credential requirement.
#[clap(long)] #[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>, enabled_credentials_mode: Option<bool>,
} }
@@ -64,6 +69,7 @@ impl From<Run> for OverrideConfig {
nym_apis: run_config.nym_apis, nym_apis: run_config.nym_apis,
disable_socket: run_config.disable_socket, disable_socket: run_config.disable_socket,
port: run_config.port, port: run_config.port,
host: run_config.host,
fastmode: run_config.fastmode, fastmode: run_config.fastmode,
no_cover: run_config.no_cover, no_cover: run_config.no_cover,
nyxd_urls: run_config.nyxd_urls, nyxd_urls: run_config.nyxd_urls,
@@ -94,7 +100,7 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> { pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
let id = &args.id; let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
+1 -1
View File
@@ -131,7 +131,7 @@ pub(crate) fn execute(args: &Upgrade) {
let id = &args.id; let id = &args.id;
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}"); eprintln!("failed to load existing config file! - {err}");
process::exit(1) process::exit(1)
}); });
+3 -3
View File
@@ -3,6 +3,7 @@
use super::handler::HandlerBuilder; use super::handler::HandlerBuilder;
use log::*; use log::*;
use std::net::IpAddr;
use std::{net::SocketAddr, process, sync::Arc}; use std::{net::SocketAddr, process, sync::Arc};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::{sync::Notify, task::JoinHandle}; use tokio::{sync::Notify, task::JoinHandle};
@@ -24,10 +25,9 @@ pub(crate) struct Listener {
} }
impl Listener { impl Listener {
pub(crate) fn new(port: u16) -> Self { pub(crate) fn new(host: IpAddr, port: u16) -> Self {
Listener { Listener {
// unless we find compelling reason not to, just listen on local only address: SocketAddr::new(host, port),
address: SocketAddr::new("127.0.0.1".parse().unwrap(), port),
state: State::AwaitingConnection, state: State::AwaitingConnection,
} }
} }
+4 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.7" version = "1.1.8"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] 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" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
@@ -19,8 +19,8 @@ log = { workspace = true }
pin-project = "1.0" pin-project = "1.0"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
serde_json = "1.0.89" serde_json = { workspace = true }
tap = "1.0.1" tap = "1.0.1"
thiserror = "1.0.34" thiserror = "1.0.34"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] } tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
@@ -45,6 +45,7 @@ nymsphinx = { path = "../../common/nymsphinx" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" } ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
pemstore = { path = "../../common/pemstore" } pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" } proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
service-providers-common = { path = "../../service-providers/common" }
socks5-requests = { path = "../../common/socks5/requests" } socks5-requests = { path = "../../common/socks5/requests" }
task = { path = "../../common/task" } task = { path = "../../common/task" }
topology = { path = "../../common/topology" } topology = { path = "../../common/topology" }
+34 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::template::config_template; use crate::client::config::template::config_template;
@@ -9,6 +9,8 @@ use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
use config::{NymConfig, OptionalSet}; use config::{NymConfig, OptionalSet};
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use service_providers_common::interface::ProviderInterfaceVersion;
use socks5_requests::Socks5ProtocolVersion;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
@@ -90,6 +92,16 @@ impl Config {
self self
} }
pub fn with_provider_interface_version(mut self, version: ProviderInterfaceVersion) -> Self {
self.socks5.provider_interface_version = version;
self
}
pub fn with_socks5_protocol_version(mut self, version: Socks5ProtocolVersion) -> Self {
self.socks5.socks5_protocol_version = version;
self
}
pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self { pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self {
self.socks5.send_anonymously = anonymous_replies; self.socks5.send_anonymously = anonymous_replies;
self self
@@ -117,6 +129,14 @@ impl Config {
.expect("malformed provider address") .expect("malformed provider address")
} }
pub fn get_provider_interface_version(&self) -> ProviderInterfaceVersion {
self.socks5.provider_interface_version
}
pub fn get_socks5_protocol_version(&self) -> Socks5ProtocolVersion {
self.socks5.socks5_protocol_version
}
pub fn get_send_anonymously(&self) -> bool { pub fn get_send_anonymously(&self) -> bool {
self.socks5.send_anonymously self.socks5.send_anonymously
} }
@@ -187,6 +207,15 @@ pub struct Socks5 {
/// The mix address of the provider to which all requests are going to be sent. /// The mix address of the provider to which all requests are going to be sent.
provider_mix_address: String, provider_mix_address: String,
/// The version of the 'service provider' this client is going to use in its communication with the
/// specified socks5 provider.
// if in doubt, use the legacy version as initially nobody will be using the updated binaries
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
provider_interface_version: ProviderInterfaceVersion,
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
socks5_protocol_version: Socks5ProtocolVersion,
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. /// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
/// While this is going to hide its actual address information, it will make the actual communication /// While this is going to hide its actual address information, it will make the actual communication
/// slower and consume nearly double the bandwidth as it will require sending reply SURBs. /// slower and consume nearly double the bandwidth as it will require sending reply SURBs.
@@ -201,6 +230,8 @@ impl Socks5 {
Socks5 { Socks5 {
listening_port: DEFAULT_SOCKS5_LISTENING_PORT, listening_port: DEFAULT_SOCKS5_LISTENING_PORT,
provider_mix_address: provider_mix_address.into(), provider_mix_address: provider_mix_address.into(),
provider_interface_version: ProviderInterfaceVersion::Legacy,
socks5_protocol_version: Socks5ProtocolVersion::Legacy,
send_anonymously: false, send_anonymously: false,
} }
} }
@@ -211,6 +242,8 @@ impl Default for Socks5 {
Socks5 { Socks5 {
listening_port: DEFAULT_SOCKS5_LISTENING_PORT, listening_port: DEFAULT_SOCKS5_LISTENING_PORT,
provider_mix_address: "".into(), provider_mix_address: "".into(),
provider_interface_version: ProviderInterfaceVersion::Legacy,
socks5_protocol_version: Socks5ProtocolVersion::Legacy,
send_anonymously: false, send_anonymously: false,
} }
} }
+2
View File
@@ -132,6 +132,8 @@ impl NymClient {
self_address, self_address,
shared_lane_queue_lengths, shared_lane_queue_lengths,
socks::client::Config::new( socks::client::Config::new(
config.get_provider_interface_version(),
config.get_socks5_protocol_version(),
config.get_send_anonymously(), config.get_send_anonymously(),
config.get_connection_start_surbs(), config.get_connection_start_surbs(),
config.get_per_request_surbs(), config.get_per_request_surbs(),
+6 -4
View File
@@ -43,7 +43,7 @@ pub(crate) struct Init {
force_register_gateway: bool, force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators /// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',')] #[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>, nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators /// Comma separated list of rest endpoints of the API validators
@@ -66,7 +66,7 @@ pub(crate) struct Init {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway /// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. /// with bandwidth credential requirement.
#[clap(long)] #[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>, enabled_credentials_mode: Option<bool>,
/// Save a summary of the initialization to a json file /// Save a summary of the initialization to a json file
@@ -117,7 +117,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
let id = &args.id; let id = &args.id;
let provider_address = &args.provider; let provider_address = &args.provider;
let already_init = Config::default_config_file_path(Some(id)).exists(); let already_init = Config::default_config_file_path(id).exists();
if already_init { if already_init {
println!("SOCKS5 client \"{id}\" was already initialised before"); println!("SOCKS5 client \"{id}\" was already initialised before");
} }
@@ -153,7 +153,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
.await .await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.get_base_mut().with_gateway_endpoint(gateway); config.get_base_mut().set_gateway_endpoint(gateway);
// TODO: ask the service provider we specified for its interface version and set it in the config
config.save_to_file(None).tap_err(|_| { config.save_to_file(None).tap_err(|_| {
log::error!("Failed to save the config file"); log::error!("Failed to save the config file");
+3 -3
View File
@@ -43,7 +43,7 @@ pub(crate) struct Run {
gateway: Option<identity::PublicKey>, gateway: Option<identity::PublicKey>,
/// Comma separated list of rest endpoints of the nyxd validators /// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',')] #[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>, nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the Nym APIs /// Comma separated list of rest endpoints of the Nym APIs
@@ -65,7 +65,7 @@ pub(crate) struct Run {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway /// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. /// with bandwidth credential requirement.
#[clap(long)] #[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>, enabled_credentials_mode: Option<bool>,
} }
@@ -108,7 +108,7 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let id = &args.id; let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
+1 -1
View File
@@ -144,7 +144,7 @@ pub(crate) fn execute(args: &Upgrade) {
let id = &args.id; let id = &args.id;
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}"); eprintln!("failed to load existing config file! - {err}");
process::exit(1) process::exit(1)
}); });
+10 -1
View File
@@ -1,6 +1,6 @@
use crate::socks::types::SocksProxyError; use crate::socks::types::SocksProxyError;
use client_core::error::ClientCoreError; use client_core::error::ClientCoreError;
use socks5_requests::ConnectionId; use socks5_requests::{ConnectionError, ConnectionId};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError { pub enum Socks5ClientError {
@@ -28,3 +28,12 @@ pub enum Socks5ClientError {
error: String, error: String,
}, },
} }
impl From<ConnectionError> for Socks5ClientError {
fn from(value: ConnectionError) -> Self {
Socks5ClientError::NetworkRequesterError {
connection_id: value.connection_id,
error: value.network_requester_error,
}
}
}
+132 -30
View File
@@ -16,7 +16,10 @@ use proxy_helpers::connection_controller::{
}; };
use proxy_helpers::proxy_runner::ProxyRunner; use proxy_helpers::proxy_runner::ProxyRunner;
use rand::RngCore; use rand::RngCore;
use socks5_requests::{ConnectionId, Message, RemoteAddress, Request}; use service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use std::io; use std::io;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::pin::Pin; use std::pin::Pin;
@@ -128,6 +131,8 @@ impl AsyncWrite for StreamState {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub(crate) struct Config { pub(crate) struct Config {
provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool, use_surbs_for_responses: bool,
connection_start_surbs: u32, connection_start_surbs: u32,
per_request_surbs: u32, per_request_surbs: u32,
@@ -135,16 +140,27 @@ pub(crate) struct Config {
impl Config { impl Config {
pub(crate) fn new( pub(crate) fn new(
provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool, use_surbs_for_responses: bool,
connection_start_surbs: u32, connection_start_surbs: u32,
per_request_surbs: u32, per_request_surbs: u32,
) -> Self { ) -> Self {
Self { Self {
provider_interface_version,
socks5_protocol_version,
use_surbs_for_responses, use_surbs_for_responses,
connection_start_surbs, connection_start_surbs,
per_request_surbs, per_request_surbs,
} }
} }
fn request_version(&self) -> RequestVersion<Socks5Request> {
RequestVersion {
provider_interface: self.provider_interface_version,
provider_protocol: self.socks5_protocol_version,
}
}
} }
/// A client connecting to the Socks proxy server, because /// A client connecting to the Socks proxy server, because
@@ -173,7 +189,9 @@ impl Drop for SocksClient {
// if we never managed to start a proxy, the entry will not exist in the controller // if we never managed to start a proxy, the entry will not exist in the controller
if self.started_proxy { if self.started_proxy {
self.controller_sender self.controller_sender
.unbounded_send(ControllerCommand::Remove(self.connection_id)) .unbounded_send(ControllerCommand::Remove {
connection_id: self.connection_id,
})
.unwrap(); .unwrap();
} }
} }
@@ -248,19 +266,26 @@ impl SocksClient {
// Send an error back to the client // Send an error back to the client
pub async fn send_error_v4(&mut self, r: ResponseCodeV4) -> Result<(), SocksProxyError> { pub async fn send_error_v4(&mut self, r: ResponseCodeV4) -> Result<(), SocksProxyError> {
self.stream.write_all(&[SOCKS4_VERSION, r as u8]).await?; self.stream
Ok(()) .write_all(&[SOCKS4_VERSION, r as u8])
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })
} }
pub async fn send_error_v5(&mut self, r: ResponseCodeV5) -> Result<(), SocksProxyError> { pub async fn send_error_v5(&mut self, r: ResponseCodeV5) -> Result<(), SocksProxyError> {
self.stream.write_all(&[SOCKS5_VERSION, r as u8]).await?; self.stream
Ok(()) .write_all(&[SOCKS5_VERSION, r as u8])
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })
} }
/// Shutdown the `TcpStream` to the client and end the session /// Shutdown the `TcpStream` to the client and end the session
pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> { pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> {
info!("client is shutting down its TCP stream"); info!("client is shutting down its TCP stream");
self.stream.shutdown().await?; self.stream
.shutdown()
.await
.map_err(|source| SocksProxyError::SocketShutdownFailure { source })?;
self.shutdown_listener.mark_as_success(); self.shutdown_listener.mark_as_success();
Ok(()) Ok(())
} }
@@ -268,11 +293,20 @@ impl SocksClient {
/// Initializes the new client, checking that the correct Socks version (5) /// Initializes the new client, checking that the correct Socks version (5)
/// is in use and that the client is authenticated, then runs the request. /// is in use and that the client is authenticated, then runs the request.
pub async fn run(&mut self) -> Result<(), SocksProxyError> { pub async fn run(&mut self) -> Result<(), SocksProxyError> {
debug!("New connection from: {}", self.stream.peer_addr()?.ip()); debug!(
"New connection from: {}",
self.stream
.peer_addr()
.map_err(|source| SocksProxyError::PeerAddrExtractionFailure { source })?
.ip()
);
// Read a byte from the stream and determine the version being requested // Read a byte from the stream and determine the version being requested
let mut header = [0u8]; let mut header = [0u8];
self.stream.read_exact(&mut header).await?; self.stream
.read_exact(&mut header)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.socks_version = match SocksVersion::try_from(header[0]) { self.socks_version = match SocksVersion::try_from(header[0]) {
Ok(version) => Some(version), Ok(version) => Some(version),
@@ -284,7 +318,10 @@ impl SocksClient {
if self.socks_version == Some(SocksVersion::V5) { if self.socks_version == Some(SocksVersion::V5) {
let mut auth = [0u8]; let mut auth = [0u8];
self.stream.read_exact(&mut auth).await?; self.stream
.read_exact(&mut auth)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.auth_nmethods = auth[0]; self.auth_nmethods = auth[0];
self.authenticate_socks5().await?; self.authenticate_socks5().await?;
} }
@@ -293,8 +330,15 @@ impl SocksClient {
} }
async fn send_anonymous_connect_to_mixnet(&mut self, remote_address: RemoteAddress) { async fn send_anonymous_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
let req = Request::new_connect(self.connection_id, remote_address, None); // TODO: simplify by using `request_version`
let msg = Message::Request(req); let req = Socks5Request::new_connect(
self.config.socks5_protocol_version,
self.connection_id,
remote_address,
None,
);
let msg =
Socks5ProviderRequest::new_provider_data(self.config.provider_interface_version, req);
let input_message = InputMessage::new_anonymous( let input_message = InputMessage::new_anonymous(
self.service_provider, self.service_provider,
@@ -309,8 +353,15 @@ impl SocksClient {
} }
async fn send_connect_to_mixnet_with_return_address(&mut self, remote_address: RemoteAddress) { async fn send_connect_to_mixnet_with_return_address(&mut self, remote_address: RemoteAddress) {
let req = Request::new_connect(self.connection_id, remote_address, Some(self.self_address)); // TODO: simplify by using `request_version`
let msg = Message::Request(req); let req = Socks5Request::new_connect(
self.config.socks5_protocol_version,
self.connection_id,
remote_address,
Some(self.self_address),
);
let msg =
Socks5ProviderRequest::new_provider_data(self.config.provider_interface_version, req);
let input_message = InputMessage::new_regular( let input_message = InputMessage::new_regular(
self.service_provider, self.service_provider,
@@ -350,6 +401,7 @@ impl SocksClient {
let input_sender = self.input_sender.clone(); let input_sender = self.input_sender.clone();
let anonymous = self.config.use_surbs_for_responses; let anonymous = self.config.use_surbs_for_responses;
let per_request_surbs = self.config.per_request_surbs; let per_request_surbs = self.config.per_request_surbs;
let request_version = self.config.request_version();
let recipient = self.service_provider; let recipient = self.service_provider;
let (stream, _) = ProxyRunner::new( let (stream, _) = ProxyRunner::new(
@@ -363,8 +415,16 @@ impl SocksClient {
self.shutdown_listener.clone(), self.shutdown_listener.clone(),
) )
.run(move |conn_id, read_data, socket_closed| { .run(move |conn_id, read_data, socket_closed| {
let provider_request = Request::new_send(conn_id, read_data, socket_closed); let provider_request = Socks5Request::new_send(
let provider_message = Message::Request(provider_request); 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); let lane = TransmissionLane::ConnectionId(conn_id);
if anonymous { if anonymous {
InputMessage::new_anonymous( InputMessage::new_anonymous(
@@ -413,7 +473,10 @@ impl SocksClient {
self.started_proxy = true; self.started_proxy = true;
self.controller_sender self.controller_sender
.unbounded_send(ControllerCommand::Insert(self.connection_id, mix_sender)) .unbounded_send(ControllerCommand::Insert {
connection_id: self.connection_id,
connection_sender: mix_sender,
})
.unwrap(); .unwrap();
info!( info!(
@@ -491,7 +554,13 @@ impl SocksClient {
/// into the Authenticator (where it'll be more easily testable) /// into the Authenticator (where it'll be more easily testable)
/// would be a good next step. /// would be a good next step.
async fn authenticate_socks5(&mut self) -> Result<(), SocksProxyError> { async fn authenticate_socks5(&mut self) -> Result<(), SocksProxyError> {
debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip()); debug!(
"Authenticating w/ {}",
self.stream
.peer_addr()
.map_err(|source| SocksProxyError::PeerAddrExtractionFailure { source })?
.ip()
);
// Get valid auth methods // Get valid auth methods
let methods = self.get_available_methods().await?; let methods = self.get_available_methods().await?;
trace!("methods: {:?}", methods); trace!("methods: {:?}", methods);
@@ -505,27 +574,45 @@ impl SocksClient {
response[1] = AuthenticationMethods::UserPass as u8; response[1] = AuthenticationMethods::UserPass as u8;
debug!("Sending USER/PASS packet"); debug!("Sending USER/PASS packet");
self.stream.write_all(&response).await?; self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
let mut header = [0u8; 2]; let mut header = [0u8; 2];
// Read a byte from the stream and determine the version being requested // Read a byte from the stream and determine the version being requested
self.stream.read_exact(&mut header).await?; self.stream
.read_exact(&mut header)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
// debug!("Auth Header: [{}, {}]", header[0], header[1]); // debug!("Auth Header: [{}, {}]", header[0], header[1]);
// Username parsing // Username parsing
let ulen = header[1]; let ulen = header[1];
let mut username = vec![0; ulen as usize]; let mut username = vec![0; ulen as usize];
self.stream.read_exact(&mut username).await?; self.stream
.read_exact(&mut username)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
// Password Parsing // Password Parsing
let plen = self.stream.read_u8().await?; let plen = self
.stream
.read_u8()
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
let mut password = vec![0; plen as usize]; let mut password = vec![0; plen as usize];
self.stream.read_exact(&mut password).await?; self.stream
.read_exact(&mut password)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
let username_str = String::from_utf8(username)?; let username_str = String::from_utf8(username)
let password_str = String::from_utf8(password)?; .map_err(|source| SocksProxyError::MalformedAuthUsername { source })?;
let password_str = String::from_utf8(password)
.map_err(|source| SocksProxyError::MalformedAuthPassword { source })?;
let user = User { let user = User {
username: username_str, username: username_str,
@@ -536,11 +623,17 @@ impl SocksClient {
if self.authenticator.is_allowed(&user) { if self.authenticator.is_allowed(&user) {
debug!("Access Granted. User: {}", user.username); debug!("Access Granted. User: {}", user.username);
let response = [1, ResponseCodeV5::Success as u8]; let response = [1, ResponseCodeV5::Success as u8];
self.stream.write_all(&response).await?; self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
} else { } else {
debug!("Access Denied. User: {}", user.username); debug!("Access Denied. User: {}", user.username);
let response = [1, ResponseCodeV5::Failure as u8]; let response = [1, ResponseCodeV5::Failure as u8];
self.stream.write_all(&response).await?; self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
// Shutdown // Shutdown
self.shutdown().await?; self.shutdown().await?;
@@ -551,12 +644,18 @@ impl SocksClient {
// set the default auth method (no auth) // set the default auth method (no auth)
response[1] = AuthenticationMethods::NoAuth as u8; response[1] = AuthenticationMethods::NoAuth as u8;
debug!("Sending NOAUTH packet"); debug!("Sending NOAUTH packet");
self.stream.write_all(&response).await?; self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
Ok(()) Ok(())
} else { } else {
warn!("Client has no suitable authentication methods!"); warn!("Client has no suitable authentication methods!");
response[1] = AuthenticationMethods::NoMethods as u8; response[1] = AuthenticationMethods::NoMethods as u8;
self.stream.write_all(&response).await?; self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
self.shutdown().await?; self.shutdown().await?;
Err(ResponseCodeV5::Failure.into()) Err(ResponseCodeV5::Failure.into())
} }
@@ -567,7 +666,10 @@ impl SocksClient {
let mut methods: Vec<u8> = Vec::with_capacity(self.auth_nmethods as usize); let mut methods: Vec<u8> = Vec::with_capacity(self.auth_nmethods as usize);
for _ in 0..self.auth_nmethods { for _ in 0..self.auth_nmethods {
let mut method = [0u8; 1]; let mut method = [0u8; 1];
self.stream.read_exact(&mut method).await?; self.stream
.read_exact(&mut method)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
if self.authenticator.auth_methods.contains(&method[0]) { if self.authenticator.auth_methods.contains(&method[0]) {
methods.append(&mut method.to_vec()); methods.append(&mut method.to_vec());
} }
+54 -30
View File
@@ -5,8 +5,9 @@ use log::*;
use client_core::client::received_buffer::ReconstructedMessagesReceiver; use client_core::client::received_buffer::ReconstructedMessagesReceiver;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender}; use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
use nymsphinx::receiver::ReconstructedMessage; use nymsphinx::receiver::ReconstructedMessage;
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender}; use proxy_helpers::connection_controller::ControllerSender;
use socks5_requests::Message; use service_providers_common::interface::{ControlResponse, ResponseContent};
use socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use task::TaskClient; use task::TaskClient;
use crate::error::Socks5ClientError; use crate::error::Socks5ClientError;
@@ -52,6 +53,39 @@ impl MixnetResponseListener {
} }
} }
fn on_control_response(
&self,
control_response: ControlResponse,
) -> Result<(), Socks5ClientError> {
error!("received a control response which we don't know how to handle yet!");
error!("got: {:?}", control_response);
// I guess we'd need another channel here to forward those to where they need to go
Ok(())
}
fn on_provider_data_response(
&self,
provider_response: Socks5Response,
) -> Result<(), Socks5ClientError> {
match provider_response.content {
Socks5ResponseContent::ConnectionError(err_response) => {
error!(
"Network requester failed on connection id {} with error: {}",
err_response.connection_id, err_response.network_requester_error
);
Err(err_response.into())
}
Socks5ResponseContent::NetworkData(response) => {
self.controller_sender
.unbounded_send(response.into())
.unwrap();
Ok(())
}
}
}
fn on_message( fn on_message(
&self, &self,
reconstructed_message: ReconstructedMessage, reconstructed_message: ReconstructedMessage,
@@ -60,38 +94,28 @@ impl MixnetResponseListener {
if reconstructed_message.sender_tag.is_some() { if reconstructed_message.sender_tag.is_some() {
warn!("this message was sent anonymously - it couldn't have come from the service provider"); warn!("this message was sent anonymously - it couldn't have come from the service provider");
} }
match Socks5ProviderResponse::try_from_bytes(&raw_message) {
let response = match Message::try_from_bytes(&raw_message) {
Err(err) => { Err(err) => {
warn!("failed to parse received response - {err}"); warn!("failed to parse received response: {err}");
return Ok(()); Ok(())
} }
Ok(Message::Request(_)) => { Ok(response) => {
warn!("unexpected request"); // as long as the client used the same (or older) interface than the service provider,
return Ok(()); // the response should have used exactly the same version
} trace!(
Ok(Message::Response(data)) => data, "the received response was sent with {:?} interface version",
Ok(Message::NetworkRequesterResponse(r)) => { response.interface_version
error!(
"Network requester failed on connection id {} with error: {}",
r.connection_id, r.network_requester_error
); );
return Err(Socks5ClientError::NetworkRequesterError { match response.content {
connection_id: r.connection_id, ResponseContent::Control(control_response) => {
error: r.network_requester_error, self.on_control_response(control_response)
}); }
ResponseContent::ProviderData(provider_response) => {
self.on_provider_data_response(provider_response)
}
}
} }
}; }
self.controller_sender
.unbounded_send(ControllerCommand::Send(
response.connection_id,
response.data,
response.is_closed,
))
.unwrap();
Ok(())
} }
pub(crate) async fn run(&mut self) { pub(crate) async fn run(&mut self) {
+1 -1
View File
@@ -33,7 +33,7 @@ impl TryFrom<u8> for SocksVersion {
match version { match version {
SOCKS4_VERSION => Ok(Self::V4), SOCKS4_VERSION => Ok(Self::V4),
SOCKS5_VERSION => Ok(Self::V5), SOCKS5_VERSION => Ok(Self::V5),
_ => Err(SocksProxyError::UnsupportedProxyVersion(version)), _ => Err(SocksProxyError::UnsupportedProxyVersion { version }),
} }
} }
} }
+39 -10
View File
@@ -28,7 +28,10 @@ impl SocksRequest {
log::trace!("read from stream socks4"); log::trace!("read from stream socks4");
let mut packet = [0u8; 3]; let mut packet = [0u8; 3];
stream.read_exact(&mut packet).await?; stream
.read_exact(&mut packet)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
// CD (command) // CD (command)
let Some(command) = SocksCommand::from(packet[0] as usize) else { let Some(command) = SocksCommand::from(packet[0] as usize) else {
@@ -43,7 +46,10 @@ impl SocksRequest {
// DSTIP // DSTIP
let mut ip = [0u8; 4]; let mut ip = [0u8; 4];
stream.read_exact(&mut ip).await?; stream
.read_exact(&mut ip)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
// USERID // USERID
let _userid = read_until_zero(stream).await; let _userid = read_until_zero(stream).await;
@@ -76,12 +82,17 @@ impl SocksRequest {
let mut packet = [0u8; 4]; let mut packet = [0u8; 4];
// Read a byte from the stream and determine the version being requested // Read a byte from the stream and determine the version being requested
stream.read_exact(&mut packet).await?; stream
.read_exact(&mut packet)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
// VER // VER
if packet[0] != SOCKS5_VERSION { if packet[0] != SOCKS5_VERSION {
warn!("Unsupported version: SOCKS{}", packet[0]); warn!("Unsupported version: SOCKS{}", packet[0]);
return Err(SocksProxyError::UnsupportedProxyVersion(packet[0])); return Err(SocksProxyError::UnsupportedProxyVersion {
version: (packet[0]),
});
} }
// CMD // CMD
@@ -103,26 +114,41 @@ impl SocksRequest {
let addr = match addr_type { let addr = match addr_type {
AddrType::Domain => { AddrType::Domain => {
let mut domain_length = [0u8]; let mut domain_length = [0u8];
stream.read_exact(&mut domain_length).await?; stream
.read_exact(&mut domain_length)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
let mut domain = vec![0u8; domain_length[0] as usize]; let mut domain = vec![0u8; domain_length[0] as usize];
stream.read_exact(&mut domain).await?; stream
.read_exact(&mut domain)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
domain domain
} }
AddrType::V4 => { AddrType::V4 => {
let mut addr = [0u8; 4]; let mut addr = [0u8; 4];
stream.read_exact(&mut addr).await?; stream
.read_exact(&mut addr)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
addr.to_vec() addr.to_vec()
} }
AddrType::V6 => { AddrType::V6 => {
let mut addr = [0u8; 16]; let mut addr = [0u8; 16];
stream.read_exact(&mut addr).await?; stream
.read_exact(&mut addr)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
addr.to_vec() addr.to_vec()
} }
}; };
// DST.PORT // DST.PORT
let mut port = [0u8; 2]; let mut port = [0u8; 2];
stream.read_exact(&mut port).await?; stream
.read_exact(&mut port)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
let port = merge_u8_into_u16(port[0], port[1]); let port = merge_u8_into_u16(port[0], port[1]);
Ok(SocksRequest { Ok(SocksRequest {
@@ -179,7 +205,10 @@ where
let mut result = Vec::new(); let mut result = Vec::new();
let mut char = [0u8]; let mut char = [0u8];
loop { loop {
stream.read_exact(&mut char).await?; stream
.read_exact(&mut char)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
if char[0] == 0 { if char[0] == 0 {
break; break;
} }
+5
View File
@@ -86,6 +86,11 @@ impl SphinxSocksServer {
mixnet_response_listener.run().await; mixnet_response_listener.run().await;
}); });
// TODO:, if required, there should be another task here responsible for control requests.
// it should get `input_sender` to send actual requests into the mixnet
// and some channel that connects it from `MixnetResponseListener` to receive
// any control responses
loop { loop {
tokio::select! { tokio::select! {
Ok((stream, _remote)) = listener.accept() => { Ok((stream, _remote)) = listener.accept() => {
+52 -24
View File
@@ -1,3 +1,7 @@
use socks5_requests::Socks5RequestError;
use std::string::FromUtf8Error;
use thiserror::Error;
/// SOCKS4 Response codes /// SOCKS4 Response codes
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) enum ResponseCodeV4 { pub(crate) enum ResponseCodeV4 {
@@ -8,9 +12,8 @@ pub(crate) enum ResponseCodeV4 {
} }
/// Possible SOCKS5 Response Codes /// Possible SOCKS5 Response Codes
#[allow(dead_code)] #[derive(Debug, Error)]
#[derive(Debug, thiserror::Error)] pub enum ResponseCodeV5 {
pub(crate) enum ResponseCodeV5 {
#[error("SOCKS5 Server Success")] #[error("SOCKS5 Server Success")]
Success = 0x00, Success = 0x00,
#[error("SOCKS5 Server Failure")] #[error("SOCKS5 Server Failure")]
@@ -31,30 +34,55 @@ pub(crate) enum ResponseCodeV5 {
AddrTypeNotSupported = 0x08, AddrTypeNotSupported = 0x08,
} }
#[derive(Debug)] #[derive(Error, Debug)]
pub enum SocksProxyError { pub enum SocksProxyError {
GenericError(Box<dyn std::error::Error + Send + Sync>), #[error("{version} of the socks protocol is not supported by this client")]
UnsupportedProxyVersion(u8), UnsupportedProxyVersion { version: u8 },
}
impl std::fmt::Display for SocksProxyError { #[error("failed to write to the socket: {source}")]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { SocketWriteError {
match self { #[source]
SocksProxyError::GenericError(err) => write!(f, "GenericError - {err}"), source: std::io::Error,
SocksProxyError::UnsupportedProxyVersion(version) => { },
write!(f, "Unsupported proxy version {}", version)
}
}
}
}
impl<E> From<E> for SocksProxyError #[error("failed to read from the socket: {source}")]
where SocketReadError {
E: std::error::Error + Send + Sync + 'static, #[source]
{ source: std::io::Error,
fn from(err: E) -> Self { },
SocksProxyError::GenericError(Box::new(err))
} #[error("failed to shutdown underlying socket stream: {source}")]
SocketShutdownFailure {
#[source]
source: std::io::Error,
},
#[error("failed to extract ip address of the connected peer: {source}")]
PeerAddrExtractionFailure {
#[source]
source: std::io::Error,
},
#[error("failed to authenticate user due to malformed username: {source}")]
MalformedAuthUsername {
#[source]
source: FromUtf8Error,
},
#[error("failed to authenticate user due to malformed password: {source}")]
MalformedAuthPassword {
#[source]
source: FromUtf8Error,
},
#[error(transparent)]
Socks5ResponseFailure(#[from] ResponseCodeV5),
#[error("could not complete the provider request: {source}")]
ProviderRequestFailure {
#[from]
source: Socks5RequestError,
},
} }
/// DST.addr variant types /// DST.addr variant types
+4
View File
@@ -6,6 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
serde = { workspace = true, features = ["derive"], optional = true }
[build-dependencies] [build-dependencies]
vergen = { version = "7", default-features = false, features = ["build", "git", "rustc", "cargo"] } vergen = { version = "7", default-features = false, features = ["build", "git", "rustc", "cargo"] }
[features]
default = []
+50
View File
@@ -4,6 +4,7 @@
// TODO: at a later date this crate should probably also expose `ContractBuildInformation` // TODO: at a later date this crate should probably also expose `ContractBuildInformation`
// and be used by our smart contracts // and be used by our smart contracts
#[derive(Debug)]
pub struct BinaryBuildInformation { pub struct BinaryBuildInformation {
// VERGEN_BUILD_TIMESTAMP // VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
@@ -53,6 +54,19 @@ impl BinaryBuildInformation {
} }
} }
pub fn to_owned(&self) -> BinaryBuildInformationOwned {
BinaryBuildInformationOwned {
build_timestamp: self.build_timestamp.to_owned(),
build_version: self.build_version.to_owned(),
commit_sha: self.commit_sha.to_owned(),
commit_timestamp: self.commit_timestamp.to_owned(),
commit_branch: self.commit_branch.to_owned(),
rustc_version: self.rustc_version.to_owned(),
rustc_channel: self.rustc_channel.to_owned(),
cargo_profile: self.cargo_profile.to_owned(),
}
}
pub fn pretty_print(&self) -> String { pub fn pretty_print(&self) -> String {
format!( format!(
r#" r#"
@@ -84,3 +98,39 @@ impl BinaryBuildInformation {
) )
} }
} }
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BinaryBuildInformationOwned {
// VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
pub build_timestamp: String,
// VERGEN_BUILD_SEMVER
/// Provides the build version, for example `0.1.0-9-g46f83e1`.
pub build_version: String,
// VERGEN_GIT_SHA
/// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.
pub commit_sha: String,
// VERGEN_GIT_COMMIT_TIMESTAMP
/// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.
pub commit_timestamp: String,
// VERGEN_GIT_BRANCH
/// Provides the name of the git branch that was used for the build, for example `master`.
pub commit_branch: String,
// VERGEN_RUSTC_SEMVER
/// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
pub rustc_version: String,
// VERGEN_RUSTC_CHANNEL
/// Provides the rustc channel that was used for the build, for example `nightly`.
pub rustc_channel: String,
// VERGEN_CARGO_PROFILE
/// Provides the cargo profile that was used for the build, for example `debug`.
pub cargo_profile: String,
}
@@ -8,7 +8,7 @@ use coconut_dkg_common::dealer::{
DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
}; };
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use coconut_dkg_common::types::{Epoch, EpochId}; use coconut_dkg_common::types::{Epoch, EpochId, InitialReplacementData};
use coconut_dkg_common::verification_key::PagedVKSharesResponse; use coconut_dkg_common::verification_key::PagedVKSharesResponse;
use cosmrs::AccountId; use cosmrs::AccountId;
@@ -16,6 +16,7 @@ use cosmrs::AccountId;
pub trait DkgQueryClient { pub trait DkgQueryClient {
async fn get_current_epoch(&self) -> Result<Epoch, NyxdError>; async fn get_current_epoch(&self) -> Result<Epoch, NyxdError>;
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError>; async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError>;
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>, NyxdError>;
async fn get_dealer_details( async fn get_dealer_details(
&self, &self,
address: &AccountId, address: &AccountId,
@@ -62,6 +63,14 @@ where
.query_contract_smart(self.coconut_dkg_contract_address(), &request) .query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await .await
} }
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>, NyxdError> {
let request = DkgQueryMsg::GetInitialDealers {};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_dealer_details( async fn get_dealer_details(
&self, &self,
address: &AccountId, address: &AccountId,
@@ -17,18 +17,21 @@ pub trait DkgSigningClient {
&self, &self,
bte_key: EncodedBTEPublicKeyWithProof, bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String, announce_address: String,
resharing: bool,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>; ) -> Result<ExecuteResult, NyxdError>;
async fn submit_dealing_bytes( async fn submit_dealing_bytes(
&self, &self,
commitment: ContractSafeBytes, commitment: ContractSafeBytes,
resharing: bool,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>; ) -> Result<ExecuteResult, NyxdError>;
async fn submit_verification_key_share( async fn submit_verification_key_share(
&self, &self,
share: VerificationKeyShare, share: VerificationKeyShare,
resharing: bool,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>; ) -> Result<ExecuteResult, NyxdError>;
} }
@@ -57,11 +60,13 @@ where
&self, &self,
bte_key: EncodedBTEPublicKeyWithProof, bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String, announce_address: String,
resharing: bool,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> { ) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::RegisterDealer { let req = DkgExecuteMsg::RegisterDealer {
bte_key_with_proof: bte_key, bte_key_with_proof: bte_key,
announce_address, announce_address,
resharing,
}; };
self.client self.client
@@ -79,9 +84,13 @@ where
async fn submit_dealing_bytes( async fn submit_dealing_bytes(
&self, &self,
dealing_bytes: ContractSafeBytes, dealing_bytes: ContractSafeBytes,
resharing: bool,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> { ) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitDealing { dealing_bytes }; let req = DkgExecuteMsg::CommitDealing {
dealing_bytes,
resharing,
};
self.client self.client
.execute( .execute(
@@ -98,9 +107,10 @@ where
async fn submit_verification_key_share( async fn submit_verification_key_share(
&self, &self,
share: VerificationKeyShare, share: VerificationKeyShare,
resharing: bool,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> { ) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitVerificationKeyShare { share }; let req = DkgExecuteMsg::CommitVerificationKeyShare { share, resharing };
self.client self.client
.execute( .execute(
@@ -1,10 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::str::FromStr;
use clap::Parser; use clap::Parser;
use log::{debug, info}; use log::{debug, info};
use coconut_bandwidth_contract_common::msg::InstantiateMsg; use coconut_bandwidth_contract_common::msg::InstantiateMsg;
use validator_client::nyxd::AccountId;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Args { pub struct Args {
@@ -12,7 +15,7 @@ pub struct Args {
pub pool_addr: String, pub pool_addr: String,
#[clap(long)] #[clap(long)]
pub multisig_addr: Option<String>, pub multisig_addr: Option<AccountId>,
#[clap(long)] #[clap(long)]
pub mix_denom: Option<String>, pub mix_denom: Option<String>,
@@ -24,8 +27,10 @@ pub async fn generate(args: Args) {
debug!("Received arguments: {:?}", args); debug!("Received arguments: {:?}", args);
let multisig_addr = args.multisig_addr.unwrap_or_else(|| { let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS) let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
.expect("Multisig address has to be set") .expect("Multisig address has to be set");
AccountId::from_str(address.as_str())
.expect("Failed converting multisig address to AccountId")
}); });
let mix_denom = args.mix_denom.unwrap_or_else(|| { let mix_denom = args.mix_denom.unwrap_or_else(|| {
@@ -34,7 +39,7 @@ pub async fn generate(args: Args) {
let instantiate_msg = InstantiateMsg { let instantiate_msg = InstantiateMsg {
pool_addr: args.pool_addr, pool_addr: args.pool_addr,
multisig_addr, multisig_addr: multisig_addr.to_string(),
mix_denom, mix_denom,
}; };
@@ -7,6 +7,7 @@ use std::str::FromStr;
use coconut_dkg_common::msg::InstantiateMsg; use coconut_dkg_common::msg::InstantiateMsg;
use coconut_dkg_common::types::TimeConfiguration; use coconut_dkg_common::types::TimeConfiguration;
use validator_client::nyxd::AccountId;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Args { pub struct Args {
@@ -14,7 +15,7 @@ pub struct Args {
pub group_addr: String, pub group_addr: String,
#[clap(long)] #[clap(long)]
pub multisig_addr: Option<String>, pub multisig_addr: Option<AccountId>,
#[clap(long)] #[clap(long)]
pub public_key_submission_time_secs: Option<u64>, pub public_key_submission_time_secs: Option<u64>,
@@ -44,8 +45,10 @@ pub async fn generate(args: Args) {
debug!("Received arguments: {:?}", args); debug!("Received arguments: {:?}", args);
let multisig_addr = args.multisig_addr.unwrap_or_else(|| { let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS) let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
.expect("Multisig address has to be set") .expect("Multisig address has to be set");
AccountId::from_str(address.as_str())
.expect("Failed converting multisig address to AccountId")
}); });
let mix_denom = args.mix_denom.unwrap_or_else(|| { let mix_denom = args.mix_denom.unwrap_or_else(|| {
@@ -86,7 +89,7 @@ pub async fn generate(args: Args) {
let instantiate_msg = InstantiateMsg { let instantiate_msg = InstantiateMsg {
group_addr: args.group_addr, group_addr: args.group_addr,
multisig_addr, multisig_addr: multisig_addr.to_string(),
time_configuration: Some(time_configuration), time_configuration: Some(time_configuration),
mix_denom, mix_denom,
}; };
@@ -6,15 +6,17 @@ use log::{debug, info};
use cosmwasm_std::Decimal; use cosmwasm_std::Decimal;
use mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent}; use mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent};
use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use validator_client::nyxd::AccountId;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Args { pub struct Args {
#[clap(long)] #[clap(long)]
pub rewarding_validator_address: Option<String>, pub rewarding_validator_address: Option<AccountId>,
#[clap(long)] #[clap(long)]
pub vesting_contract_address: Option<String>, pub vesting_contract_address: Option<AccountId>,
#[clap(long)] #[clap(long)]
pub rewarding_denom: Option<String>, pub rewarding_denom: Option<String>,
@@ -77,13 +79,17 @@ pub async fn generate(args: Args) {
debug!("initial_rewarding_params: {:?}", initial_rewarding_params); debug!("initial_rewarding_params: {:?}", initial_rewarding_params);
let rewarding_validator_address = args.rewarding_validator_address.unwrap_or_else(|| { let rewarding_validator_address = args.rewarding_validator_address.unwrap_or_else(|| {
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS) let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
.expect("Rewarding validator address has to be set") .expect("Rewarding validator address has to be set");
AccountId::from_str(address.as_str())
.expect("Failed converting rewarding validator address to AccountId")
}); });
let vesting_contract_address = args.vesting_contract_address.unwrap_or_else(|| { let vesting_contract_address = args.vesting_contract_address.unwrap_or_else(|| {
std::env::var(network_defaults::var_names::VESTING_CONTRACT_ADDRESS) let address = std::env::var(network_defaults::var_names::VESTING_CONTRACT_ADDRESS)
.expect("Vesting contract address has to be set") .expect("Vesting contract address has to be set");
AccountId::from_str(address.as_str())
.expect("Failed converting vesting contract address to AccountId")
}); });
let rewarding_denom = args.rewarding_denom.unwrap_or_else(|| { let rewarding_denom = args.rewarding_denom.unwrap_or_else(|| {
@@ -92,8 +98,8 @@ pub async fn generate(args: Args) {
}); });
let instantiate_msg = InstantiateMsg { let instantiate_msg = InstantiateMsg {
rewarding_validator_address, rewarding_validator_address: rewarding_validator_address.to_string(),
vesting_contract_address, vesting_contract_address: vesting_contract_address.to_string(),
rewarding_denom, rewarding_denom,
epochs_in_interval: args.epochs_in_interval, epochs_in_interval: args.epochs_in_interval,
epoch_duration: Duration::from_secs(args.epoch_duration), epoch_duration: Duration::from_secs(args.epoch_duration),
@@ -1,12 +1,14 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use clap::Parser; use std::str::FromStr;
use log::{debug, info};
use clap::Parser;
use cosmwasm_std::Decimal; use cosmwasm_std::Decimal;
use cw_utils::{Duration, Threshold}; use cw_utils::{Duration, Threshold};
use log::{debug, info};
use multisig_contract_common::msg::InstantiateMsg; use multisig_contract_common::msg::InstantiateMsg;
use validator_client::nyxd::AccountId;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Args { pub struct Args {
@@ -20,10 +22,10 @@ pub struct Args {
pub max_voting_period: u64, pub max_voting_period: u64,
#[clap(long)] #[clap(long)]
pub coconut_bandwidth_contract_address: Option<String>, pub coconut_bandwidth_contract_address: Option<AccountId>,
#[clap(long)] #[clap(long)]
pub coconut_dkg_contract_address: Option<String>, pub coconut_dkg_contract_address: Option<AccountId>,
} }
pub async fn generate(args: Args) { pub async fn generate(args: Args) {
@@ -33,13 +35,18 @@ pub async fn generate(args: Args) {
let coconut_bandwidth_contract_address = let coconut_bandwidth_contract_address =
args.coconut_bandwidth_contract_address.unwrap_or_else(|| { args.coconut_bandwidth_contract_address.unwrap_or_else(|| {
std::env::var(network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS) let address =
.expect("Coconut bandwidth contract address has to be set") std::env::var(network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
.expect("Coconut bandwidth contract address has to be set");
AccountId::from_str(address.as_str())
.expect("Failed converting bandwidth contract address to AccountId")
}); });
let coconut_dkg_contract_address = args.coconut_dkg_contract_address.unwrap_or_else(|| { let coconut_dkg_contract_address = args.coconut_dkg_contract_address.unwrap_or_else(|| {
std::env::var(network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS) let address = std::env::var(network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS)
.expect("Coconut DKG contract address has to be set") .expect("Coconut DKG contract address has to be set");
AccountId::from_str(address.as_str())
.expect("Failed converting DKG contract address to AccountId")
}); });
let instantiate_msg = InstantiateMsg { let instantiate_msg = InstantiateMsg {
@@ -49,8 +56,8 @@ pub async fn generate(args: Args) {
.expect("threshold can't be converted to Decimal"), .expect("threshold can't be converted to Decimal"),
}, },
max_voting_period: Duration::Time(args.max_voting_period), max_voting_period: Duration::Time(args.max_voting_period),
coconut_bandwidth_contract_address, coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
coconut_dkg_contract_address, coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
}; };
debug!("instantiate_msg: {:?}", instantiate_msg); debug!("instantiate_msg: {:?}", instantiate_msg);
@@ -1,15 +1,18 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::str::FromStr;
use clap::Parser; use clap::Parser;
use log::{debug, info}; use log::{debug, info};
use validator_client::nyxd::AccountId;
use vesting_contract_common::InitMsg; use vesting_contract_common::InitMsg;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Args { pub struct Args {
#[clap(long)] #[clap(long)]
pub mixnet_contract_address: Option<String>, pub mixnet_contract_address: Option<AccountId>,
#[clap(long)] #[clap(long)]
pub mix_denom: Option<String>, pub mix_denom: Option<String>,
@@ -21,8 +24,10 @@ pub async fn generate(args: Args) {
debug!("Received arguments: {:?}", args); debug!("Received arguments: {:?}", args);
let mixnet_contract_address = args.mixnet_contract_address.unwrap_or_else(|| { let mixnet_contract_address = args.mixnet_contract_address.unwrap_or_else(|| {
std::env::var(network_defaults::var_names::MIXNET_CONTRACT_ADDRESS) let address = std::env::var(network_defaults::var_names::MIXNET_CONTRACT_ADDRESS)
.expect("Mixnet contract address has to be set") .expect("Mixnet contract address has to be set");
AccountId::from_str(address.as_str())
.expect("Failed converting mixnet address to AccountId")
}); });
let mix_denom = args.mix_denom.unwrap_or_else(|| { let mix_denom = args.mix_denom.unwrap_or_else(|| {
@@ -30,7 +35,7 @@ pub async fn generate(args: Args) {
}); });
let instantiate_msg = InitMsg { let instantiate_msg = InitMsg {
mixnet_contract_address, mixnet_contract_address: mixnet_contract_address.to_string(),
mix_denom, mix_denom,
}; };
+11 -27
View File
@@ -30,23 +30,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
fn default_root_directory() -> PathBuf; fn default_root_directory() -> PathBuf;
// default, most probable, implementations; can be easily overridden where required // default, most probable, implementations; can be easily overridden where required
fn default_config_directory(id: Option<&str>) -> PathBuf { fn default_config_directory(id: &str) -> PathBuf {
if let Some(id) = id { Self::default_root_directory().join(id).join(CONFIG_DIR)
Self::default_root_directory().join(id).join(CONFIG_DIR)
} else {
Self::default_root_directory().join(CONFIG_DIR)
}
} }
fn default_data_directory(id: Option<&str>) -> PathBuf { fn default_data_directory(id: &str) -> PathBuf {
if let Some(id) = id { Self::default_root_directory().join(id).join(DATA_DIR)
Self::default_root_directory().join(id).join(DATA_DIR)
} else {
Self::default_root_directory().join(DATA_DIR)
}
} }
fn default_config_file_path(id: Option<&str>) -> PathBuf { fn default_config_file_path(id: &str) -> PathBuf {
Self::default_config_directory(id).join(Self::config_file_name()) Self::default_config_directory(id).join(Self::config_file_name())
} }
@@ -54,23 +46,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
fn try_default_root_directory() -> Option<PathBuf>; fn try_default_root_directory() -> Option<PathBuf>;
fn try_default_config_directory(id: Option<&str>) -> Option<PathBuf> { fn try_default_config_directory(id: &str) -> Option<PathBuf> {
if let Some(id) = id { Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR))
Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR))
} else {
Self::try_default_root_directory().map(|d| d.join(CONFIG_DIR))
}
} }
fn try_default_data_directory(id: Option<&str>) -> Option<PathBuf> { fn try_default_data_directory(id: &str) -> Option<PathBuf> {
if let Some(id) = id { Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR))
Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR))
} else {
Self::try_default_root_directory().map(|d| d.join(DATA_DIR))
}
} }
fn try_default_config_file_path(id: Option<&str>) -> Option<PathBuf> { fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name())) Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name()))
} }
@@ -113,7 +97,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
Ok(()) Ok(())
} }
fn load_from_file(id: Option<&str>) -> io::Result<Self> { fn load_from_file(id: &str) -> io::Result<Self> {
let file = Self::default_config_file_path(id); let file = Self::default_config_file_path(id);
log::trace!("Loading from file: {:#?}", file); log::trace!("Loading from file: {:#?}", file);
let config_contents = fs::read_to_string(file)?; let config_contents = fs::read_to_string(file)?;
@@ -21,18 +21,22 @@ pub enum ExecuteMsg {
RegisterDealer { RegisterDealer {
bte_key_with_proof: EncodedBTEPublicKeyWithProof, bte_key_with_proof: EncodedBTEPublicKeyWithProof,
announce_address: String, announce_address: String,
resharing: bool,
}, },
CommitDealing { CommitDealing {
dealing_bytes: ContractSafeBytes, dealing_bytes: ContractSafeBytes,
resharing: bool,
}, },
CommitVerificationKeyShare { CommitVerificationKeyShare {
share: VerificationKeyShare, share: VerificationKeyShare,
resharing: bool,
}, },
VerifyVerificationKeyShare { VerifyVerificationKeyShare {
owner: Addr, owner: Addr,
resharing: bool,
}, },
SurpassedThreshold {}, SurpassedThreshold {},
@@ -45,6 +49,7 @@ pub enum ExecuteMsg {
pub enum QueryMsg { pub enum QueryMsg {
GetCurrentEpochState {}, GetCurrentEpochState {},
GetCurrentEpochThreshold {}, GetCurrentEpochThreshold {},
GetInitialDealers {},
GetDealerDetails { GetDealerDetails {
dealer_address: String, dealer_address: String,
}, },
@@ -18,6 +18,12 @@ pub type EpochId = u64;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential // 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub struct InitialReplacementData {
pub initial_dealers: Vec<Addr>,
pub initial_height: Option<u64>,
}
#[derive( #[derive(
Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, JsonSchema, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, JsonSchema,
)] )]
@@ -86,15 +92,17 @@ impl Epoch {
current_timestamp: Timestamp, current_timestamp: Timestamp,
) -> Self { ) -> Self {
let duration = match state { let duration = match state {
EpochState::PublicKeySubmission => time_configuration.public_key_submission_time_secs, EpochState::PublicKeySubmission { .. } => {
EpochState::DealingExchange => time_configuration.dealing_exchange_time_secs, time_configuration.public_key_submission_time_secs
EpochState::VerificationKeySubmission => { }
EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs,
EpochState::VerificationKeySubmission { .. } => {
time_configuration.verification_key_submission_time_secs time_configuration.verification_key_submission_time_secs
} }
EpochState::VerificationKeyValidation => { EpochState::VerificationKeyValidation { .. } => {
time_configuration.verification_key_validation_time_secs time_configuration.verification_key_validation_time_secs
} }
EpochState::VerificationKeyFinalization => { EpochState::VerificationKeyFinalization { .. } => {
time_configuration.verification_key_finalization_time_secs time_configuration.verification_key_finalization_time_secs
} }
EpochState::InProgress => time_configuration.in_progress_time_secs, EpochState::InProgress => time_configuration.in_progress_time_secs,
@@ -123,28 +131,36 @@ impl Epoch {
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum EpochState { pub enum EpochState {
PublicKeySubmission, PublicKeySubmission { resharing: bool },
DealingExchange, DealingExchange { resharing: bool },
VerificationKeySubmission, VerificationKeySubmission { resharing: bool },
VerificationKeyValidation, VerificationKeyValidation { resharing: bool },
VerificationKeyFinalization, VerificationKeyFinalization { resharing: bool },
InProgress, InProgress,
} }
impl Default for EpochState { impl Default for EpochState {
fn default() -> Self { fn default() -> Self {
Self::PublicKeySubmission Self::PublicKeySubmission { resharing: false }
} }
} }
impl Display for EpochState { impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
EpochState::PublicKeySubmission => write!(f, "PublicKeySubmission"), EpochState::PublicKeySubmission { resharing } => {
EpochState::DealingExchange => write!(f, "DealingExchange"), write!(f, "PublicKeySubmission with resharing {resharing}")
EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"), }
EpochState::VerificationKeyValidation => write!(f, "VerificationKeyValidation"), EpochState::DealingExchange { resharing } => write!(f, "DealingExchange {resharing}"),
EpochState::VerificationKeyFinalization => write!(f, "VerificationKeyFinalization"), EpochState::VerificationKeySubmission { resharing } => {
write!(f, "VerificationKeySubmission with resharing {resharing}")
}
EpochState::VerificationKeyValidation { resharing } => {
write!(f, "VerificationKeyValidation with resharing {resharing}")
}
EpochState::VerificationKeyFinalization { resharing } => {
write!(f, "VerificationKeyFinalization with resharing {resharing}")
}
EpochState::InProgress => write!(f, "InProgress"), EpochState::InProgress => write!(f, "InProgress"),
} }
} }
@@ -153,11 +169,19 @@ impl Display for EpochState {
impl EpochState { impl EpochState {
pub fn next(self) -> Option<Self> { pub fn next(self) -> Option<Self> {
match self { match self {
EpochState::PublicKeySubmission => Some(EpochState::DealingExchange), EpochState::PublicKeySubmission { resharing } => {
EpochState::DealingExchange => Some(EpochState::VerificationKeySubmission), Some(EpochState::DealingExchange { resharing })
EpochState::VerificationKeySubmission => Some(EpochState::VerificationKeyValidation), }
EpochState::VerificationKeyValidation => Some(EpochState::VerificationKeyFinalization), EpochState::DealingExchange { resharing } => {
EpochState::VerificationKeyFinalization => Some(EpochState::InProgress), Some(EpochState::VerificationKeySubmission { resharing })
}
EpochState::VerificationKeySubmission { resharing } => {
Some(EpochState::VerificationKeyValidation { resharing })
}
EpochState::VerificationKeyValidation { resharing } => {
Some(EpochState::VerificationKeyFinalization { resharing })
}
EpochState::VerificationKeyFinalization { .. } => Some(EpochState::InProgress),
EpochState::InProgress => None, EpochState::InProgress => None,
} }
} }
@@ -30,11 +30,12 @@ pub struct PagedVKSharesResponse {
pub fn to_cosmos_msg( pub fn to_cosmos_msg(
owner: Addr, owner: Addr,
resharing: bool,
coconut_dkg_addr: String, coconut_dkg_addr: String,
multisig_addr: String, multisig_addr: String,
expiration_time: Timestamp, expiration_time: Timestamp,
) -> StdResult<CosmosMsg> { ) -> StdResult<CosmosMsg> {
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner }; let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute { let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_dkg_addr, contract_addr: coconut_dkg_addr,
msg: to_binary(&verify_vk_share_req)?, msg: to_binary(&verify_vk_share_req)?,
@@ -62,7 +63,8 @@ pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
funds: _, funds: _,
})) = msgs.get(0) })) = msgs.get(0)
{ {
if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner }) = from_binary::<ExecuteMsg>(msg) if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner, .. }) =
from_binary::<ExecuteMsg>(msg)
{ {
return Some(owner); return Some(owner);
} }
@@ -13,3 +13,4 @@ cw4 = { version = "0.13.4" }
cosmwasm-std = "1.0.0" cosmwasm-std = "1.0.0"
schemars = "0.8" schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] } serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
@@ -1,3 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::StdError; use cosmwasm_std::StdError;
use cw_utils::ThresholdError; use cw_utils::ThresholdError;
@@ -1 +1,2 @@
pub mod error;
pub mod msg; pub mod msg;
Binary file not shown.
+10 -4
View File
@@ -92,6 +92,7 @@ pub async fn obtain_aggregate_signature(
params: &Parameters, params: &Parameters,
attributes: &BandwidthVoucher, attributes: &BandwidthVoucher,
coconut_api_clients: &[CoconutApiClient], coconut_api_clients: &[CoconutApiClient],
threshold: u64,
) -> Result<Signature, Error> { ) -> Result<Signature, Error> {
if coconut_api_clients.is_empty() { if coconut_api_clients.is_empty() {
return Err(Error::NoValidatorsAvailable); return Err(Error::NoValidatorsAvailable);
@@ -110,15 +111,20 @@ pub async fn obtain_aggregate_signature(
.collect(); .collect();
for coconut_api_client in coconut_api_clients.iter() { for coconut_api_client in coconut_api_clients.iter() {
let signature = obtain_partial_credential( if let Ok(signature) = obtain_partial_credential(
params, params,
attributes, attributes,
&coconut_api_client.api_client, &coconut_api_client.api_client,
&coconut_api_client.verification_key, &coconut_api_client.verification_key,
) )
.await?; .await
let share = SignatureShare::new(signature, coconut_api_client.node_id); {
shares.push(share) let share = SignatureShare::new(signature, coconut_api_client.node_id);
shares.push(share)
}
}
if shares.len() < threshold as usize {
return Err(Error::NotEnoughShares);
} }
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
+3
View File
@@ -29,4 +29,7 @@ pub enum Error {
#[error("Could not parse the key - {0}")] #[error("Could not parse the key - {0}")]
ParsePublicKey(#[from] KeyRecoveryError), ParsePublicKey(#[from] KeyRecoveryError),
#[error("Could not gather enough signature shares")]
NotEnoughShares,
} }
+4
View File
@@ -93,6 +93,10 @@ impl SecretKey {
Self { x, ys } Self { x, ys }
} }
pub fn into_raw(&self) -> (Scalar, Vec<Scalar>) {
(self.x, self.ys.clone())
}
/// Derive verification key using this secret key. /// Derive verification key using this secret key.
pub fn verification_key(&self, params: &Parameters) -> VerificationKey { pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
let g1 = params.gen1(); let g1 = params.gen1();
@@ -12,7 +12,7 @@ bs58 = "0.4"
serde = "1.0" serde = "1.0"
thiserror = "1" thiserror = "1"
crypto = { path = "../../crypto" } crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
nymsphinx-addressing = { path = "../addressing" } nymsphinx-addressing = { path = "../addressing" }
nymsphinx-params = { path = "../params" } nymsphinx-params = { path = "../params" }
nymsphinx-types = { path = "../types" } nymsphinx-types = { path = "../types" }
@@ -6,7 +6,7 @@ use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData}; use ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData};
use socks5_requests::ConnectionId; use socks5_requests::{ConnectionId, NetworkData, SendRequest};
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
time::Duration, time::Duration,
@@ -36,9 +36,38 @@ pub type ControllerSender = mpsc::UnboundedSender<ControllerCommand>;
pub type ControllerReceiver = mpsc::UnboundedReceiver<ControllerCommand>; pub type ControllerReceiver = mpsc::UnboundedReceiver<ControllerCommand>;
pub enum ControllerCommand { pub enum ControllerCommand {
Insert(ConnectionId, ConnectionSender), Insert {
Remove(ConnectionId), connection_id: ConnectionId,
Send(ConnectionId, Vec<u8>, bool), connection_sender: ConnectionSender,
},
Remove {
connection_id: ConnectionId,
},
Send {
connection_id: ConnectionId,
data: Vec<u8>,
is_closed: bool,
},
}
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,
}
}
} }
struct ActiveConnection { struct ActiveConnection {
@@ -235,13 +264,13 @@ impl Controller {
loop { loop {
tokio::select! { tokio::select! {
command = self.receiver.next() => match command { command = self.receiver.next() => match command {
Some(ControllerCommand::Send(conn_id, data, is_closed)) => { Some(ControllerCommand::Send{connection_id, data, is_closed}) => {
self.send_to_connection(conn_id, data, is_closed) self.send_to_connection(connection_id, data, is_closed)
} }
Some(ControllerCommand::Insert(conn_id, sender)) => { Some(ControllerCommand::Insert{connection_id, connection_sender}) => {
self.insert_connection(conn_id, sender) self.insert_connection(connection_id, connection_sender)
} }
Some(ControllerCommand::Remove(conn_id)) => self.remove_connection(conn_id), Some(ControllerCommand::Remove{ connection_id }) => self.remove_connection(connection_id),
None => { None => {
log::trace!("SOCKS5 Controller: Stopping since channel closed"); log::trace!("SOCKS5 Controller: Stopping since channel closed");
break; break;
+5 -1
View File
@@ -7,5 +7,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
nymsphinx-addressing = { path = "../../../common/nymsphinx/addressing" } nymsphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
thiserror = "1" service-providers-common = { path = "../../../service-providers/common" }
+123 -7
View File
@@ -1,12 +1,128 @@
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
pub mod msg; use service_providers_common::interface;
pub mod network_requester_response; use service_providers_common::interface::ServiceProviderMessagingError;
pub mod request; use thiserror::Error;
pub mod response;
pub use msg::*;
pub use network_requester_response::*;
pub use request::*; pub use request::*;
pub use response::*; pub use response::*;
pub use version::*;
pub mod request;
pub mod response;
pub mod version;
pub type Socks5ProviderRequest = interface::Request<Socks5Request>;
pub type Socks5ProviderResponse = interface::Response<Socks5Request>;
#[derive(Debug, Error)]
pub enum Socks5RequestError {
#[error("failed to deserialize received request: {source}")]
RequestDeserialization {
#[from]
source: RequestDeserializationError,
},
#[error("failed to deserialize received response: {source}")]
ResponseDeserialization {
#[from]
source: ResponseDeserializationError,
},
#[error(transparent)]
ProviderInterfaceError(#[from] ServiceProviderMessagingError),
}
#[cfg(test)]
mod tests {
use super::*;
use service_providers_common::interface::RequestContent;
#[cfg(test)]
mod interface_backwards_compatibility {
use super::*;
use service_providers_common::interface::ProviderInterfaceVersion;
#[test]
fn old_client_vs_new_service_provider() {
let old_serialized_connect = vec![
0, 0, 2, 254, 34, 100, 192, 20, 13, 171, 0, 16, 56, 48, 46, 50, 52, 57, 46, 57, 57,
46, 49, 52, 56, 58, 56, 48, 34, 112, 17, 182, 225, 6, 174, 216, 160, 41, 72, 236,
160, 90, 156, 3, 250, 41, 243, 53, 191, 178, 218, 53, 170, 14, 185, 33, 94, 153,
25, 41, 6, 82, 169, 187, 88, 246, 211, 57, 68, 225, 228, 231, 116, 29, 119, 235,
160, 14, 156, 205, 66, 1, 75, 204, 204, 220, 14, 150, 191, 203, 174, 88, 121, 173,
83, 219, 188, 164, 194, 212, 238, 228, 4, 128, 48, 105, 224, 83, 17, 246, 233, 16,
235, 223, 68, 87, 13, 40, 34, 186, 218, 204, 126, 145,
];
let new_deserialized =
Socks5ProviderRequest::try_from_bytes(&old_serialized_connect).unwrap();
match new_deserialized.content {
RequestContent::ProviderData(req) => match req.content {
Socks5RequestContent::Connect(connect_req) => {
assert_eq!(connect_req.remote_addr, "80.249.99.148:80".to_string());
assert_eq!(connect_req.conn_id, 215647648274976171);
assert_eq!(connect_req.return_address, Some("3KRydEpanwjFhq5GAraVjRUF1Tno7w7oc4EwJYTGNo5J.RgZ7uMJHruBQqD5hC9Ghi3sqiTn6NycfM5qCfJz6yoM@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J".parse().unwrap()));
}
_ => panic!("unexpected request"),
},
_ => panic!("unexpected request"),
}
let old_serialized_send = vec![
0, 1, 108, 102, 28, 19, 50, 178, 37, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 69, 84,
32, 47, 49, 77, 66, 46, 122, 105, 112, 32, 72, 84, 84, 80, 47, 49, 46, 49, 13, 10,
72, 111, 115, 116, 58, 32, 105, 112, 118, 52, 46, 100, 111, 119, 110, 108, 111, 97,
100, 46, 116, 104, 105, 110, 107, 98, 114, 111, 97, 100, 98, 97, 110, 100, 46, 99,
111, 109, 13, 10, 85, 115, 101, 114, 45, 65, 103, 101, 110, 116, 58, 32, 99, 117,
114, 108, 47, 55, 46, 54, 56, 46, 48, 13, 10, 65, 99, 99, 101, 112, 116, 58, 32,
42, 47, 42, 13, 10, 13, 10,
];
let new_deserialized =
Socks5ProviderRequest::try_from_bytes(&old_serialized_send).unwrap();
match new_deserialized.content {
RequestContent::ProviderData(req) => match req.content {
Socks5RequestContent::Send(send_req) => {
assert_eq!(send_req.conn_id, 7810961472501196273);
assert_eq!(send_req.data.len(), 111);
assert!(!send_req.local_closed);
}
_ => panic!("unexpected request"),
},
_ => panic!("unexpected request"),
}
}
#[test]
fn new_client_vs_old_service_provider() {
let return_address = "3KRydEpanwjFhq5GAraVjRUF1Tno7w7oc4EwJYTGNo5J.RgZ7uMJHruBQqD5hC9Ghi3sqiTn6NycfM5qCfJz6yoM@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J".parse().unwrap();
let new_connect = Socks5ProviderRequest::new_provider_data(
ProviderInterfaceVersion::Legacy,
Socks5Request::new_connect(
Socks5ProtocolVersion::Legacy,
215647648274976171,
"80.249.99.148:80".to_string(),
Some(return_address),
),
);
let legacy_serialised = new_connect.into_bytes();
let old_serialized_connect = vec![
0, 0, 2, 254, 34, 100, 192, 20, 13, 171, 0, 16, 56, 48, 46, 50, 52, 57, 46, 57, 57,
46, 49, 52, 56, 58, 56, 48, 34, 112, 17, 182, 225, 6, 174, 216, 160, 41, 72, 236,
160, 90, 156, 3, 250, 41, 243, 53, 191, 178, 218, 53, 170, 14, 185, 33, 94, 153,
25, 41, 6, 82, 169, 187, 88, 246, 211, 57, 68, 225, 228, 231, 116, 29, 119, 235,
160, 14, 156, 205, 66, 1, 75, 204, 204, 220, 14, 150, 191, 203, 174, 88, 121, 173,
83, 219, 188, 164, 194, 212, 238, 228, 4, 128, 48, 105, 224, 83, 17, 246, 233, 16,
235, 223, 68, 87, 13, 40, 34, 186, 218, 204, 126, 145,
];
assert_eq!(legacy_serialised, old_serialized_connect);
}
}
}
-97
View File
@@ -1,97 +0,0 @@
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
use crate::network_requester_response::{Error as NrError, NetworkRequesterResponse};
use crate::request::{Request, RequestError};
use crate::response::{Response, ResponseError};
#[derive(Debug, Error)]
pub enum MessageError {
#[error(transparent)]
Request(RequestError),
#[error("{0:?}")]
Response(ResponseError),
#[error(transparent)]
NetworkRequesterResponseError(NrError),
#[error("no data")]
NoData,
#[error("unknown message type received")]
UnknownMessageType,
}
#[derive(Debug)]
pub enum Message {
Request(Request),
Response(Response),
NetworkRequesterResponse(NetworkRequesterResponse),
}
impl Message {
const REQUEST_FLAG: u8 = 0;
const RESPONSE_FLAG: u8 = 1;
const NR_RESPONSE_FLAG: u8 = 2;
pub fn conn_id(&self) -> u64 {
match self {
Message::Request(req) => match req {
Request::Connect(c) => c.conn_id,
Request::Send(conn_id, _, _) => *conn_id,
},
Message::Response(resp) => resp.connection_id,
Message::NetworkRequesterResponse(resp) => resp.connection_id,
}
}
pub fn size(&self) -> usize {
match self {
Message::Request(req) => match req {
Request::Connect(_) => 0,
Request::Send(_, data, _) => data.len(),
},
Message::Response(resp) => resp.data.len(),
Message::NetworkRequesterResponse(_) => 0,
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<Message, MessageError> {
if b.is_empty() {
return Err(MessageError::NoData);
}
if b[0] == Self::REQUEST_FLAG {
Request::try_from_bytes(&b[1..])
.map(Message::Request)
.map_err(MessageError::Request)
} else if b[0] == Self::RESPONSE_FLAG {
Response::try_from_bytes(&b[1..])
.map(Message::Response)
.map_err(MessageError::Response)
} else if b[0] == Self::NR_RESPONSE_FLAG {
NetworkRequesterResponse::try_from_bytes(&b[1..])
.map(Message::NetworkRequesterResponse)
.map_err(MessageError::NetworkRequesterResponseError)
} else {
Err(MessageError::UnknownMessageType)
}
}
pub fn into_bytes(self) -> Vec<u8> {
match self {
Self::Request(r) => std::iter::once(Self::REQUEST_FLAG)
.chain(r.into_bytes().iter().cloned())
.collect(),
Self::Response(r) => std::iter::once(Self::RESPONSE_FLAG)
.chain(r.into_bytes().iter().cloned())
.collect(),
Self::NetworkRequesterResponse(r) => std::iter::once(Self::NR_RESPONSE_FLAG)
.chain(r.into_bytes().iter().cloned())
.collect(),
}
}
}
@@ -1,112 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::ConnectionId;
#[derive(Debug)]
pub struct NetworkRequesterResponse {
pub connection_id: ConnectionId,
pub network_requester_error: String,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("no data provided")]
NoData,
#[error("not enough bytes to recover the connection id")]
ConnectionIdTooShort,
#[error("message is not utf8 encoded")]
MalformedErrorMessage(#[from] std::string::FromUtf8Error),
}
impl NetworkRequesterResponse {
pub fn new(connection_id: ConnectionId, network_requester_error: String) -> Self {
NetworkRequesterResponse {
connection_id,
network_requester_error,
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<NetworkRequesterResponse, Error> {
if b.is_empty() {
return Err(Error::NoData);
}
if b.len() < 8 {
return Err(Error::ConnectionIdTooShort);
}
let mut connection_id_bytes = b.to_vec();
let network_requester_error_bytes = connection_id_bytes.split_off(8);
let connection_id = u64::from_be_bytes([
connection_id_bytes[0],
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],
]);
let network_requester_error = String::from_utf8(network_requester_error_bytes)?;
Ok(NetworkRequesterResponse {
connection_id,
network_requester_error,
})
}
pub fn into_bytes(self) -> Vec<u8> {
self.connection_id
.to_be_bytes()
.iter()
.copied()
.chain(self.network_requester_error.into_bytes().into_iter())
.collect()
}
}
#[cfg(test)]
mod network_requester_response_serde_tests {
use super::*;
#[test]
fn simple_serde() {
let conn_id = 42;
let network_requester_error = String::from("This is a test msg");
let response = NetworkRequesterResponse::new(conn_id, network_requester_error.clone());
let bytes = response.into_bytes();
let deserialized_response = NetworkRequesterResponse::try_from_bytes(&bytes).unwrap();
assert_eq!(conn_id, deserialized_response.connection_id);
assert_eq!(
network_requester_error,
deserialized_response.network_requester_error
);
}
#[test]
fn deserialization_errors() {
let err = NetworkRequesterResponse::try_from_bytes(&[]).err().unwrap();
assert_eq!(err, Error::NoData);
let bytes: [u8; 5] = [1, 2, 3, 4, 5];
let err = NetworkRequesterResponse::try_from_bytes(&bytes)
.err()
.unwrap();
assert_eq!(err, Error::ConnectionIdTooShort);
let bytes: Vec<u8> = 42u64
.to_be_bytes()
.into_iter()
.chain([0, 159, 146, 150].into_iter())
.collect();
let err = NetworkRequesterResponse::try_from_bytes(&bytes)
.err()
.unwrap();
assert!(matches!(err, Error::MalformedErrorMessage(_)));
}
}
+178 -55
View File
@@ -1,7 +1,9 @@
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response};
use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError}; use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError};
use service_providers_common::interface::{Serializable, ServiceProviderRequest};
use std::convert::TryFrom; use std::convert::TryFrom;
use thiserror::Error; use thiserror::Error;
@@ -16,19 +18,19 @@ pub enum RequestFlag {
} }
impl TryFrom<u8> for RequestFlag { impl TryFrom<u8> for RequestFlag {
type Error = RequestError; type Error = RequestDeserializationError;
fn try_from(value: u8) -> Result<RequestFlag, RequestError> { fn try_from(value: u8) -> Result<RequestFlag, RequestDeserializationError> {
match value { match value {
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect), _ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send), _ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
_ => Err(RequestError::UnknownRequestFlag), value => Err(RequestDeserializationError::UnknownRequestFlag { value }),
} }
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum RequestError { pub enum RequestDeserializationError {
#[error("not enough bytes to recover the length of the address")] #[error("not enough bytes to recover the length of the address")]
AddressLengthTooShort, AddressLengthTooShort,
@@ -41,8 +43,8 @@ pub enum RequestError {
#[error("no data provided")] #[error("no data provided")]
NoData, NoData,
#[error("request of unknown type")] #[error("{value} is not a valid request flag")]
UnknownRequestFlag, UnknownRequestFlag { value: u8 },
#[error("too short return address")] #[error("too short return address")]
ReturnAddressTooShort, ReturnAddressTooShort,
@@ -51,13 +53,13 @@ pub enum RequestError {
MalformedReturnAddress(RecipientFormattingError), MalformedReturnAddress(RecipientFormattingError),
} }
impl RequestError { impl RequestDeserializationError {
pub fn is_malformed_return(&self) -> bool { pub fn is_malformed_return(&self) -> bool {
matches!(self, RequestError::MalformedReturnAddress(_)) matches!(self, RequestDeserializationError::MalformedReturnAddress(_))
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct ConnectRequest { pub struct ConnectRequest {
// TODO: is connection_id redundant now? // TODO: is connection_id redundant now?
pub conn_id: ConnectionId, pub conn_id: ConnectionId,
@@ -65,27 +67,128 @@ pub struct ConnectRequest {
pub return_address: Option<Recipient>, pub return_address: Option<Recipient>,
} }
#[derive(Debug, Clone)]
pub struct SendRequest {
pub conn_id: ConnectionId,
pub data: Vec<u8>,
pub local_closed: bool,
}
#[derive(Debug, Clone)]
pub struct Socks5Request {
pub protocol_version: Socks5ProtocolVersion,
pub content: Socks5RequestContent,
}
impl Serializable for Socks5Request {
type Error = Socks5RequestError;
// legacy requests had the format of
// 0 (Message::REQUEST_FLAG) || 0 (RequestFlag::Connect) || <data> for connect requests
// 0 (Message::REQUEST_FLAG) || 1 (RequestFlag::Send) || <data> for send requests
// the updated formats use
// 3 (Socks5ProtocolVersion) || 0 (RequestFlag::Connect) || <data> for connect requests
// 3 (Socks5ProtocolVersion) || 1 (RequestFlag::Send) || <data> for send requests
// in both cases, the actual data is serialized the same way, so the process is quite straight forward
fn into_bytes(self) -> Vec<u8> {
if let Some(version) = self.protocol_version.as_u8() {
std::iter::once(version)
.chain(self.content.into_bytes().into_iter())
.collect()
} else {
std::iter::once(Self::LEGACY_TYPE_TAG)
.chain(self.content.into_bytes())
.collect()
}
}
fn try_from_bytes(b: &[u8]) -> Result<Self, Self::Error> {
if b.is_empty() {
return Err(RequestDeserializationError::NoData.into());
}
let protocol_version = Socks5ProtocolVersion::from(b[0]);
Ok(Socks5Request {
protocol_version,
content: Socks5RequestContent::try_from_bytes(&b[1..])?,
})
}
}
impl ServiceProviderRequest for Socks5Request {
type ProtocolVersion = Socks5ProtocolVersion;
type Response = Socks5Response;
type Error = Socks5RequestError;
fn provider_specific_version(&self) -> Self::ProtocolVersion {
self.protocol_version
}
fn max_supported_version() -> Self::ProtocolVersion {
Socks5ProtocolVersion::new_current()
}
}
impl Socks5Request {
// type tag that used to be prepended to all request messages
const LEGACY_TYPE_TAG: u8 = 0x00;
pub fn new(
protocol_version: Socks5ProtocolVersion,
content: Socks5RequestContent,
) -> Socks5Request {
Socks5Request {
protocol_version,
content,
}
}
pub fn new_connect(
protocol_version: Socks5ProtocolVersion,
conn_id: ConnectionId,
remote_addr: RemoteAddress,
return_address: Option<Recipient>,
) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::new_connect(conn_id, remote_addr, return_address),
}
}
pub fn new_send(
protocol_version: Socks5ProtocolVersion,
conn_id: ConnectionId,
data: Vec<u8>,
local_closed: bool,
) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::new_send(conn_id, data, local_closed),
}
}
}
/// A request from a SOCKS5 client that a Nym Socks5 service provider should /// 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. /// take an action for an application using a (probably local) Nym Socks5 proxy.
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Request { pub enum Socks5RequestContent {
/// Start a new TCP connection to the specified `RemoteAddress` and send /// Start a new TCP connection to the specified `RemoteAddress` and send
/// the request data up the connection. /// the request data up the connection.
/// All responses produced on this `ConnectionId` should come back to the specified `Recipient` /// All responses produced on this `ConnectionId` should come back to the specified `Recipient`
Connect(Box<ConnectRequest>), Connect(Box<ConnectRequest>),
/// Re-use an existing TCP connection, sending more request data up it. /// Re-use an existing TCP connection, sending more request data up it.
Send(ConnectionId, Vec<u8>, bool), Send(SendRequest),
} }
impl Request { impl Socks5RequestContent {
/// Construct a new Request::Connect instance /// Construct a new Request::Connect instance
pub fn new_connect( pub fn new_connect(
conn_id: ConnectionId, conn_id: ConnectionId,
remote_addr: RemoteAddress, remote_addr: RemoteAddress,
return_address: Option<Recipient>, return_address: Option<Recipient>,
) -> Request { ) -> Socks5RequestContent {
Request::Connect(Box::new(ConnectRequest { Socks5RequestContent::Connect(Box::new(ConnectRequest {
conn_id, conn_id,
remote_addr, remote_addr,
return_address, return_address,
@@ -93,8 +196,16 @@ impl Request {
} }
/// Construct a new Request::Send instance /// Construct a new Request::Send instance
pub fn new_send(conn_id: ConnectionId, data: Vec<u8>, local_closed: bool) -> Request { pub fn new_send(
Request::Send(conn_id, data, local_closed) 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, /// Deserialize the request type, connection id, destination address and port,
@@ -111,23 +222,23 @@ impl Request {
/// The request_flag tells us whether this is a new connection request (`new_connect`), /// 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 /// an already-established connection we should send up (`new_send`), or
/// a request to close an established connection (`new_close`). /// a request to close an established connection (`new_close`).
pub fn try_from_bytes(b: &[u8]) -> Result<Request, RequestError> { pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
// each request needs to at least contain flag and ConnectionId // each request needs to at least contain flag and ConnectionId
if b.is_empty() { if b.is_empty() {
return Err(RequestError::NoData); return Err(RequestDeserializationError::NoData);
} }
if b.len() < 9 { if b.len() < 9 {
return Err(RequestError::ConnectionIdTooShort); return Err(RequestDeserializationError::ConnectionIdTooShort);
} }
let connection_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]); 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])? { match RequestFlag::try_from(b[0])? {
RequestFlag::Connect => { RequestFlag::Connect => {
let connect_request_bytes = &b[9..]; let connect_request_bytes = &b[9..];
// we need to be able to read at least 2 bytes that specify address length // we need to be able to read at least 2 bytes that specify address length
if connect_request_bytes.len() < 2 { if connect_request_bytes.len() < 2 {
return Err(RequestError::AddressLengthTooShort); return Err(RequestDeserializationError::AddressLengthTooShort);
} }
let address_length = let address_length =
@@ -135,7 +246,7 @@ impl Request {
as usize; as usize;
if connect_request_bytes.len() < 2 + address_length { if connect_request_bytes.len() < 2 + address_length {
return Err(RequestError::AddressTooShort); return Err(RequestDeserializationError::AddressTooShort);
} }
let address_start = 2; let address_start = 2;
@@ -150,19 +261,19 @@ impl Request {
None None
} else { } else {
if recipient_data_bytes.len() != Recipient::LEN { if recipient_data_bytes.len() != Recipient::LEN {
return Err(RequestError::ReturnAddressTooShort); return Err(RequestDeserializationError::ReturnAddressTooShort);
} }
let mut return_bytes = [0u8; Recipient::LEN]; let mut return_bytes = [0u8; Recipient::LEN];
return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]); return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]);
Some( Some(
Recipient::try_from_bytes(return_bytes) Recipient::try_from_bytes(return_bytes)
.map_err(RequestError::MalformedReturnAddress)?, .map_err(RequestDeserializationError::MalformedReturnAddress)?,
) )
}; };
Ok(Request::new_connect( Ok(Socks5RequestContent::new_connect(
connection_id, conn_id,
remote_address, remote_address,
return_address, return_address,
)) ))
@@ -171,7 +282,11 @@ impl Request {
let local_closed = b[9] != 0; let local_closed = b[9] != 0;
let data = b[10..].to_vec(); let data = b[10..].to_vec();
Ok(Request::Send(connection_id, data, local_closed)) Ok(Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}))
} }
} }
} }
@@ -182,7 +297,7 @@ impl Request {
pub fn into_bytes(self) -> Vec<u8> { pub fn into_bytes(self) -> Vec<u8> {
match self { match self {
// connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN // connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN
Request::Connect(req) => { Socks5RequestContent::Connect(req) => {
let remote_address_bytes = req.remote_addr.into_bytes(); let remote_address_bytes = req.remote_addr.into_bytes();
let remote_address_bytes_len = remote_address_bytes.len() as u16; let remote_address_bytes_len = remote_address_bytes.len() as u16;
@@ -197,10 +312,10 @@ impl Request {
iter.collect() iter.collect()
} }
} }
Request::Send(conn_id, data, local_closed) => std::iter::once(RequestFlag::Send as u8) Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8)
.chain(conn_id.to_be_bytes().into_iter()) .chain(req.conn_id.to_be_bytes().into_iter())
.chain(std::iter::once(local_closed as u8)) .chain(std::iter::once(req.local_closed as u8))
.chain(data.into_iter()) .chain(req.data.into_iter())
.collect(), .collect(),
} }
} }
@@ -216,8 +331,8 @@ mod request_deserialization_tests {
#[test] #[test]
fn returns_error_when_zero_bytes() { fn returns_error_when_zero_bytes() {
let request_bytes = Vec::new(); let request_bytes = Vec::new();
match Request::try_from_bytes(&request_bytes).unwrap_err() { match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::NoData => {} RequestDeserializationError::NoData => {}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -225,8 +340,8 @@ mod request_deserialization_tests {
#[test] #[test]
fn returns_error_when_connection_id_too_short() { fn returns_error_when_connection_id_too_short() {
let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7].to_vec(); // 7 bytes connection id let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7].to_vec(); // 7 bytes connection id
match Request::try_from_bytes(&request_bytes).unwrap_err() { match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::ConnectionIdTooShort => {} RequestDeserializationError::ConnectionIdTooShort => {}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -241,13 +356,13 @@ mod request_deserialization_tests {
let request_bytes1 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8].to_vec(); // 8 bytes connection id, 0 bytes address length (2 were expected) let request_bytes1 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8].to_vec(); // 8 bytes connection id, 0 bytes address length (2 were expected)
let request_bytes2 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec(); // 8 bytes connection id, 1 bytes address length (2 were expected) let request_bytes2 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec(); // 8 bytes connection id, 1 bytes address length (2 were expected)
match Request::try_from_bytes(&request_bytes1).unwrap_err() { match Socks5RequestContent::try_from_bytes(&request_bytes1).unwrap_err() {
RequestError::AddressLengthTooShort => {} RequestDeserializationError::AddressLengthTooShort => {}
_ => unreachable!(), _ => unreachable!(),
} }
match Request::try_from_bytes(&request_bytes2).unwrap_err() { match Socks5RequestContent::try_from_bytes(&request_bytes2).unwrap_err() {
RequestError::AddressLengthTooShort => {} RequestDeserializationError::AddressLengthTooShort => {}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -255,8 +370,8 @@ mod request_deserialization_tests {
#[test] #[test]
fn returns_error_when_address_too_short_for_given_address_length() { fn returns_error_when_address_too_short_for_given_address_length() {
let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1].to_vec(); // 8 bytes connection id, 2 bytes address length, missing address let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1].to_vec(); // 8 bytes connection id, 2 bytes address length, missing address
match Request::try_from_bytes(&request_bytes).unwrap_err() { match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::AddressTooShort => {} RequestDeserializationError::AddressTooShort => {}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -295,8 +410,8 @@ mod request_deserialization_tests {
.chain(recipient_bytes.iter().take(40).cloned()) .chain(recipient_bytes.iter().take(40).cloned())
.collect(); .collect();
match Request::try_from_bytes(&request_bytes).unwrap_err() { match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::ReturnAddressTooShort => {} RequestDeserializationError::ReturnAddressTooShort => {}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -338,7 +453,7 @@ mod request_deserialization_tests {
.cloned() .cloned()
.chain(recipient_bytes.into_iter()) .chain(recipient_bytes.into_iter())
.collect(); .collect();
assert!(Request::try_from_bytes(&request_bytes) assert!(Socks5RequestContent::try_from_bytes(&request_bytes)
.unwrap_err() .unwrap_err()
.is_malformed_return()); .is_malformed_return());
} }
@@ -376,9 +491,9 @@ mod request_deserialization_tests {
.chain(recipient_bytes.into_iter()) .chain(recipient_bytes.into_iter())
.collect(); .collect();
let request = Request::try_from_bytes(&request_bytes).unwrap(); let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request { match request {
Request::Connect(req) => { Socks5RequestContent::Connect(req) => {
assert_eq!("foo.com".to_string(), req.remote_addr); assert_eq!("foo.com".to_string(), req.remote_addr);
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id); assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id);
assert_eq!( assert_eq!(
@@ -423,9 +538,9 @@ mod request_deserialization_tests {
.chain(recipient_bytes.into_iter()) .chain(recipient_bytes.into_iter())
.collect(); .collect();
let request = Request::try_from_bytes(&request_bytes).unwrap(); let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request { match request {
Request::Connect(req) => { Socks5RequestContent::Connect(req) => {
assert_eq!("foo.com".to_string(), req.remote_addr); assert_eq!("foo.com".to_string(), req.remote_addr);
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id); assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id);
assert_eq!( assert_eq!(
@@ -446,9 +561,13 @@ mod request_deserialization_tests {
fn works_when_request_is_sized_properly_even_without_data() { fn works_when_request_is_sized_properly_even_without_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 0 bytes request data // 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_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec();
let request = Request::try_from_bytes(&request_bytes).unwrap(); let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request { match request {
Request::Send(conn_id, data, local_closed) => { 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!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(Vec::<u8>::new(), data); assert_eq!(Vec::<u8>::new(), data);
assert!(!local_closed) assert!(!local_closed)
@@ -477,9 +596,13 @@ mod request_deserialization_tests {
] ]
.to_vec(); .to_vec();
let request = Request::try_from_bytes(&request_bytes).unwrap(); let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request { match request {
Request::Send(conn_id, data, local_closed) => { 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!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(vec![255, 255, 255], data); assert_eq!(vec![255, 255, 255], data);
assert!(!local_closed) assert!(!local_closed)
+348 -55
View File
@@ -1,46 +1,239 @@
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError};
use service_providers_common::interface::{Serializable, ServiceProviderResponse};
use thiserror::Error; use thiserror::Error;
use crate::ConnectionId; // don't start tags from 0 for easier backwards compatibility since `NetworkData`
// used to be a `Response` with tag 1
// and `ConnectionError` used to be `NetworkRequesterResponse` with tag 2
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ResponseFlag {
NetworkData = 1,
ConnectionError = 2,
}
impl TryFrom<u8> for ResponseFlag {
type Error = ResponseDeserializationError;
fn try_from(value: u8) -> Result<ResponseFlag, ResponseDeserializationError> {
match value {
_ if value == (ResponseFlag::NetworkData as u8) => Ok(Self::NetworkData),
_ if value == (ResponseFlag::ConnectionError as u8) => Ok(Self::ConnectionError),
value => Err(ResponseDeserializationError::UnknownResponseFlag { value }),
}
}
}
#[derive(Debug, Error, PartialEq, Eq)] #[derive(Debug, Error, PartialEq, Eq)]
pub enum ResponseError { pub enum ResponseDeserializationError {
#[error("not enough bytes to recover the connection id")] #[error("not enough bytes to recover the connection id")]
ConnectionIdTooShort, ConnectionIdTooShort,
#[error("{value} is not a valid response flag")]
UnknownResponseFlag { value: u8 },
#[error("no data provided")] #[error("no data provided")]
NoData, NoData,
#[error("message is not utf8 encoded: {source}")]
MalformedErrorMessage {
#[from]
source: std::string::FromUtf8Error,
},
} }
/// A remote network response retrieved by the Socks5 service provider. This
#[derive(Debug)]
pub struct Socks5Response {
pub protocol_version: Socks5ProtocolVersion,
pub content: Socks5ResponseContent,
}
impl Serializable for Socks5Response {
type Error = Socks5RequestError;
// legacy responses had the format of
// 1 (Message::RESPONSE_FLAG) || <data> for data responses
// 2 (Message::NR_RESPONSE_FLAG) || <data> for error responses
// the updated formats use
// 3 (Socks5ProtocolVersion) || 0 (ResponseFlag::NetworkData) || <data> for data responses
// 3 (Socks5ProtocolVersion) || 1 (ResponseFlag::ConnectionError) || <data> for error responses
// so for serialization an optional version tag is prepended
// and in deserialization it's just the case of shifting the buffer in case of non-legacy response payload
fn into_bytes(self) -> Vec<u8> {
if let Some(version) = self.protocol_version.as_u8() {
std::iter::once(version)
.chain(self.content.into_bytes().into_iter())
.collect()
} else {
self.content.into_bytes()
}
}
fn try_from_bytes(b: &[u8]) -> Result<Self, Self::Error> {
if b.is_empty() {
return Err(ResponseDeserializationError::NoData.into());
}
let protocol_version = Socks5ProtocolVersion::from(b[0]);
let content = if protocol_version.is_legacy() {
Socks5ResponseContent::try_from_bytes(b)
} else {
Socks5ResponseContent::try_from_bytes(&b[1..])
}?;
Ok(Socks5Response {
protocol_version,
content,
})
}
}
impl ServiceProviderResponse for Socks5Response {}
impl Socks5Response {
pub fn new(
protocol_version: Socks5ProtocolVersion,
content: Socks5ResponseContent,
) -> Socks5Response {
Socks5Response {
protocol_version,
content,
}
}
pub fn new_network_data(
protocol_version: Socks5ProtocolVersion,
connection_id: ConnectionId,
data: Vec<u8>,
is_closed: bool,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::new_network_data(connection_id, data, is_closed),
}
}
pub fn new_closed_empty(
protocol_version: Socks5ProtocolVersion,
connection_id: ConnectionId,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::new_closed_empty(connection_id),
}
}
pub fn new_connection_error(
protocol_version: Socks5ProtocolVersion,
connection_id: ConnectionId,
error_message: String,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::new_connection_error(connection_id, error_message),
}
}
}
#[derive(Debug)]
pub enum Socks5ResponseContent {
NetworkData(NetworkData),
ConnectionError(ConnectionError),
}
impl Socks5ResponseContent {
pub fn new_network_data(
connection_id: ConnectionId,
data: Vec<u8>,
is_closed: bool,
) -> Socks5ResponseContent {
Socks5ResponseContent::NetworkData(NetworkData::new(connection_id, data, is_closed))
}
pub fn new_closed_empty(connection_id: ConnectionId) -> Socks5ResponseContent {
Socks5ResponseContent::NetworkData(NetworkData::new_closed_empty(connection_id))
}
pub fn new_connection_error(
connection_id: ConnectionId,
error_message: String,
) -> Socks5ResponseContent {
Socks5ResponseContent::ConnectionError(ConnectionError::new(connection_id, error_message))
}
pub fn into_bytes(self) -> Vec<u8> {
match self {
Socks5ResponseContent::NetworkData(res) => {
std::iter::once(ResponseFlag::NetworkData as u8)
.chain(res.into_bytes().into_iter())
.collect()
}
Socks5ResponseContent::ConnectionError(res) => {
std::iter::once(ResponseFlag::ConnectionError as u8)
.chain(res.into_bytes().into_iter())
.collect()
}
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5ResponseContent, ResponseDeserializationError> {
if b.is_empty() {
// TODO: bad error type since this branch could be reached in the 'versioned' case
// after reading 1 byte already
return Err(ResponseDeserializationError::NoData);
}
let response_flag = ResponseFlag::try_from(b[0])?;
match response_flag {
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData(
NetworkData::try_from_bytes(&b[1..])?,
)),
ResponseFlag::ConnectionError => Ok(Socks5ResponseContent::ConnectionError(
ConnectionError::try_from_bytes(&b[1..])?,
)),
}
}
}
/// 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 /// can be serialized and sent back through the mixnet to the requesting
/// application. /// application.
#[derive(Debug)] #[derive(Debug)]
pub struct Response { pub struct NetworkData {
pub data: Vec<u8>, pub data: Vec<u8>,
pub connection_id: ConnectionId, pub connection_id: ConnectionId,
pub is_closed: bool, pub is_closed: bool,
} }
impl Response { impl NetworkData {
/// Constructor for responses /// Constructor for responses
pub fn new(connection_id: ConnectionId, data: Vec<u8>, is_closed: bool) -> Self { pub fn new(connection_id: ConnectionId, data: Vec<u8>, is_closed: bool) -> Self {
Response { NetworkData {
data, data,
connection_id, connection_id,
is_closed, is_closed,
} }
} }
pub fn try_from_bytes(b: &[u8]) -> Result<Response, ResponseError> { 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() { if b.is_empty() {
return Err(ResponseError::NoData); return Err(ResponseDeserializationError::NoData);
} }
let is_closed = b[0] != 0; let is_closed = b[0] != 0;
if b.len() < 9 { if b.len() < 9 {
return Err(ResponseError::ConnectionIdTooShort); return Err(ResponseDeserializationError::ConnectionIdTooShort);
} }
let mut connection_id_bytes = b.to_vec(); let mut connection_id_bytes = b.to_vec();
@@ -57,7 +250,7 @@ impl Response {
connection_id_bytes[8], connection_id_bytes[8],
]); ]);
let response = Response::new(connection_id, data, is_closed); let response = NetworkData::new(connection_id, data, is_closed);
Ok(response) Ok(response)
} }
@@ -71,54 +264,154 @@ impl Response {
} }
} }
#[cfg(test)] #[derive(Debug)]
mod constructing_socks5_responses_from_bytes { pub struct ConnectionError {
use super::*; pub connection_id: ConnectionId,
pub network_requester_error: String,
}
#[test] impl ConnectionError {
fn fails_when_zero_bytes_are_supplied() { pub fn new(connection_id: ConnectionId, network_requester_error: String) -> Self {
let response_bytes = Vec::new(); ConnectionError {
connection_id,
assert_eq!( network_requester_error,
ResponseError::NoData, }
Response::try_from_bytes(&response_bytes).unwrap_err()
);
} }
#[test] pub fn try_from_bytes(b: &[u8]) -> Result<ConnectionError, ResponseDeserializationError> {
fn fails_when_connection_id_bytes_are_too_short() { if b.is_empty() {
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6]; return Err(ResponseDeserializationError::NoData);
assert_eq!( }
ResponseError::ConnectionIdTooShort,
Response::try_from_bytes(&response_bytes).unwrap_err() if b.len() < 8 {
); return Err(ResponseDeserializationError::ConnectionIdTooShort);
}
let mut connection_id_bytes = b.to_vec();
let network_requester_error_bytes = connection_id_bytes.split_off(8);
let connection_id = u64::from_be_bytes([
connection_id_bytes[0],
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],
]);
let network_requester_error = String::from_utf8(network_requester_error_bytes)?;
Ok(ConnectionError {
connection_id,
network_requester_error,
})
} }
#[test] pub fn into_bytes(self) -> Vec<u8> {
fn works_when_there_is_no_data() { self.connection_id
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7]; .to_be_bytes()
let expected = Response::new( .iter()
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]), .copied()
Vec::new(), .chain(self.network_requester_error.into_bytes().into_iter())
false, .collect()
); }
let actual = Response::try_from_bytes(&response_bytes).unwrap(); }
assert_eq!(expected.connection_id, actual.connection_id);
assert_eq!(expected.data, actual.data); #[cfg(test)]
assert_eq!(expected.is_closed, actual.is_closed); mod tests {
} use super::*;
#[test] #[cfg(test)]
fn works_when_there_is_data() { mod constructing_socks5_data_responses_from_bytes {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255]; use super::*;
let expected = Response::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]), #[test]
vec![255, 255, 255], fn fails_when_zero_bytes_are_supplied() {
false, let response_bytes = Vec::new();
);
let actual = Response::try_from_bytes(&response_bytes).unwrap(); assert_eq!(
assert_eq!(expected.connection_id, actual.connection_id); ResponseDeserializationError::NoData,
assert_eq!(expected.data, actual.data); NetworkData::try_from_bytes(&response_bytes).unwrap_err()
assert_eq!(expected.is_closed, actual.is_closed); );
}
#[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::*;
#[test]
fn simple_serde() {
let conn_id = 42;
let network_requester_error = String::from("This is a test msg");
let response = ConnectionError::new(conn_id, network_requester_error.clone());
let bytes = response.into_bytes();
let deserialized_response = ConnectionError::try_from_bytes(&bytes).unwrap();
assert_eq!(conn_id, deserialized_response.connection_id);
assert_eq!(
network_requester_error,
deserialized_response.network_requester_error
);
}
#[test]
fn deserialization_errors() {
let err = ConnectionError::try_from_bytes(&[]).err().unwrap();
assert_eq!(err, ResponseDeserializationError::NoData);
let bytes: [u8; 5] = [1, 2, 3, 4, 5];
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
assert_eq!(err, ResponseDeserializationError::ConnectionIdTooShort);
let bytes: Vec<u8> = 42u64
.to_be_bytes()
.into_iter()
.chain([0, 159, 146, 150].into_iter())
.collect();
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
assert!(matches!(
err,
ResponseDeserializationError::MalformedErrorMessage { .. }
));
}
} }
} }
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use service_providers_common::{define_simple_version, interface::Version};
/// Defines initial version of the communication interface between socks5 clients and
/// network requesters (socks5).
// note: we start from '3' so that we could distinguish cases where no version is provided
// and legacy communication mode is used instead
pub const INITIAL_INTERFACE_VERSION: u8 = 3;
/// Defines the current version of the communication interface between socks5 clients and
/// network requesters (socks5).
/// It has to be incremented for any breaking change.
pub const INTERFACE_VERSION: u8 = 3;
define_simple_version!(
Socks5ProtocolVersion,
INITIAL_INTERFACE_VERSION,
INTERFACE_VERSION
);
BIN
View File
Binary file not shown.
+17 -9
View File
@@ -7,7 +7,9 @@ use crate::dealers::queries::{
use crate::dealers::transactions::try_add_dealer; use crate::dealers::transactions::try_add_dealer;
use crate::dealings::queries::query_dealings_paged; use crate::dealings::queries::query_dealings_paged;
use crate::dealings::transactions::try_commit_dealings; use crate::dealings::transactions::try_commit_dealings;
use crate::epoch_state::queries::{query_current_epoch, query_current_epoch_threshold}; use crate::epoch_state::queries::{
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
};
use crate::epoch_state::storage::CURRENT_EPOCH; use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold}; use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold};
use crate::error::ContractError; use crate::error::ContractError;
@@ -75,15 +77,17 @@ pub fn execute(
ExecuteMsg::RegisterDealer { ExecuteMsg::RegisterDealer {
bte_key_with_proof, bte_key_with_proof,
announce_address, announce_address,
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address), resharing,
ExecuteMsg::CommitDealing { dealing_bytes } => { } => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing),
try_commit_dealings(deps, info, dealing_bytes) ExecuteMsg::CommitDealing {
dealing_bytes,
resharing,
} => try_commit_dealings(deps, info, dealing_bytes, resharing),
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
try_commit_verification_key_share(deps, env, info, share, resharing)
} }
ExecuteMsg::CommitVerificationKeyShare { share } => { ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => {
try_commit_verification_key_share(deps, env, info, share) try_verify_verification_key_share(deps, info, owner, resharing)
}
ExecuteMsg::VerifyVerificationKeyShare { owner } => {
try_verify_verification_key_share(deps, info, owner)
} }
ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env), ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env),
ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env), ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env),
@@ -97,6 +101,7 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
QueryMsg::GetCurrentEpochThreshold {} => { QueryMsg::GetCurrentEpochThreshold {} => {
to_binary(&query_current_epoch_threshold(deps.storage)?)? to_binary(&query_current_epoch_threshold(deps.storage)?)?
} }
QueryMsg::GetInitialDealers {} => to_binary(&query_initial_dealers(deps.storage)?)?,
QueryMsg::GetDealerDetails { dealer_address } => { QueryMsg::GetDealerDetails { dealer_address } => {
to_binary(&query_dealer_details(deps, dealer_address)?)? to_binary(&query_dealer_details(deps, dealer_address)?)?
} }
@@ -238,6 +243,7 @@ mod tests {
&RegisterDealer { &RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(), bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
}, },
&vec![], &vec![],
) )
@@ -251,6 +257,7 @@ mod tests {
&RegisterDealer { &RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(), bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
}, },
&vec![], &vec![],
) )
@@ -266,6 +273,7 @@ mod tests {
&RegisterDealer { &RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(), bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
}, },
&vec![], &vec![],
) )
@@ -2,26 +2,33 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage; use crate::dealers::storage as dealers_storage;
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
use crate::epoch_state::utils::check_epoch_state; use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError; use crate::error::ContractError;
use crate::state::{State, STATE}; use crate::state::STATE;
use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState}; use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
// currently we only require that // currently we only require that
// a) it's part of the signer group // a) it's part of the signer group
// b) it isn't already a dealer // b) it isn't already a dealer
fn verify_dealer(deps: DepsMut<'_>, state: &State, dealer: &Addr) -> Result<(), ContractError> { fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<(), ContractError> {
if dealers_storage::current_dealers() if dealers_storage::current_dealers()
.may_load(deps.storage, dealer)? .may_load(deps.storage, dealer)?
.is_some() .is_some()
{ {
return Err(ContractError::AlreadyADealer); return Err(ContractError::AlreadyADealer);
} }
let state = STATE.load(deps.storage)?;
let height = if resharing {
INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height
} else {
None
};
state state
.group_addr .group_addr
.is_voting_member(&deps.querier, dealer, None)? .is_voting_member(&deps.querier, dealer, height)?
.ok_or(ContractError::Unauthorized {})?; .ok_or(ContractError::Unauthorized {})?;
Ok(()) Ok(())
@@ -32,11 +39,11 @@ pub fn try_add_dealer(
info: MessageInfo, info: MessageInfo,
bte_key_with_proof: EncodedBTEPublicKeyWithProof, bte_key_with_proof: EncodedBTEPublicKeyWithProof,
announce_address: String, announce_address: String,
resharing: bool,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?; check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?;
let state = STATE.load(deps.storage)?;
verify_dealer(deps.branch(), &state, &info.sender)?; verify_dealer(deps.branch(), &info.sender, resharing)?;
// if it was already a dealer in the past, assign the same node index // if it was already a dealer in the past, assign the same node index
let node_index = if let Some(prior_details) = let node_index = if let Some(prior_details) =
@@ -69,10 +76,63 @@ pub fn try_add_dealer(
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
use crate::dealers::storage::current_dealers;
use crate::epoch_state::transactions::advance_epoch_state; use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::fixtures::dealer_details_fixture;
use crate::support::tests::helpers; use crate::support::tests::helpers;
use coconut_dkg_common::types::TimeConfiguration; use crate::support::tests::helpers::GROUP_MEMBERS;
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::testing::{mock_env, mock_info};
use cw4::Member;
use rusty_fork::rusty_fork_test;
rusty_fork_test! {
#[test]
fn verification() {
let mut deps = helpers::init_contract();
let new_dealer = Addr::unchecked("new_dealer");
let details1 = dealer_details_fixture(1);
let details2 = dealer_details_fixture(2);
let details3 = dealer_details_fixture(3);
current_dealers()
.save(deps.as_mut().storage, &details1.address, &details1)
.unwrap();
let err = verify_dealer(deps.as_mut(), &details1.address, false).unwrap_err();
assert_eq!(err, ContractError::AlreadyADealer);
INITIAL_REPLACEMENT_DATA
.save(
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![details1.address, details2.address, details3.address],
initial_height: Some(1),
},
)
.unwrap();
let err = verify_dealer(deps.as_mut(), &new_dealer, false).unwrap_err();
assert_eq!(err, ContractError::Unauthorized);
GROUP_MEMBERS.lock().unwrap().push((
Member {
addr: new_dealer.to_string(),
weight: 10,
},
2,
));
verify_dealer(deps.as_mut(), &new_dealer, false).unwrap();
let err = verify_dealer(deps.as_mut(), &new_dealer, true).unwrap_err();
assert_eq!(err, ContractError::Unauthorized);
INITIAL_REPLACEMENT_DATA
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
data.initial_height = Some(2);
Ok(data)
})
.unwrap();
verify_dealer(deps.as_mut(), &new_dealer, true).unwrap();
}
}
#[test] #[test]
fn invalid_state() { fn invalid_state() {
@@ -94,12 +154,13 @@ pub(crate) mod tests {
info.clone(), info.clone(),
bte_key_with_proof.clone(), bte_key_with_proof.clone(),
announce_address.clone(), announce_address.clone(),
false,
) )
.unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::IncorrectEpochState { ContractError::IncorrectEpochState {
current_state: EpochState::DealingExchange.to_string(), current_state: EpochState::DealingExchange { resharing: false }.to_string(),
expected_state: EpochState::default().to_string(), expected_state: EpochState::default().to_string(),
} }
); );
@@ -3,6 +3,7 @@
use crate::dealers::storage as dealers_storage; use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::DEALINGS_BYTES; use crate::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
use crate::epoch_state::utils::check_epoch_state; use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError; use crate::error::ContractError;
use coconut_dkg_common::types::{ContractSafeBytes, EpochState}; use coconut_dkg_common::types::{ContractSafeBytes, EpochState};
@@ -12,8 +13,9 @@ pub fn try_commit_dealings(
deps: DepsMut<'_>, deps: DepsMut<'_>,
info: MessageInfo, info: MessageInfo,
dealing_bytes: ContractSafeBytes, dealing_bytes: ContractSafeBytes,
resharing: bool,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::DealingExchange)?; check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?;
// ensure the sender is a dealer // ensure the sender is a dealer
if dealers_storage::current_dealers() if dealers_storage::current_dealers()
.may_load(deps.storage, &info.sender)? .may_load(deps.storage, &info.sender)?
@@ -21,6 +23,14 @@ pub fn try_commit_dealings(
{ {
return Err(ContractError::NotADealer); return Err(ContractError::NotADealer);
} }
if resharing
&& !INITIAL_REPLACEMENT_DATA
.load(deps.storage)?
.initial_dealers
.contains(&info.sender)
{
return Err(ContractError::NotAnInitialDealer);
}
// check if this dealer has already committed to all dealings // check if this dealer has already committed to all dealings
// (we don't want to allow overwriting anything) // (we don't want to allow overwriting anything)
@@ -39,29 +49,30 @@ pub fn try_commit_dealings(
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::advance_epoch_state; use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::fixtures::dealing_bytes_fixture; use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture};
use crate::support::tests::helpers; use crate::support::tests::helpers;
use coconut_dkg_common::dealer::DealerDetails; use coconut_dkg_common::dealer::DealerDetails;
use coconut_dkg_common::types::TimeConfiguration; use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr; use cosmwasm_std::Addr;
#[test] #[test]
fn invalid_commit_dealing() { fn invalid_commit_dealing() {
let mut deps = helpers::init_contract(); let mut deps = helpers::init_contract();
let owner = Addr::unchecked("owner"); let owner = Addr::unchecked("owner1");
let mut env = mock_env(); let mut env = mock_env();
let info = mock_info(owner.as_str(), &[]); let info = mock_info(owner.as_str(), &[]);
let dealing_bytes = dealing_bytes_fixture(); let dealing_bytes = dealing_bytes_fixture();
let ret = let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::IncorrectEpochState { ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(), current_state: EpochState::default().to_string(),
expected_state: EpochState::DealingExchange.to_string() expected_state: EpochState::DealingExchange { resharing: false }.to_string()
} }
); );
@@ -71,8 +82,8 @@ pub(crate) mod tests {
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap(); advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); .unwrap_err();
assert_eq!(ret, ContractError::NotADealer); assert_eq!(ret, ContractError::NotADealer);
let dealer_details = DealerDetails { let dealer_details = DealerDetails {
@@ -85,14 +96,41 @@ pub(crate) mod tests {
.save(deps.as_mut().storage, &owner, &dealer_details) .save(deps.as_mut().storage, &owner, &dealer_details)
.unwrap(); .unwrap();
// assume we're in resharing mode
CURRENT_EPOCH
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
epoch.state = EpochState::DealingExchange { resharing: true };
Ok(epoch)
})
.unwrap();
INITIAL_REPLACEMENT_DATA
.save(
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![],
initial_height: None,
},
)
.unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
.unwrap_err();
assert_eq!(ret, ContractError::NotAnInitialDealer);
INITIAL_REPLACEMENT_DATA
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
data.initial_dealers = vec![dealer_details_fixture(1).address];
Ok(data)
})
.unwrap();
for dealings in DEALINGS_BYTES { for dealings in DEALINGS_BYTES {
assert!(!dealings.has(deps.as_mut().storage, &owner)); assert!(!dealings.has(deps.as_mut().storage, &owner));
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()); let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true);
assert!(ret.is_ok()); assert!(ret.is_ok());
assert!(dealings.has(deps.as_mut().storage, &owner)); assert!(dealings.has(deps.as_mut().storage, &owner));
} }
let ret = let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::AlreadyCommitted { ContractError::AlreadyCommitted {
@@ -1,9 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
use crate::error::ContractError; use crate::error::ContractError;
use coconut_dkg_common::types::Epoch; use coconut_dkg_common::types::{Epoch, InitialReplacementData};
use cosmwasm_std::Storage; use cosmwasm_std::Storage;
pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> { pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
@@ -18,6 +18,12 @@ pub(crate) fn query_current_epoch_threshold(
Ok(THRESHOLD.may_load(storage)?) Ok(THRESHOLD.may_load(storage)?)
} }
pub(crate) fn query_initial_dealers(
storage: &dyn Storage,
) -> Result<Option<InitialReplacementData>, ContractError> {
Ok(INITIAL_REPLACEMENT_DATA.may_load(storage)?)
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod test { pub(crate) mod test {
use super::*; use super::*;
@@ -29,7 +35,10 @@ pub(crate) mod test {
fn query_state() { fn query_state() {
let mut deps = init_contract(); let mut deps = init_contract();
let epoch = query_current_epoch(deps.as_mut().storage).unwrap(); let epoch = query_current_epoch(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::PublicKeySubmission); assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
assert_eq!( assert_eq!(
epoch.finish_timestamp, epoch.finish_timestamp,
mock_env() mock_env()
@@ -1,8 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::Epoch; use coconut_dkg_common::types::{Epoch, InitialReplacementData};
use cw_storage_plus::Item; use cw_storage_plus::Item;
pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch"); pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch");
pub const THRESHOLD: Item<u64> = Item::new("threshold"); pub const THRESHOLD: Item<u64> = Item::new("threshold");
pub const INITIAL_REPLACEMENT_DATA: Item<InitialReplacementData> =
Item::new("initial_replacement_data");
@@ -3,12 +3,12 @@
use crate::dealers::storage::{current_dealers, past_dealers}; use crate::dealers::storage::{current_dealers, past_dealers};
use crate::dealings::storage::DEALINGS_BYTES; use crate::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
use crate::epoch_state::utils::check_epoch_state; use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError; use crate::error::ContractError;
use crate::state::STATE; use crate::state::STATE;
use coconut_dkg_common::types::{Epoch, EpochState}; use coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
use cosmwasm_std::{DepsMut, Env, Order, Response, Storage}; use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> { fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
THRESHOLD.remove(storage); THRESHOLD.remove(storage);
@@ -27,13 +27,13 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
Ok(()) Ok(())
} }
fn dealers_still_active(deps: &DepsMut<'_>) -> Result<usize, ContractError> { fn dealers_still_active(
deps: &Deps<'_>,
dealers: impl Iterator<Item = Addr>,
) -> Result<usize, ContractError> {
let state = STATE.load(deps.storage)?; let state = STATE.load(deps.storage)?;
let mut still_active = 0; let mut still_active = 0;
for dealer_addr in current_dealers() for dealer_addr in dealers {
.keys(deps.storage, None, None, Order::Ascending)
.flatten()
{
if state if state
.group_addr .group_addr
.is_voting_member(&deps.querier, &dealer_addr, None)? .is_voting_member(&deps.querier, &dealer_addr, None)?
@@ -45,6 +45,36 @@ fn dealers_still_active(deps: &DepsMut<'_>) -> Result<usize, ContractError> {
Ok(still_active) Ok(still_active)
} }
fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let dealers_still_active = dealers_still_active(
&deps.as_ref(),
current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten(),
)?;
let all_dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.count();
let group_members = STATE
.load(deps.storage)?
.group_addr
.list_members(&deps.querier, None, None)?
.len();
Ok(dealers_still_active == all_dealers && all_dealers == group_members)
}
fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let threshold = THRESHOLD.load(deps.storage)? as usize;
let initial_dealers = INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_dealers;
let initial_dealer_count = initial_dealers.len();
let replacement_threshold = threshold - (initial_dealers.len() + 2 - 1) / 2 + 1;
let removed_dealer_count =
initial_dealer_count - dealers_still_active(&deps.as_ref(), initial_dealers.into_iter())?;
Ok(removed_dealer_count >= replacement_threshold)
}
pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> { pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
let epoch = CURRENT_EPOCH.load(deps.storage)?; let epoch = CURRENT_EPOCH.load(deps.storage)?;
if epoch.finish_timestamp > env.block.time { if epoch.finish_timestamp > env.block.time {
@@ -59,13 +89,20 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
let current_epoch = CURRENT_EPOCH.load(deps.storage)?; let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
let next_epoch = if let Some(state) = current_epoch.state.next() { let next_epoch = if let Some(state) = current_epoch.state.next() {
// We are during DKG process // We are during DKG process
if state == EpochState::DealingExchange { if let EpochState::DealingExchange { resharing } = state {
let current_dealer_count = current_dealers() let current_dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending) .keys(deps.storage, None, None, Order::Ascending)
.count(); .collect::<Result<Vec<Addr>, _>>()?;
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y; // note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
let threshold = (2 * current_dealer_count as u64 + 3 - 1) / 3; let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
THRESHOLD.save(deps.storage, &threshold)?; THRESHOLD.save(deps.storage, &threshold)?;
if !resharing {
let replacement_data = InitialReplacementData {
initial_dealers: current_dealers,
initial_height: None,
};
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
}
} }
Epoch::new( Epoch::new(
state, state,
@@ -73,14 +110,9 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
current_epoch.time_configuration, current_epoch.time_configuration,
env.block.time, env.block.time,
) )
} else if dealers_still_active(&deps)? } else if dealers_eq_members(&deps)? {
== STATE
.load(deps.storage)?
.group_addr
.list_members(&deps.querier, None, None)?
.len()
{
// The dealer set hasn't changed, so we only extend the finish timestamp // The dealer set hasn't changed, so we only extend the finish timestamp
// The epoch remains the same, as we use it as key for storing VKs
Epoch::new( Epoch::new(
current_epoch.state, current_epoch.state,
current_epoch.epoch_id, current_epoch.epoch_id,
@@ -88,10 +120,21 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
env.block.time, env.block.time,
) )
} else { } else {
// Dealer set changed, we need to redo DKG from scratch // Dealer set changed, we need to redo DKG...
let state = if replacement_threshold_surpassed(&deps)? {
// ... in reset mode
EpochState::default()
} else {
// ... in reshare mode
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
data.initial_height = Some(env.block.height);
Ok(data)
})?;
EpochState::PublicKeySubmission { resharing: true }
};
reset_epoch_state(deps.storage)?; reset_epoch_state(deps.storage)?;
Epoch::new( Epoch::new(
EpochState::default(), state,
current_epoch.epoch_id + 1, current_epoch.epoch_id + 1,
current_epoch.time_configuration, current_epoch.time_configuration,
env.block.time, env.block.time,
@@ -109,7 +152,10 @@ pub(crate) fn try_surpassed_threshold(
check_epoch_state(deps.storage, EpochState::InProgress)?; check_epoch_state(deps.storage, EpochState::InProgress)?;
let threshold = THRESHOLD.load(deps.storage)?; let threshold = THRESHOLD.load(deps.storage)?;
if dealers_still_active(&deps)? < threshold as usize { let dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten();
if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize {
reset_epoch_state(deps.storage)?; reset_epoch_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| { CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new( Ok(Epoch::new(
@@ -139,27 +185,137 @@ pub(crate) mod tests {
use rusty_fork::rusty_fork_test; use rusty_fork::rusty_fork_test;
// Because of the global variable handling group, we need individual process for each test // Because of the global variable handling group, we need individual process for each test
rusty_fork_test! { rusty_fork_test! {
// Using values from the DKG document
#[test]
fn threshold_surpassed() {
let mut deps = init_contract();
let two_thirds = |n: u64| (2 * n + 3 - 1) / 3;
let three_fourths = |n: u64| (3 * n + 4 - 1) / 4;
let ninty_pc = |n: u64| (9 * n + 10 - 2) / 10;
let mut limits = [3, 4, 5, 5, 7, 11, 10, 14, 21, 18, 26, 41].iter();
for n in [10, 25, 50, 100] {
let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect();
let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect();
let data = InitialReplacementData {
initial_dealers,
initial_height: None,
};
for f in [two_thirds, three_fourths, ninty_pc] {
let threshold = f(n);
THRESHOLD.save(deps.as_mut().storage, &threshold).unwrap();
INITIAL_REPLACEMENT_DATA
.save(deps.as_mut().storage, &data)
.unwrap();
let limit = *limits.next().unwrap();
{
let mut group_members = GROUP_MEMBERS.lock().unwrap();
for i in 0..n as usize {
group_members.push((
Member {
addr: dealers[i].address.to_string(),
weight: 10,
},
1,
));
}
for _ in 1..limit {
group_members.pop();
}
}
assert!(!replacement_threshold_surpassed(&deps.as_mut()).unwrap());
GROUP_MEMBERS.lock().unwrap().pop();
assert!(replacement_threshold_surpassed(&deps.as_mut()).unwrap());
*GROUP_MEMBERS.lock().unwrap() = vec![];
}
}
}
#[test]
fn dealers_and_members() {
let mut deps = init_contract();
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
let details = dealer_details_fixture(1);
let different_details = dealer_details_fixture(2);
current_dealers()
.save(deps.as_mut().storage, &details.address, &details)
.unwrap();
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
.remove(deps.as_mut().storage, &details.address)
.unwrap();
GROUP_MEMBERS.lock().unwrap().push((
Member {
addr: "owner1".to_string(),
weight: 10,
},
1,
));
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
.save(
deps.as_mut().storage,
&different_details.address,
&different_details,
)
.unwrap();
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
.remove(deps.as_mut().storage, &different_details.address)
.unwrap();
current_dealers()
.save(deps.as_mut().storage, &details.address, &details)
.unwrap();
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
}
#[test] #[test]
fn still_active() { fn still_active() {
let mut deps = init_contract(); let mut deps = init_contract();
{ {
let mut group = GROUP_MEMBERS.lock().unwrap(); let mut group = GROUP_MEMBERS.lock().unwrap();
group.push(Member { group.push((
addr: "owner1".to_string(), Member {
weight: 10, addr: "owner1".to_string(),
}); weight: 10,
group.push(Member { },
addr: "owner2".to_string(), 1,
weight: 10, ));
}); group.push((
group.push(Member { Member {
addr: "owner3".to_string(), addr: "owner2".to_string(),
weight: 10, weight: 10,
}); },
1,
));
group.push((
Member {
addr: "owner3".to_string(),
weight: 10,
},
1,
));
} }
assert_eq!(0, dealers_still_active(&deps.as_mut()).unwrap()); assert_eq!(
0,
dealers_still_active(
&deps.as_ref(),
current_dealers()
.keys(&deps.storage, None, None, Order::Ascending)
.flatten()
)
.unwrap()
);
for i in 0..3 as u64 { for i in 0..3 as u64 {
let details = dealer_details_fixture(i + 1); let details = dealer_details_fixture(i + 1);
current_dealers() current_dealers()
@@ -167,7 +323,13 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
i as usize + 1, i as usize + 1,
dealers_still_active(&deps.as_mut()).unwrap() dealers_still_active(
&deps.as_ref(),
current_dealers()
.keys(&deps.storage, None, None, Order::Ascending)
.flatten()
)
.unwrap()
); );
} }
} }
@@ -179,22 +341,41 @@ pub(crate) mod tests {
{ {
let mut group = GROUP_MEMBERS.lock().unwrap(); let mut group = GROUP_MEMBERS.lock().unwrap();
group.push(Member { group.push((
addr: "owner1".to_string(), Member {
weight: 10, addr: "owner1".to_string(),
}); weight: 10,
group.push(Member { },
addr: "owner2".to_string(), 1,
weight: 10, ));
}); group.push((
group.push(Member { Member {
addr: "owner3".to_string(), addr: "owner2".to_string(),
weight: 10, weight: 10,
}); },
1,
));
group.push((
Member {
addr: "owner3".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner4".to_string(),
weight: 10,
},
1,
));
} }
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::PublicKeySubmission); assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
assert_eq!( assert_eq!(
epoch.finish_timestamp, epoch.finish_timestamp,
env.block env.block
@@ -211,16 +392,37 @@ pub(crate) mod tests {
EarlyEpochStateAdvancement(1) EarlyEpochStateAdvancement(1)
); );
// setup dealer details
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() {
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
assert!(INITIAL_REPLACEMENT_DATA
.may_load(&deps.storage)
.unwrap()
.is_none());
env.block.time = env.block.time.plus_seconds(1); env.block.time = env.block.time.plus_seconds(1);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::DealingExchange); assert_eq!(
epoch.state,
EpochState::DealingExchange { resharing: false }
);
assert_eq!( assert_eq!(
epoch.finish_timestamp, epoch.finish_timestamp,
env.block env.block
.time .time
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs) .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs)
); );
let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap();
let expected_replacement_data = InitialReplacementData {
initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(),
initial_height: None,
};
assert_eq!(replacement_data, expected_replacement_data);
env.block.time = env env.block.time = env
.block .block
@@ -234,7 +436,10 @@ pub(crate) mod tests {
env.block.time = env.block.time.plus_seconds(3); env.block.time = env.block.time.plus_seconds(3);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::VerificationKeySubmission); assert_eq!(
epoch.state,
EpochState::VerificationKeySubmission { resharing: false }
);
assert_eq!( assert_eq!(
epoch.finish_timestamp, epoch.finish_timestamp,
env.block.time.plus_seconds( env.block.time.plus_seconds(
@@ -258,7 +463,10 @@ pub(crate) mod tests {
env.block.time = env.block.time.plus_seconds(3); env.block.time = env.block.time.plus_seconds(3);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::VerificationKeyValidation); assert_eq!(
epoch.state,
EpochState::VerificationKeyValidation { resharing: false }
);
assert_eq!( assert_eq!(
epoch.finish_timestamp, epoch.finish_timestamp,
env.block.time.plus_seconds( env.block.time.plus_seconds(
@@ -282,7 +490,10 @@ pub(crate) mod tests {
env.block.time = env.block.time.plus_seconds(3); env.block.time = env.block.time.plus_seconds(3);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::VerificationKeyFinalization); assert_eq!(
epoch.state,
EpochState::VerificationKeyFinalization { resharing: false }
);
assert_eq!( assert_eq!(
epoch.finish_timestamp, epoch.finish_timestamp,
env.block.time.plus_seconds( env.block.time.plus_seconds(
@@ -327,14 +538,6 @@ pub(crate) mod tests {
EarlyEpochStateAdvancement(50) EarlyEpochStateAdvancement(50)
); );
// setup dealer details
let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() {
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
// Group hasn't changed, so we remain in the same epoch, with updated finish timestamp // Group hasn't changed, so we remain in the same epoch, with updated finish timestamp
env.block.time = env.block.time.plus_seconds(100); env.block.time = env.block.time.plus_seconds(100);
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
@@ -348,11 +551,14 @@ pub(crate) mod tests {
); );
assert_eq!(curr_epoch, expected_epoch); assert_eq!(curr_epoch, expected_epoch);
// Group changed, slightly, so reset dkg state // Group changed slightly, so re-run dkg in reshare mode
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = Member { *GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = (
addr: "owner4".to_string(), Member {
weight: 10, addr: "owner5".to_string(),
}; weight: 10,
},
1,
);
env.block.time = env env.block.time = env
.block .block
.time .time
@@ -361,7 +567,49 @@ pub(crate) mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
let expected_epoch = Epoch::new( let expected_epoch = Epoch::new(
EpochState::default(), EpochState::PublicKeySubmission { resharing: true },
prev_epoch.epoch_id + 1,
prev_epoch.time_configuration,
env.block.time,
);
assert_eq!(curr_epoch, expected_epoch);
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
let all_details: [_; 2] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
for details in all_details.iter() {
past_dealers().remove(deps.as_mut().storage, &details.address).unwrap();
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
for times in [
epoch.time_configuration.public_key_submission_time_secs,
epoch.time_configuration.dealing_exchange_time_secs,
epoch.time_configuration.verification_key_submission_time_secs,
epoch.time_configuration.verification_key_validation_time_secs,
epoch.time_configuration.verification_key_finalization_time_secs,
] {
env.block.time = env.block.time.plus_seconds(times);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
}
// Group changed even more, surpassing threshold, so re-run dkg in reset mode
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = (
Member {
addr: "owner6".to_string(),
weight: 10,
},
1,
);
env.block.time = env
.block
.time
.plus_seconds(epoch.time_configuration.in_progress_time_secs);
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
let expected_epoch = Epoch::new(
EpochState::PublicKeySubmission { resharing: false },
prev_epoch.epoch_id + 1, prev_epoch.epoch_id + 1,
prev_epoch.time_configuration, prev_epoch.time_configuration,
env.block.time, env.block.time,
@@ -370,89 +618,103 @@ pub(crate) mod tests {
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
} }
#[test]
fn surpass_threshold() {
let mut deps = init_contract();
let mut env = mock_env();
let time_configuration = TimeConfiguration::default();
{
let mut group = GROUP_MEMBERS.lock().unwrap();
#[test] group.push((
fn surpass_threshold() { Member {
let mut deps = init_contract(); addr: "owner1".to_string(),
let mut env = mock_env(); weight: 10,
let time_configuration = TimeConfiguration::default(); },
{ 1,
let mut group = GROUP_MEMBERS.lock().unwrap(); ));
group.push((
group.push(Member { Member {
addr: "owner1".to_string(), addr: "owner2".to_string(),
weight: 10, weight: 10,
}); },
group.push(Member { 1,
addr: "owner2".to_string(), ));
weight: 10, group.push((
}); Member {
group.push(Member { addr: "owner3".to_string(),
addr: "owner3".to_string(), weight: 10,
weight: 10, },
}); 1,
} ));
let ret = try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
expected_state: EpochState::InProgress.to_string()
} }
);
let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1)); let ret = try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap_err();
for details in all_details.iter() { assert_eq!(
current_dealers() ret,
.save(deps.as_mut().storage, &details.address, details) ContractError::IncorrectEpochState {
.unwrap(); current_state: EpochState::default().to_string(),
expected_state: EpochState::InProgress.to_string()
}
);
let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() {
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
for times in [
time_configuration.public_key_submission_time_secs,
time_configuration.dealing_exchange_time_secs,
time_configuration.verification_key_submission_time_secs,
time_configuration.verification_key_validation_time_secs,
time_configuration.verification_key_finalization_time_secs,
] {
env.block.time = env.block.time.plus_seconds(times);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
}
let curr_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
// epoch hasn't advanced as we are still in the threshold range
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch);
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = (
Member {
addr: "owner4".to_string(),
weight: 10,
},
1,
);
// epoch hasn't advanced as we are still in the threshold range
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch);
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = (
Member {
addr: "owner5".to_string(),
weight: 10,
},
1,
);
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
let next_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert_eq!(
next_epoch,
Epoch::new(
EpochState::default(),
curr_epoch.epoch_id + 1,
curr_epoch.time_configuration,
env.block.time,
)
);
} }
for times in [
time_configuration.public_key_submission_time_secs,
time_configuration.dealing_exchange_time_secs,
time_configuration.verification_key_submission_time_secs,
time_configuration.verification_key_validation_time_secs,
time_configuration.verification_key_finalization_time_secs,
] {
env.block.time = env.block.time.plus_seconds(times);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
}
let curr_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
// epoch hasn't advanced as we are still in the threshold range
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch);
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = Member {
addr: "owner4".to_string(),
weight: 10,
};
// epoch hasn't advanced as we are still in the threshold range
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch);
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = Member {
addr: "owner5".to_string(),
weight: 10,
};
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
let next_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert_eq!(
next_epoch,
Epoch::new(
EpochState::default(),
curr_epoch.epoch_id + 1,
curr_epoch.time_configuration,
env.block.time,
)
);
}
} }
#[test] #[test]
+3
View File
@@ -40,6 +40,9 @@ pub enum ContractError {
#[error("This sender is not a dealer for the current epoch")] #[error("This sender is not a dealer for the current epoch")]
NotADealer, NotADealer,
#[error("This sender is not a dealer for the current resharing epoch")]
NotAnInitialDealer,
#[error("This dealer has already committed {commitment}")] #[error("This dealer has already committed {commitment}")]
AlreadyCommitted { commitment: String }, AlreadyCommitted { commitment: String },
@@ -19,7 +19,7 @@ pub const GROUP_CONTRACT: &str = "group contract address";
pub const MULTISIG_CONTRACT: &str = "multisig contract address"; pub const MULTISIG_CONTRACT: &str = "multisig contract address";
lazy_static! { lazy_static! {
pub static ref GROUP_MEMBERS: Mutex<Vec<Member>> = Mutex::new(vec![]); pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
} }
fn querier_handler(query: &WasmQuery) -> QuerierResult { fn querier_handler(query: &WasmQuery) -> QuerierResult {
@@ -29,9 +29,14 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
panic!("Not supported"); panic!("Not supported");
} }
match from_binary(msg) { match from_binary(msg) {
Ok(Cw4QueryMsg::Member { addr, .. }) => { Ok(Cw4QueryMsg::Member { addr, at_height }) => {
let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|m| { let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| {
if m.addr == addr { if m.addr == addr {
if let Some(height) = at_height {
if height != *h {
return None;
}
}
Some(m.weight) Some(m.weight)
} else { } else {
None None
@@ -40,7 +45,12 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
to_binary(&MemberResponse { weight }).unwrap() to_binary(&MemberResponse { weight }).unwrap()
} }
Ok(Cw4QueryMsg::ListMembers { .. }) => { Ok(Cw4QueryMsg::ListMembers { .. }) => {
let members = GROUP_MEMBERS.lock().unwrap().to_vec(); let members = GROUP_MEMBERS
.lock()
.unwrap()
.iter()
.map(|m| m.0.clone())
.collect();
to_binary(&MemberListResponse { members }).unwrap() to_binary(&MemberListResponse { members }).unwrap()
} }
_ => panic!("Not supported"), _ => panic!("Not supported"),
@@ -17,8 +17,12 @@ pub fn try_commit_verification_key_share(
env: Env, env: Env,
info: MessageInfo, info: MessageInfo,
share: VerificationKeyShare, share: VerificationKeyShare,
resharing: bool,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::VerificationKeySubmission)?; check_epoch_state(
deps.storage,
EpochState::VerificationKeySubmission { resharing },
)?;
// ensure the sender is a dealer // ensure the sender is a dealer
let details = dealers_storage::current_dealers() let details = dealers_storage::current_dealers()
.load(deps.storage, &info.sender) .load(deps.storage, &info.sender)
@@ -45,6 +49,7 @@ pub fn try_commit_verification_key_share(
let msg = to_cosmos_msg( let msg = to_cosmos_msg(
info.sender, info.sender,
resharing,
env.contract.address.to_string(), env.contract.address.to_string(),
STATE.load(deps.storage)?.multisig_addr.to_string(), STATE.load(deps.storage)?.multisig_addr.to_string(),
env.block env.block
@@ -59,8 +64,12 @@ pub fn try_verify_verification_key_share(
deps: DepsMut<'_>, deps: DepsMut<'_>,
info: MessageInfo, info: MessageInfo,
owner: Addr, owner: Addr,
resharing: bool,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::VerificationKeyFinalization)?; check_epoch_state(
deps.storage,
EpochState::VerificationKeyFinalization { resharing },
)?;
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id; let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
MULTISIG.assert_admin(deps.as_ref(), &info.sender)?; MULTISIG.assert_admin(deps.as_ref(), &info.sender)?;
vk_shares().update(deps.storage, (&owner, epoch_id), |vk_share| { vk_shares().update(deps.storage, (&owner, epoch_id), |vk_share| {
@@ -117,8 +126,14 @@ mod tests {
.save(deps.as_mut().storage, &dealer, &dealer_details) .save(deps.as_mut().storage, &dealer, &dealer_details)
.unwrap(); .unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone()) try_commit_verification_key_share(
.unwrap(); deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap(); let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap();
assert_eq!( assert_eq!(
vk_share, vk_share,
@@ -145,13 +160,15 @@ mod tests {
env.clone(), env.clone(),
info.clone(), info.clone(),
share.clone(), share.clone(),
false,
) )
.unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::IncorrectEpochState { ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(), current_state: EpochState::default().to_string(),
expected_state: EpochState::VerificationKeySubmission.to_string() expected_state: EpochState::VerificationKeySubmission { resharing: false }
.to_string()
} }
); );
env.block.time = env env.block.time = env
@@ -169,6 +186,7 @@ mod tests {
env.clone(), env.clone(),
info.clone(), info.clone(),
share.clone(), share.clone(),
false,
) )
.unwrap_err(); .unwrap_err();
assert_eq!(ret, ContractError::NotADealer); assert_eq!(ret, ContractError::NotADealer);
@@ -184,14 +202,21 @@ mod tests {
.save(deps.as_mut().storage, &dealer, &dealer_details) .save(deps.as_mut().storage, &dealer, &dealer_details)
.unwrap(); .unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone()) try_commit_verification_key_share(
.unwrap(); deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
let ret = try_commit_verification_key_share( let ret = try_commit_verification_key_share(
deps.as_mut(), deps.as_mut(),
env.clone(), env.clone(),
info.clone(), info.clone(),
share.clone(), share.clone(),
false,
) )
.unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
@@ -210,13 +235,15 @@ mod tests {
let owner = Addr::unchecked("owner"); let owner = Addr::unchecked("owner");
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
let ret = try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone()) let ret =
.unwrap_err(); try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false)
.unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::IncorrectEpochState { ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(), current_state: EpochState::default().to_string(),
expected_state: EpochState::VerificationKeyFinalization.to_string() expected_state: EpochState::VerificationKeyFinalization { resharing: false }
.to_string()
} }
); );
@@ -241,12 +268,13 @@ mod tests {
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap(); advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false)
try_verify_verification_key_share(deps.as_mut(), info, owner.clone()).unwrap_err(); .unwrap_err();
assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {})); assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {}));
let ret = try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()) let ret =
.unwrap_err(); try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
.unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::NoCommitForOwner { ContractError::NoCommitForOwner {
@@ -284,8 +312,14 @@ mod tests {
dealers_storage::current_dealers() dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details) .save(deps.as_mut().storage, &owner, &dealer_details)
.unwrap(); .unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone()) try_commit_verification_key_share(
.unwrap(); deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
env.block.time = env env.block.time = env
.block .block
@@ -298,6 +332,7 @@ mod tests {
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap(); advance_epoch_state(deps.as_mut(), env).unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()).unwrap(); try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
.unwrap();
} }
} }
+2 -1
View File
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response}; use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
use cw3_flex_multisig::{state::CONFIG, ContractError}; use cw3_flex_multisig::state::CONFIG;
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper}; use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
use multisig_contract_common::error::ContractError;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -103,6 +103,7 @@ fn dkg_proposal() {
&RegisterDealer { &RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(), bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
}, },
&vec![], &vec![],
) )
@@ -124,6 +125,7 @@ fn dkg_proposal() {
let msg = CommitVerificationKeyShare { let msg = CommitVerificationKeyShare {
share: "share".to_string(), share: "share".to_string(),
resharing: false,
}; };
let res = app let res = app
.execute_contract( .execute_contract(
@@ -26,7 +26,6 @@ cw-storage-plus = { version = "0.13.4" }
cosmwasm-std = { version = "1.0.0" } cosmwasm-std = { version = "1.0.0" }
schemars = "0.8.1" schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] } serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" }
@@ -17,8 +17,8 @@ use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff};
use cw_storage_plus::Bound; use cw_storage_plus::Bound;
use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; use cw_utils::{maybe_addr, Expiration, ThresholdResponse};
use crate::error::ContractError;
use crate::state::{Config, CONFIG}; use crate::state::{Config, CONFIG};
use multisig_contract_common::error::ContractError;
use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
// version info for migration info // version info for migration info
@@ -1,5 +1,2 @@
pub mod contract; pub mod contract;
pub mod error;
pub mod state; pub mod state;
pub use crate::error::ContractError;
+4 -4
View File
@@ -12,10 +12,10 @@ DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n12ckdkm3q7eytefs7rwu4ue3t9hxgvl9v08jddmtwgct2ve0pv50q0t8dlt COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19d2nwj7fdhxqmyvgy8lf3ad49a6vmww4shryhrkj2mqk36att66s6xzszw
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm28l5
MULTISIG_CONTRACT_ADDRESS=n14krxe8ukzagwhvec0rmteexu62w8k9kp9sra9ww6em2hnmzcukqsa0utc8 MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
COCONUT_DKG_CONTRACT_ADDRESS=n1rl5n6cxuz2hdy3f7d9hsnw8zn0zwwwr0r4dxfz7tktgpgkcnz9zshmvksc COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://qwerty-validator.qa.nymte.ch/" NYXD="https://qwerty-validator.qa.nymte.ch/"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "explorer-api" name = "explorer-api"
version = "1.1.7" version = "1.1.8"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+10
View File
@@ -1,5 +1,15 @@
## UNRELEASED ## UNRELEASED
## [nym-explorer-v1.0.4](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.4) (2023-01-31)
- Add routing score on gateway list ([#2913])
- Add gateway's last Routing Score to the gateways list page ([#2186])
- Upgrade Sandbox and make below changes: ([#2332])
[#2913]: https://github.com/nymtech/nym/pull/2913
[#2186]: https://github.com/nymtech/nym/issues/2186
[#2332]: https://github.com/nymtech/nym/issues/2332
## [nym-explorer-v1.0.3](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.3) (2023-01-24) ## [nym-explorer-v1.0.3](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.3) (2023-01-24)
- Stake Saturation tooltip on node list and node pages updated ([#2877]) - Stake Saturation tooltip on node list and node pages updated ([#2877])
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nym/network-explorer", "name": "@nym/network-explorer",
"version": "1.0.3", "version": "1.0.4",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -120,4 +120,4 @@
"last 1 safari version" "last 1 safari version"
] ]
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ export const OVERVIEW_API = `${API_BASE_URL}/overview`;
export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODE_PING = `${API_BASE_URL}/ping`;
export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`;
export const MIXNODE_API = `${API_BASE_URL}/mix-node`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`;
export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/gateways`; export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/status/gateways/detailed`;
export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`; export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`;
export const BLOCK_API = `${NYM_API_BASE_URL}/block`; export const BLOCK_API = `${NYM_API_BASE_URL}/block`;
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`; export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
+9 -3
View File
@@ -15,7 +15,6 @@ import {
CountryDataResponse, CountryDataResponse,
DelegationsResponse, DelegationsResponse,
UniqDelegationsResponse, UniqDelegationsResponse,
GatewayResponse,
GatewayReportResponse, GatewayReportResponse,
UptimeStoryResponse, UptimeStoryResponse,
MixNodeDescriptionResponse, MixNodeDescriptionResponse,
@@ -27,7 +26,10 @@ import {
StatusResponse, StatusResponse,
SummaryOverviewResponse, SummaryOverviewResponse,
ValidatorsResponse, ValidatorsResponse,
GatewayBondAnnotated,
GatewayBond,
} from '../typeDefs/explorer-api'; } from '../typeDefs/explorer-api';
import { toPercentIntegerString } from '../utils';
function getFromCache(key: string) { function getFromCache(key: string) {
const ts = Number(localStorage.getItem('ts')); const ts = Number(localStorage.getItem('ts'));
@@ -89,9 +91,13 @@ export class Api {
return response.json(); return response.json();
}; };
static fetchGateways = async (): Promise<GatewayResponse> => { static fetchGateways = async (): Promise<GatewayBond[]> => {
const res = await fetch(GATEWAYS_API); const res = await fetch(GATEWAYS_API);
return res.json(); const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json();
return gatewaysAnnotated.map(({ gateway_bond, performance }) => ({
...gateway_bond,
performance: toPercentIntegerString(performance),
}));
}; };
static fetchGatewayUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> => static fetchGatewayUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> =>
+5 -5
View File
@@ -1,4 +1,4 @@
import { GatewayResponse, GatewayResponseItem, GatewayReportResponse } from '../typeDefs/explorer-api'; import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api';
export type GatewayRowType = { export type GatewayRowType = {
id: string; id: string;
@@ -8,6 +8,7 @@ export type GatewayRowType = {
host: string; host: string;
location: string; location: string;
version: string; version: string;
performance: string;
}; };
export type GatewayEnrichedRowType = GatewayRowType & { export type GatewayEnrichedRowType = GatewayRowType & {
@@ -28,13 +29,11 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
bond: gw.pledge_amount.amount || 0, bond: gw.pledge_amount.amount || 0,
host: gw.gateway.host || '', host: gw.gateway.host || '',
version: gw.gateway.version || '', version: gw.gateway.version || '',
performance: gw.performance,
})); }));
} }
export function gatewayEnrichedToGridRow( export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayReportResponse): GatewayEnrichedRowType {
gateway: GatewayResponseItem,
report: GatewayReportResponse,
): GatewayEnrichedRowType {
return { return {
id: gateway.owner, id: gateway.owner,
owner: gateway.owner, owner: gateway.owner,
@@ -47,5 +46,6 @@ export function gatewayEnrichedToGridRow(
mixPort: gateway.gateway.mix_port || 0, mixPort: gateway.gateway.mix_port || 0,
routingScore: `${report.most_recent}%`, routingScore: `${report.most_recent}%`,
avgUptime: `${report.last_day || report.last_hour}%`, avgUptime: `${report.last_day || report.last_hour}%`,
performance: gateway.performance,
}; };
} }
+3 -3
View File
@@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'; import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { GatewayResponseItem } from '../../typeDefs/explorer-api'; import { GatewayBond } from '../../typeDefs/explorer-api';
import { ColumnsType, DetailTable } from '../../components/DetailTable'; import { ColumnsType, DetailTable } from '../../components/DetailTable';
import { gatewayEnrichedToGridRow, GatewayEnrichedRowType } from '../../components/Gateways'; import { gatewayEnrichedToGridRow, GatewayEnrichedRowType } from '../../components/Gateways';
import { ComponentError } from '../../components/ComponentError'; import { ComponentError } from '../../components/ComponentError';
@@ -69,7 +69,7 @@ const columns: ColumnsType[] = [
/** /**
* Shows gateway details * Shows gateway details
*/ */
const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: GatewayResponseItem | undefined }) => { const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: GatewayBond | undefined }) => {
const [enrichGateway, setEnrichGateway] = React.useState<GatewayEnrichedRowType>(); const [enrichGateway, setEnrichGateway] = React.useState<GatewayEnrichedRowType>();
const [status, setStatus] = React.useState<number[] | undefined>(); const [status, setStatus] = React.useState<number[] | undefined>();
const { uptimeReport, uptimeStory } = useGatewayContext(); const { uptimeReport, uptimeStory } = useGatewayContext();
@@ -130,7 +130,7 @@ const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: Gat
* Guard component to handle loading and not found states * Guard component to handle loading and not found states
*/ */
const PageGatewayDetailGuard: FCWithChildren = () => { const PageGatewayDetailGuard: FCWithChildren = () => {
const [selectedGateway, setSelectedGateway] = React.useState<GatewayResponseItem | undefined>(); const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond | undefined>();
const { gateways } = useMainContext(); const { gateways } = useMainContext();
const { id } = useParams<{ id: string | undefined }>(); const { id } = useParams<{ id: string | undefined }>();
+18
View File
@@ -81,6 +81,24 @@ export const PageGateways: FCWithChildren = () => {
</MuiLink> </MuiLink>
), ),
}, },
{
field: 'performance',
headerName: 'Routing Score',
renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />,
width: 150,
headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => (
<MuiLink
sx={{ ...cellStyles }}
component={RRDLink}
to={`/network-components/gateway/${params.row.identityKey}`}
data-testid="pledge-amount"
>
{`${params.value}%`}
</MuiLink>
),
},
{ {
field: 'host', field: 'host',
renderHeader: () => <CustomColumnHeading headingTitle="IP:Port" />, renderHeader: () => <CustomColumnHeading headingTitle="IP:Port" />,
+8 -2
View File
@@ -116,15 +116,21 @@ export interface StatsResponse {
export type MixNodeHistoryResponse = StatsResponse; export type MixNodeHistoryResponse = StatsResponse;
export interface GatewayResponseItem { export interface GatewayBond {
block_height: number; block_height: number;
pledge_amount: Amount; pledge_amount: Amount;
total_delegation: Amount; total_delegation: Amount;
owner: string; owner: string;
gateway: Gateway; gateway: Gateway;
performance: string;
} }
export type GatewayResponse = GatewayResponseItem[]; export interface GatewayBondAnnotated {
gateway_bond: GatewayBond;
performance: string;
}
export type GatewayResponse = GatewayBond[];
export interface GatewayReportResponse { export interface GatewayReportResponse {
identity: string; identity: string;
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-gateway" name = "nym-gateway"
version = "1.1.7" version = "1.1.8"
authors = [ authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>", "Jędrzej Stuczyński <andrew@nymtech.net>",
+4 -3
View File
@@ -55,7 +55,8 @@ pub struct Init {
long, long,
alias = "validators", alias = "validators",
alias = "nymd_validators", alias = "nymd_validators",
value_delimiter = ',' value_delimiter = ',',
hide = true
)] )]
// the alias here is included for backwards compatibility (1.1.4 and before) // the alias here is included for backwards compatibility (1.1.4 and before)
nyxd_urls: Option<Vec<url::Url>>, nyxd_urls: Option<Vec<url::Url>>,
@@ -66,7 +67,7 @@ pub struct Init {
/// Set this gateway to work only with coconut credentials; that would disallow clients to /// Set this gateway to work only with coconut credentials; that would disallow clients to
/// bypass bandwidth credential requirement /// bypass bandwidth credential requirement
#[clap(long)] #[clap(long, hide = true)]
only_coconut_credentials: Option<bool>, only_coconut_credentials: Option<bool>,
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server /// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
@@ -102,7 +103,7 @@ impl From<Init> for OverrideConfig {
pub async fn execute(args: Init, output: OutputFormat) -> Result<(), Box<dyn Error + Send + Sync>> { pub async fn execute(args: Init, output: OutputFormat) -> Result<(), Box<dyn Error + Send + Sync>> {
println!("Initialising gateway {}...", args.id); println!("Initialising gateway {}...", args.id);
let already_init = if Config::default_config_file_path(Some(&args.id)).exists() { let already_init = if Config::default_config_file_path(&args.id).exists() {
eprintln!( eprintln!(
"Gateway \"{}\" was already initialised before! Config information will be \ "Gateway \"{}\" was already initialised before! Config information will be \
overwritten (but keys will be kept)!", overwritten (but keys will be kept)!",
+3 -2
View File
@@ -53,7 +53,8 @@ pub struct Run {
long, long,
alias = "validators", alias = "validators",
alias = "nymd_validators", alias = "nymd_validators",
value_delimiter = ',' value_delimiter = ',',
hide = true
)] )]
// the alias here is included for backwards compatibility (1.1.4 and before) // the alias here is included for backwards compatibility (1.1.4 and before)
nyxd_urls: Option<Vec<url::Url>>, nyxd_urls: Option<Vec<url::Url>>,
@@ -64,7 +65,7 @@ pub struct Run {
/// Set this gateway to work only with coconut credentials; that would disallow clients to /// Set this gateway to work only with coconut credentials; that would disallow clients to
/// bypass bandwidth credential requirement /// bypass bandwidth credential requirement
#[clap(long)] #[clap(long, hide = true)]
only_coconut_credentials: Option<bool>, only_coconut_credentials: Option<bool>,
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server /// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
+1 -1
View File
@@ -139,7 +139,7 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) {
pub async fn execute(args: &Upgrade) { pub async fn execute(args: &Upgrade) {
let package_version = parse_package_version(); let package_version = parse_package_version();
let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| { let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}"); eprintln!("failed to load existing config file! - {err}");
process::exit(1) process::exit(1)
}); });
+5 -5
View File
@@ -374,23 +374,23 @@ pub struct Gateway {
impl Gateway { impl Gateway {
fn default_private_sphinx_key_file(id: &str) -> PathBuf { fn default_private_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("private_sphinx.pem") Config::default_data_directory(id).join("private_sphinx.pem")
} }
fn default_public_sphinx_key_file(id: &str) -> PathBuf { fn default_public_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("public_sphinx.pem") Config::default_data_directory(id).join("public_sphinx.pem")
} }
fn default_private_identity_key_file(id: &str) -> PathBuf { fn default_private_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("private_identity.pem") Config::default_data_directory(id).join("private_identity.pem")
} }
fn default_public_identity_key_file(id: &str) -> PathBuf { fn default_public_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("public_identity.pem") Config::default_data_directory(id).join("public_identity.pem")
} }
fn default_database_path(id: &str) -> PathBuf { fn default_database_path(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("db.sqlite") Config::default_data_directory(id).join("db.sqlite")
} }
} }
+2 -2
View File
@@ -11,14 +11,14 @@ pub(crate) fn build_config<O: Into<OverrideConfig>>(
id: String, id: String,
override_args: O, override_args: O,
) -> Result<Config, GatewayError> { ) -> Result<Config, GatewayError> {
let config = match Config::load_from_file(Some(&id)) { let config = match Config::load_from_file(&id) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!( error!(
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
); );
return Err(GatewayError::ConfigLoadFailure { return Err(GatewayError::ConfigLoadFailure {
path: Config::default_config_file_path(Some(&id)), path: Config::default_config_file_path(&id),
id, id,
source: err, source: err,
}); });
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.1.8" version = "1.1.9"
authors = [ authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>", "Jędrzej Stuczyński <andrew@nymtech.net>",
+2 -2
View File
@@ -41,7 +41,7 @@ fn read_user_input() -> String {
pub(crate) fn execute(args: Describe) { pub(crate) fn execute(args: Describe) {
// ensure that the mixnode has in fact been initialized // ensure that the mixnode has in fact been initialized
match Config::load_from_file(Some(&args.id)) { match Config::load_from_file(&args.id) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id); error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id);
@@ -83,7 +83,7 @@ pub(crate) fn execute(args: Describe) {
// save the struct // save the struct
NodeDescription::save_to_file( NodeDescription::save_to_file(
&node_description, &node_description,
Config::default_config_directory(Some(&args.id)), Config::default_config_directory(&args.id),
) )
.unwrap() .unwrap()
} }
+1 -1
View File
@@ -68,7 +68,7 @@ pub(crate) fn execute(args: &Init, output: OutputFormat) {
let id = &override_config_fields.id; let id = &override_config_fields.id;
eprintln!("Initialising mixnode {id}..."); eprintln!("Initialising mixnode {id}...");
let already_init = if Config::default_config_file_path(Some(id)).exists() { let already_init = if Config::default_config_file_path(id).exists() {
eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!"); eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!");
true true
} else { } else {
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) struct NodeDetails {
} }
pub(crate) fn execute(args: &NodeDetails, output: OutputFormat) { pub(crate) fn execute(args: &NodeDetails, output: OutputFormat) {
let config = match Config::load_from_file(Some(&args.id)) { let config = match Config::load_from_file(&args.id) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!( error!(
+1 -1
View File
@@ -79,7 +79,7 @@ fn special_addresses() -> Vec<&'static str> {
pub(crate) async fn execute(args: &Run, output: OutputFormat) { pub(crate) async fn execute(args: &Run, output: OutputFormat) {
eprintln!("Starting mixnode {}...", args.id); eprintln!("Starting mixnode {}...", args.id);
let mut config = match Config::load_from_file(Some(&args.id)) { let mut config = match Config::load_from_file(&args.id) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!( error!(

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