Compare commits

..

58 Commits

Author SHA1 Message Date
farbanas 165e7d8b27 Merge branch 'release/v1.1.9' of github.com:nymtech/nym into release/v1.1.9 2023-02-14 07:30:25 -05:00
farbanas 3c97d0d16b Updated changelogs 2023-02-14 07:28:59 -05:00
farbanas bc55c10e19 bumped versions for release/v1.1.9 2023-02-14 07:18:03 -05:00
Fouad a925c39642 Wallet - Fix operator cost in playground (#3021)
* remove upper limit restriction

* update validation
2023-02-14 12:13:19 +00:00
Fouad d23fb366e4 update changelog and fix lint errors (#3023)
add unreleased tag into NC changelog
2023-02-14 10:46:10 +00:00
Bogdan-Ștefan Neacşu 3d500c25c5 Hide coconut runtime flags (#2990) 2023-02-14 11:58:04 +02:00
Fouad 9348722b84 Feature/nym connect pick a gateway (#3008)
* Add new route and initial UI

* allow IdentityKeyFormField to have a small size option

* add disabled prop to the shared IdentityKeyFormField component

* defined custom gateway type

* use custom gateway in state

* set and validate custom gateway in settings page

* validate user gateway when moving away from page

* use storage

* hide gateway input when inactive

* add explorer link to settings page
2023-02-13 22:47:48 +00:00
Fouad 726a406797 update add account instructions (#2981)
* update add account instructions
2023-02-13 10:55:53 +00:00
Fouad 4652d65874 Update password strength checking (#2994)
* use zxcvbn password strength checker

* prevent user from proceeding with a weak password + add tips

* create storybook for password strength component

* update storybook

* update password-strength
2023-02-13 10:27:04 +00:00
Fouad a4ca94ccef dont force user to copy mnemonic (#2992)
* dont force user to copy mnemonic

* add title to mnemonic page
2023-02-13 10:26:37 +00:00
Dave Hrycyszyn 24839770ff Fixing Cargo.lock to include updated version of nym-api 2023-02-08 16:54:09 +00:00
Dave Hrycyszyn 7ac3ec3598 Merge branch 'release/v1.1.9' 2023-02-08 16:21:57 +00:00
Dave Hrycyszyn 77ae71eba4 Changelog bump to trigger nym-connect build 2023-02-08 16:15:33 +00:00
Dave Hrycyszyn d4b836277e Merge branch 'release/v1.1.9' 2023-02-08 16:04:49 +00:00
Dave Hrycyszyn b92ee84874 Building the SDK package and only selected examples 2023-02-08 15:56:30 +00:00
pierre 2eb0ce381a fix(nym-connect): lint errors 2023-02-08 16:36:00 +01:00
Jon Häggblad 9f42f0152b Merge branch 'release/v1.1.9' 2023-02-08 13:27:32 +01:00
Dave Hrycyszyn 5217edcca3 Changelog tweaks 2023-02-08 13:14:12 +01:00
joeiacono2021 e306effdac Merge branch 'release/v1.1.9' of https://github.com/nymtech/nym into release/v1.1.9 2023-02-08 13:14:12 +01:00
joeiacono2021 dc2b1c6d2a changelog changes for release 1.1.9 2023-02-08 13:14:12 +01:00
Jon Häggblad 4232801e80 changelog: add note about fix for unexpected shutdown 2023-02-08 13:14:12 +01:00
Fouad 96df3ad4ce Feature/nym connect health status frontend (#2969)
* filter services on rust side by gateway performance

* format rust code

* create events hook

* use events hook

* remove unused component

remove unused component

update prop names in svg

* display errors in connected screen when needed

update failed health check message
2023-02-08 13:14:12 +01:00
Fouad d614a2b81b link owner field to ng explorer (#2970)
* link owner field to ng explorer
2023-02-08 13:14:12 +01:00
Fouad d27245e184 filter services on rust side by gateway performance (#2966)
* filter services on rust side by gateway performance

* update changelog for NC
2023-02-08 13:14:12 +01:00
Mark Sinclair 5dbfcadfdb GitHub Actions: fix up build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Jędrzej Stuczyński 035dada0e0 introduced '/circulating-supply/total-supply-value' and '/circulating-supply/circulating-supply-value' endpoints (#2965) 2023-02-08 13:14:12 +01:00
Mark Sinclair 1d867156e3 Update build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Mark Sinclair ed9be47ec4 Update build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Mark Sinclair 3aa2e6c54d Update build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Mark Sinclair eb96fc72b9 GitHub Actions: add action to build and upload binaries to CI server 2023-02-08 13:14:12 +01:00
Jon Häggblad 59cec6f03c Don't drop in mixnet connection handlers (#2963) 2023-02-08 13:14:12 +01:00
Fouad c0a0d89a90 NymConnect - Add button animations (#2950)
* add button animations

* pulse and disable button on connecting/disconnecting status

* update button component story

* disabled hover on connecting/disconnecting

* add transition delay

fix up overflow
2023-02-08 13:14:12 +01:00
Jędrzej Stuczyński 362e7f2fea 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-08 08:59:48 +01:00
Pierre Dommerc eeba17a01f 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-08 08:59:48 +01:00
Bogdan-Ștefan Neacşu 3bc7f281b4 Fix typo during merge back to develop (#2956) 2023-02-08 08:59:48 +01:00
cgi-bin/ 5a89e894a9 typo: electrum (#2954) 2023-02-08 08:59:48 +01:00
farbanas 8dbddb7b7e fix: formatting 2023-02-08 08:59:48 +01:00
farbanas b62c969a7c fix: wrong parameter type for addresses in generator commands (should be AccountId instead of String) 2023-02-08 08:59:48 +01:00
Fran Arbanas 5d10e62450 Update CHANGELOG.md 2023-02-08 08:59:48 +01:00
Jon Häggblad 21e636616d Merge tag 'nym-binaries-v1.1.8' into develop 2023-02-08 08:25:24 +01:00
farbanas 9881a94757 bumped nym-api version 2023-02-03 15:12:18 +01:00
Jędrzej Stuczyński 76b07d487b introduced '/circulating-supply/total-supply-value' and '/circulating-supply/circulating-supply-value' endpoints (#2965) 2023-02-03 13:37:55 +00:00
farbanas f04fc452dc Merge branch 'release/v1.1.8' of github.com:nymtech/nym into release/v1.1.8 2023-01-31 14:05:53 +01:00
farbanas be90d03129 changelog cleanup 2023-01-31 14:01:25 +01:00
Bogdan-Ștefan Neacşu 0a3e42700c Fix vote soft error everywhere (#2941) 2023-01-31 14:58:47 +02:00
joeiacono2021 55d554701c Merge branch 'release/v1.1.8' of https://github.com/nymtech/nym into release/v1.1.8 2023-01-31 12:55:09 +00:00
joeiacono2021 19c4769260 Changelog Updates for RELEASE 1.1.8 on 31/01 2023-01-31 12:54:55 +00:00
farbanas 71aadc8e1b update versions for the release v1.1.8 2023-01-31 13:44:41 +01:00
Fouad 95340b5817 Feature/nym connect new UI (#2916)
* reduce window size

* use new highlight color

* use react router

* render new routes

* remove old help page

* render app routes

* update connection status UI

* remove service provider info

* remove unneeded additional step

* render title from route

* experimental warning as component

* render connection page

* nym-connect: connectivity status improvements (#2915)

* connect: keep track of connectivity state

* nym-connect: query connection state

* nym-connect: function for kicking of the health check task

* rustfmt

* nym-connect: extract out into function

* nym-connect: extract out events.rs

* add app version to menu page

* help page content and style updates

* update guide content

* use layout component on disconnect page

* handle gateway issues

* only show info modal once after connecting

* power button colors

* update stories and button colors

---------

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2023-01-31 11:39:38 +00:00
Tommy Verrall 12751665bb white space 2023-01-31 09:14:56 -01:00
Tommy Verrall 01b86bcc0d updating qwerty contract addresses 2023-01-31 07:39:18 -01:00
Bogdan-Ștefan Neacşu c6ce8caaf7 Feature/fix soft multisig error (#2938) 2023-01-30 18:44:45 +02:00
Jędrzej Stuczyński 265713b9d2 Renamed 'initial_supply' to 'total_supply' in the 'circulating-supply' endpoint (#2932)
* Renamed 'initial_supply' to 'total_supply' in the 'circulating-supply' endpoint

* clippy issue messing with CI
2023-01-30 15:39:55 +00:00
Jon Häggblad c9af4721f3 wasm-utils: fix clippy 2023-01-30 10:46:11 +01:00
Pierre Dommerc 8c0ab7c697 feat(explorer): add routing score on gateway list (#2913)
* feat: adding routing score on gateway list

* feat(explorer): adding routing score on gateway list

* feat(explorer): add routing score on gateway list
2023-01-26 18:39:26 +01:00
Bogdan-Ștefan Neacșu 92b220ca4b Fix typo 2023-01-26 12:27:11 +02:00
Jędrzej Stuczyński c218cba96c Feature/default nym api (#2898)
* Setting default 'id' if not provided

* Modified 'NymConfig' to always require 'id'

* moved creation of nym-api directories away from 'override_config'

* missing optional id usage in nym-connect

* changelog

* Removed default value for '--id' argument
2023-01-25 15:49:28 +01:00
Jędrzej Stuczyński c958975fff Merge branch 'master' into release/v1.1.8 2023-01-25 13:40:20 +00:00
219 changed files with 2186 additions and 5355 deletions
@@ -34,7 +34,7 @@ on:
- 'tools/ts-rs-cli/**'
env:
NETWORK: mainnet
NETWORK: mainnet
jobs:
publish-nym:
@@ -46,7 +46,7 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Prepare build output directory
shell: bash
env:
@@ -110,4 +110,3 @@ jobs:
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
EXCLUDE: "/dist/, /node_modules/"
-127
View File
@@ -1,127 +0,0 @@
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
nym-connect/Cargo.lock
.parcel-cache
**/.DS_Store
.DS_Store
+11 -6
View File
@@ -4,15 +4,20 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
# [Unreleased]
# [v1.1.9] (2023-02-07)
### Added
- Remove Coconut feature flag ([#2793])
- Separate `nym-api` endpoints with values of "total-supply" and "circulating-supply" in `nym` ([#2964])
- 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])
### Changed
- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2912]). 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.
- mixnode, gateway: fix unexpected shutdown on corrupted connection ([#2963])
[#2890]: https://github.com/nymtech/nym/pull/2890
[#2939]: https://github.com/nymtech/nym/pull/2939
[#2936]: https://github.com/nymtech/nym/pull/2936
[#2793]: https://github.com/nymtech/nym/issues/2793
[#2912]: https://github.com/nymtech/nym/issues/2912
[#2964]: https://github.com/nymtech/nym/issues/2964
[#2963]: https://github.com/nymtech/nym/issues/3017
# [v1.1.8] (2023-01-31)
Generated
+1 -24
View File
@@ -458,7 +458,6 @@ dependencies = [
name = "build-information"
version = "0.1.0"
dependencies = [
"serde",
"vergen 7.5.0",
]
@@ -3439,7 +3438,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.8"
version = "1.1.9"
dependencies = [
"anyhow",
"async-trait",
@@ -3735,7 +3734,6 @@ name = "nym-network-requester"
version = "1.1.8"
dependencies = [
"async-trait",
"build-information",
"clap 4.1.3",
"client-connections",
"completions",
@@ -3753,7 +3751,6 @@ dependencies = [
"rand 0.7.3",
"reqwest",
"serde",
"service-providers-common",
"socks5-requests",
"sqlx 0.6.2",
"statistics-common",
@@ -3853,7 +3850,6 @@ dependencies = [
"rand 0.7.3",
"serde",
"serde_json",
"service-providers-common",
"socks5-requests",
"tap",
"task",
@@ -5497,22 +5493,6 @@ dependencies = [
"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]]
name = "sha-1"
version = "0.9.8"
@@ -5694,9 +5674,6 @@ name = "socks5-requests"
version = "0.1.0"
dependencies = [
"nymsphinx-addressing",
"serde",
"serde_json",
"service-providers-common",
"thiserror",
]
-6
View File
@@ -76,7 +76,6 @@ members = [
"integrations/bity",
"mixnode",
"sdk/rust/nym-sdk",
"service-providers/common",
"service-providers/network-requester",
"service-providers/network-statistics",
"nym-api",
@@ -106,9 +105,4 @@ homepage = "https://nymtech.net"
edition = "2021"
[workspace.dependencies]
async-trait = "0.1.63"
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]
name = "client-core"
version = "1.1.8"
version = "1.1.9"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
+1 -6
View File
@@ -171,15 +171,10 @@ impl<T> Config<T> {
self
}
pub fn set_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
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) {
self.client.gateway_endpoint.gateway_id = id.into();
}
-6
View File
@@ -82,11 +82,6 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
let config = Config::try_from_nym_network_details(&network_details)?;
let client = validator_client::Client::new_query(config)?;
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 params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
@@ -103,7 +98,6 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
&params,
&bandwidth_credential_attributes,
&coconut_api_clients,
threshold,
)
.await?;
println!("Signature: {:?}", signature.to_bs58());
-3
View File
@@ -34,7 +34,4 @@ pub enum CredentialClientError {
#[error("Could not use shared storage")]
SharedStorageError(#[from] StorageError),
#[error("Threshold not set yet")]
NoThreshold,
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.8"
version = "1.1.9"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+3 -5
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::template::config_template;
use client_core::config::ClientCoreConfigTrait;
pub use client_core::config::Config as BaseConfig;
pub use client_core::config::MISSING_VALUE;
use client_core::config::{ClientCoreConfigTrait, DebugConfig};
use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use config::{NymConfig, OptionalSet};
use serde::{Deserialize, Serialize};
@@ -11,10 +13,6 @@ use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
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;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
+9 -52
View File
@@ -11,22 +11,19 @@ use client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use client_core::client::inbound_messages::InputMessage;
use client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
};
use client_core::client::key_manager::KeyManager;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
use nymsphinx::receiver::ReconstructedMessage;
use task::TaskManager;
use tokio::sync::watch::error::SendError;
use validator_client::nyxd::QueryNyxdClient;
pub use client_core::client::key_manager::KeyManager;
pub use nymsphinx::addressing::clients::Recipient;
pub use nymsphinx::receiver::ReconstructedMessage;
pub mod config;
pub(crate) mod config;
pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates,
@@ -48,13 +45,6 @@ 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> {
let details = network_defaults::NymNetworkDetails::new_from_env();
let mut client_config = validator_client::Config::try_from_nym_network_details(&details)
@@ -130,17 +120,10 @@ impl SocketClient {
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(
self.config.get_base(),
self.key_manager,
bandwidth_controller,
Some(Self::create_bandwidth_controller(&self.config).await),
non_wasm_helpers::setup_fs_reply_surb_backend(
Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(),
@@ -174,17 +157,10 @@ impl SocketClient {
return Err(ClientError::InvalidSocketMode);
}
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None
} else {
Some(Self::create_bandwidth_controller(&self.config).await)
};
let base_client = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
bandwidth_controller,
Some(Self::create_bandwidth_controller(&self.config).await),
non_wasm_helpers::setup_fs_reply_surb_backend(
Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(),
@@ -192,8 +168,6 @@ impl SocketClient {
.await?,
);
let address = base_client.as_mix_recipient();
let mut started_client = base_client.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
@@ -211,38 +185,21 @@ impl SocketClient {
Ok(DirectClient {
client_input,
_received_buffer_request_sender: client_output.received_buffer_request_sender,
reconstructed_receiver,
address,
shutdown_notifier: started_client.task_manager,
_shutdown_notifier: started_client.task_manager,
})
}
}
pub struct DirectClient {
client_input: ClientInput,
// make sure to not drop the channel
_received_buffer_request_sender: ReceivedBufferRequestSender,
reconstructed_receiver: ReconstructedMessagesReceiver,
address: Recipient,
// we need to keep reference to this guy otherwise things will start dropping
shutdown_notifier: TaskManager,
_shutdown_notifier: TaskManager,
}
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
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
+1 -1
View File
@@ -147,7 +147,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.get_base_mut().set_gateway_endpoint(gateway);
config.get_base_mut().with_gateway_endpoint(gateway);
config.save_to_file(None).tap_err(|_| {
log::error!("Failed to save the config file");
+3 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.8"
version = "1.1.9"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -19,8 +19,8 @@ log = { workspace = true }
pin-project = "1.0"
pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
serde_json = { workspace = true }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
serde_json = "1.0.89"
tap = "1.0.1"
thiserror = "1.0.34"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
@@ -45,7 +45,6 @@ nymsphinx = { path = "../../common/nymsphinx" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
service-providers-common = { path = "../../service-providers/common" }
socks5-requests = { path = "../../common/socks5/requests" }
task = { path = "../../common/task" }
topology = { path = "../../common/topology" }
+1 -34
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::template::config_template;
@@ -9,8 +9,6 @@ use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
use config::{NymConfig, OptionalSet};
use nymsphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use service_providers_common::interface::ProviderInterfaceVersion;
use socks5_requests::Socks5ProtocolVersion;
use std::fmt::Debug;
use std::path::PathBuf;
use std::str::FromStr;
@@ -92,16 +90,6 @@ impl Config {
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 {
self.socks5.send_anonymously = anonymous_replies;
self
@@ -129,14 +117,6 @@ impl Config {
.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 {
self.socks5.send_anonymously
}
@@ -207,15 +187,6 @@ pub struct Socks5 {
/// The mix address of the provider to which all requests are going to be sent.
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.
/// 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.
@@ -230,8 +201,6 @@ impl Socks5 {
Socks5 {
listening_port: DEFAULT_SOCKS5_LISTENING_PORT,
provider_mix_address: provider_mix_address.into(),
provider_interface_version: ProviderInterfaceVersion::Legacy,
socks5_protocol_version: Socks5ProtocolVersion::Legacy,
send_anonymously: false,
}
}
@@ -242,8 +211,6 @@ impl Default for Socks5 {
Socks5 {
listening_port: DEFAULT_SOCKS5_LISTENING_PORT,
provider_mix_address: "".into(),
provider_interface_version: ProviderInterfaceVersion::Legacy,
socks5_protocol_version: Socks5ProtocolVersion::Legacy,
send_anonymously: false,
}
}
-2
View File
@@ -132,8 +132,6 @@ impl NymClient {
self_address,
shared_lane_queue_lengths,
socks::client::Config::new(
config.get_provider_interface_version(),
config.get_socks5_protocol_version(),
config.get_send_anonymously(),
config.get_connection_start_surbs(),
config.get_per_request_surbs(),
+1 -3
View File
@@ -153,9 +153,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
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.get_base_mut().with_gateway_endpoint(gateway);
config.save_to_file(None).tap_err(|_| {
log::error!("Failed to save the config file");
+1 -10
View File
@@ -1,6 +1,6 @@
use crate::socks::types::SocksProxyError;
use client_core::error::ClientCoreError;
use socks5_requests::{ConnectionError, ConnectionId};
use socks5_requests::ConnectionId;
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
@@ -28,12 +28,3 @@ pub enum Socks5ClientError {
error: String,
},
}
impl From<ConnectionError> for Socks5ClientError {
fn from(value: ConnectionError) -> Self {
Socks5ClientError::NetworkRequesterError {
connection_id: value.connection_id,
error: value.network_requester_error,
}
}
}
+30 -132
View File
@@ -16,10 +16,7 @@ use proxy_helpers::connection_controller::{
};
use proxy_helpers::proxy_runner::ProxyRunner;
use rand::RngCore;
use service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use socks5_requests::{ConnectionId, Message, RemoteAddress, Request};
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
@@ -131,8 +128,6 @@ impl AsyncWrite for StreamState {
#[derive(Debug, Copy, Clone)]
pub(crate) struct Config {
provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool,
connection_start_surbs: u32,
per_request_surbs: u32,
@@ -140,27 +135,16 @@ pub(crate) struct Config {
impl Config {
pub(crate) fn new(
provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool,
connection_start_surbs: u32,
per_request_surbs: u32,
) -> Self {
Self {
provider_interface_version,
socks5_protocol_version,
use_surbs_for_responses,
connection_start_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
@@ -189,9 +173,7 @@ impl Drop for SocksClient {
// if we never managed to start a proxy, the entry will not exist in the controller
if self.started_proxy {
self.controller_sender
.unbounded_send(ControllerCommand::Remove {
connection_id: self.connection_id,
})
.unbounded_send(ControllerCommand::Remove(self.connection_id))
.unwrap();
}
}
@@ -266,26 +248,19 @@ impl SocksClient {
// Send an error back to the client
pub async fn send_error_v4(&mut self, r: ResponseCodeV4) -> Result<(), SocksProxyError> {
self.stream
.write_all(&[SOCKS4_VERSION, r as u8])
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })
self.stream.write_all(&[SOCKS4_VERSION, r as u8]).await?;
Ok(())
}
pub async fn send_error_v5(&mut self, r: ResponseCodeV5) -> Result<(), SocksProxyError> {
self.stream
.write_all(&[SOCKS5_VERSION, r as u8])
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })
self.stream.write_all(&[SOCKS5_VERSION, r as u8]).await?;
Ok(())
}
/// Shutdown the `TcpStream` to the client and end the session
pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> {
info!("client is shutting down its TCP stream");
self.stream
.shutdown()
.await
.map_err(|source| SocksProxyError::SocketShutdownFailure { source })?;
self.stream.shutdown().await?;
self.shutdown_listener.mark_as_success();
Ok(())
}
@@ -293,20 +268,11 @@ impl SocksClient {
/// Initializes the new client, checking that the correct Socks version (5)
/// is in use and that the client is authenticated, then runs the request.
pub async fn run(&mut self) -> Result<(), SocksProxyError> {
debug!(
"New connection from: {}",
self.stream
.peer_addr()
.map_err(|source| SocksProxyError::PeerAddrExtractionFailure { source })?
.ip()
);
debug!("New connection from: {}", self.stream.peer_addr()?.ip());
// Read a byte from the stream and determine the version being requested
let mut header = [0u8];
self.stream
.read_exact(&mut header)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.stream.read_exact(&mut header).await?;
self.socks_version = match SocksVersion::try_from(header[0]) {
Ok(version) => Some(version),
@@ -318,10 +284,7 @@ impl SocksClient {
if self.socks_version == Some(SocksVersion::V5) {
let mut auth = [0u8];
self.stream
.read_exact(&mut auth)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.stream.read_exact(&mut auth).await?;
self.auth_nmethods = auth[0];
self.authenticate_socks5().await?;
}
@@ -330,15 +293,8 @@ impl SocksClient {
}
async fn send_anonymous_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
// TODO: simplify by using `request_version`
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 req = Request::new_connect(self.connection_id, remote_address, None);
let msg = Message::Request(req);
let input_message = InputMessage::new_anonymous(
self.service_provider,
@@ -353,15 +309,8 @@ impl SocksClient {
}
async fn send_connect_to_mixnet_with_return_address(&mut self, remote_address: RemoteAddress) {
// TODO: simplify by using `request_version`
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 req = Request::new_connect(self.connection_id, remote_address, Some(self.self_address));
let msg = Message::Request(req);
let input_message = InputMessage::new_regular(
self.service_provider,
@@ -401,7 +350,6 @@ impl SocksClient {
let input_sender = self.input_sender.clone();
let anonymous = self.config.use_surbs_for_responses;
let per_request_surbs = self.config.per_request_surbs;
let request_version = self.config.request_version();
let recipient = self.service_provider;
let (stream, _) = ProxyRunner::new(
@@ -415,16 +363,8 @@ impl SocksClient {
self.shutdown_listener.clone(),
)
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Socks5Request::new_send(
request_version.provider_protocol,
conn_id,
read_data,
socket_closed,
);
let provider_message = Socks5ProviderRequest::new_provider_data(
request_version.provider_interface,
provider_request,
);
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
let provider_message = Message::Request(provider_request);
let lane = TransmissionLane::ConnectionId(conn_id);
if anonymous {
InputMessage::new_anonymous(
@@ -473,10 +413,7 @@ impl SocksClient {
self.started_proxy = true;
self.controller_sender
.unbounded_send(ControllerCommand::Insert {
connection_id: self.connection_id,
connection_sender: mix_sender,
})
.unbounded_send(ControllerCommand::Insert(self.connection_id, mix_sender))
.unwrap();
info!(
@@ -554,13 +491,7 @@ impl SocksClient {
/// into the Authenticator (where it'll be more easily testable)
/// would be a good next step.
async fn authenticate_socks5(&mut self) -> Result<(), SocksProxyError> {
debug!(
"Authenticating w/ {}",
self.stream
.peer_addr()
.map_err(|source| SocksProxyError::PeerAddrExtractionFailure { source })?
.ip()
);
debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip());
// Get valid auth methods
let methods = self.get_available_methods().await?;
trace!("methods: {:?}", methods);
@@ -574,45 +505,27 @@ impl SocksClient {
response[1] = AuthenticationMethods::UserPass as u8;
debug!("Sending USER/PASS packet");
self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
self.stream.write_all(&response).await?;
let mut header = [0u8; 2];
// Read a byte from the stream and determine the version being requested
self.stream
.read_exact(&mut header)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.stream.read_exact(&mut header).await?;
// debug!("Auth Header: [{}, {}]", header[0], header[1]);
// Username parsing
let ulen = header[1];
let mut username = vec![0; ulen as usize];
self.stream
.read_exact(&mut username)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.stream.read_exact(&mut username).await?;
// Password Parsing
let plen = self
.stream
.read_u8()
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
let plen = self.stream.read_u8().await?;
let mut password = vec![0; plen as usize];
self.stream
.read_exact(&mut password)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.stream.read_exact(&mut password).await?;
let username_str = String::from_utf8(username)
.map_err(|source| SocksProxyError::MalformedAuthUsername { source })?;
let password_str = String::from_utf8(password)
.map_err(|source| SocksProxyError::MalformedAuthPassword { source })?;
let username_str = String::from_utf8(username)?;
let password_str = String::from_utf8(password)?;
let user = User {
username: username_str,
@@ -623,17 +536,11 @@ impl SocksClient {
if self.authenticator.is_allowed(&user) {
debug!("Access Granted. User: {}", user.username);
let response = [1, ResponseCodeV5::Success as u8];
self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
self.stream.write_all(&response).await?;
} else {
debug!("Access Denied. User: {}", user.username);
let response = [1, ResponseCodeV5::Failure as u8];
self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
self.stream.write_all(&response).await?;
// Shutdown
self.shutdown().await?;
@@ -644,18 +551,12 @@ impl SocksClient {
// set the default auth method (no auth)
response[1] = AuthenticationMethods::NoAuth as u8;
debug!("Sending NOAUTH packet");
self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
self.stream.write_all(&response).await?;
Ok(())
} else {
warn!("Client has no suitable authentication methods!");
response[1] = AuthenticationMethods::NoMethods as u8;
self.stream
.write_all(&response)
.await
.map_err(|source| SocksProxyError::SocketWriteError { source })?;
self.stream.write_all(&response).await?;
self.shutdown().await?;
Err(ResponseCodeV5::Failure.into())
}
@@ -666,10 +567,7 @@ impl SocksClient {
let mut methods: Vec<u8> = Vec::with_capacity(self.auth_nmethods as usize);
for _ in 0..self.auth_nmethods {
let mut method = [0u8; 1];
self.stream
.read_exact(&mut method)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
self.stream.read_exact(&mut method).await?;
if self.authenticator.auth_methods.contains(&method[0]) {
methods.append(&mut method.to_vec());
}
+30 -54
View File
@@ -5,9 +5,8 @@ use log::*;
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
use nymsphinx::receiver::ReconstructedMessage;
use proxy_helpers::connection_controller::ControllerSender;
use service_providers_common::interface::{ControlResponse, ResponseContent};
use socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
use socks5_requests::Message;
use task::TaskClient;
use crate::error::Socks5ClientError;
@@ -53,39 +52,6 @@ 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(
&self,
reconstructed_message: ReconstructedMessage,
@@ -94,28 +60,38 @@ impl MixnetResponseListener {
if reconstructed_message.sender_tag.is_some() {
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) => {
warn!("failed to parse received response: {err}");
Ok(())
warn!("failed to parse received response - {err}");
return Ok(());
}
Ok(response) => {
// as long as the client used the same (or older) interface than the service provider,
// the response should have used exactly the same version
trace!(
"the received response was sent with {:?} interface version",
response.interface_version
Ok(Message::Request(_)) => {
warn!("unexpected request");
return Ok(());
}
Ok(Message::Response(data)) => data,
Ok(Message::NetworkRequesterResponse(r)) => {
error!(
"Network requester failed on connection id {} with error: {}",
r.connection_id, r.network_requester_error
);
match response.content {
ResponseContent::Control(control_response) => {
self.on_control_response(control_response)
}
ResponseContent::ProviderData(provider_response) => {
self.on_provider_data_response(provider_response)
}
}
return Err(Socks5ClientError::NetworkRequesterError {
connection_id: r.connection_id,
error: r.network_requester_error,
});
}
}
};
self.controller_sender
.unbounded_send(ControllerCommand::Send(
response.connection_id,
response.data,
response.is_closed,
))
.unwrap();
Ok(())
}
pub(crate) async fn run(&mut self) {
+1 -1
View File
@@ -33,7 +33,7 @@ impl TryFrom<u8> for SocksVersion {
match version {
SOCKS4_VERSION => Ok(Self::V4),
SOCKS5_VERSION => Ok(Self::V5),
_ => Err(SocksProxyError::UnsupportedProxyVersion { version }),
_ => Err(SocksProxyError::UnsupportedProxyVersion(version)),
}
}
}
+10 -39
View File
@@ -28,10 +28,7 @@ impl SocksRequest {
log::trace!("read from stream socks4");
let mut packet = [0u8; 3];
stream
.read_exact(&mut packet)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut packet).await?;
// CD (command)
let Some(command) = SocksCommand::from(packet[0] as usize) else {
@@ -46,10 +43,7 @@ impl SocksRequest {
// DSTIP
let mut ip = [0u8; 4];
stream
.read_exact(&mut ip)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut ip).await?;
// USERID
let _userid = read_until_zero(stream).await;
@@ -82,17 +76,12 @@ impl SocksRequest {
let mut packet = [0u8; 4];
// Read a byte from the stream and determine the version being requested
stream
.read_exact(&mut packet)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut packet).await?;
// VER
if packet[0] != SOCKS5_VERSION {
warn!("Unsupported version: SOCKS{}", packet[0]);
return Err(SocksProxyError::UnsupportedProxyVersion {
version: (packet[0]),
});
return Err(SocksProxyError::UnsupportedProxyVersion(packet[0]));
}
// CMD
@@ -114,41 +103,26 @@ impl SocksRequest {
let addr = match addr_type {
AddrType::Domain => {
let mut domain_length = [0u8];
stream
.read_exact(&mut domain_length)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut domain_length).await?;
let mut domain = vec![0u8; domain_length[0] as usize];
stream
.read_exact(&mut domain)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut domain).await?;
domain
}
AddrType::V4 => {
let mut addr = [0u8; 4];
stream
.read_exact(&mut addr)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut addr).await?;
addr.to_vec()
}
AddrType::V6 => {
let mut addr = [0u8; 16];
stream
.read_exact(&mut addr)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut addr).await?;
addr.to_vec()
}
};
// DST.PORT
let mut port = [0u8; 2];
stream
.read_exact(&mut port)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut port).await?;
let port = merge_u8_into_u16(port[0], port[1]);
Ok(SocksRequest {
@@ -205,10 +179,7 @@ where
let mut result = Vec::new();
let mut char = [0u8];
loop {
stream
.read_exact(&mut char)
.await
.map_err(|source| SocksProxyError::SocketReadError { source })?;
stream.read_exact(&mut char).await?;
if char[0] == 0 {
break;
}
-5
View File
@@ -86,11 +86,6 @@ impl SphinxSocksServer {
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 {
tokio::select! {
Ok((stream, _remote)) = listener.accept() => {
+24 -52
View File
@@ -1,7 +1,3 @@
use socks5_requests::Socks5RequestError;
use std::string::FromUtf8Error;
use thiserror::Error;
/// SOCKS4 Response codes
#[allow(dead_code)]
pub(crate) enum ResponseCodeV4 {
@@ -12,8 +8,9 @@ pub(crate) enum ResponseCodeV4 {
}
/// Possible SOCKS5 Response Codes
#[derive(Debug, Error)]
pub enum ResponseCodeV5 {
#[allow(dead_code)]
#[derive(Debug, thiserror::Error)]
pub(crate) enum ResponseCodeV5 {
#[error("SOCKS5 Server Success")]
Success = 0x00,
#[error("SOCKS5 Server Failure")]
@@ -34,55 +31,30 @@ pub enum ResponseCodeV5 {
AddrTypeNotSupported = 0x08,
}
#[derive(Error, Debug)]
#[derive(Debug)]
pub enum SocksProxyError {
#[error("{version} of the socks protocol is not supported by this client")]
UnsupportedProxyVersion { version: u8 },
GenericError(Box<dyn std::error::Error + Send + Sync>),
UnsupportedProxyVersion(u8),
}
#[error("failed to write to the socket: {source}")]
SocketWriteError {
#[source]
source: std::io::Error,
},
impl std::fmt::Display for SocksProxyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SocksProxyError::GenericError(err) => write!(f, "GenericError - {err}"),
SocksProxyError::UnsupportedProxyVersion(version) => {
write!(f, "Unsupported proxy version {}", version)
}
}
}
}
#[error("failed to read from the socket: {source}")]
SocketReadError {
#[source]
source: std::io::Error,
},
#[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,
},
impl<E> From<E> for SocksProxyError
where
E: std::error::Error + Send + Sync + 'static,
{
fn from(err: E) -> Self {
SocksProxyError::GenericError(Box::new(err))
}
}
/// DST.addr variant types
-4
View File
@@ -6,10 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { workspace = true, features = ["derive"], optional = true }
[build-dependencies]
vergen = { version = "7", default-features = false, features = ["build", "git", "rustc", "cargo"] }
[features]
default = []
-50
View File
@@ -4,7 +4,6 @@
// TODO: at a later date this crate should probably also expose `ContractBuildInformation`
// and be used by our smart contracts
#[derive(Debug)]
pub struct BinaryBuildInformation {
// VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
@@ -54,19 +53,6 @@ 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 {
format!(
r#"
@@ -98,39 +84,3 @@ 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,
};
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use coconut_dkg_common::types::{Epoch, EpochId, InitialReplacementData};
use coconut_dkg_common::types::{Epoch, EpochId};
use coconut_dkg_common::verification_key::PagedVKSharesResponse;
use cosmrs::AccountId;
@@ -16,7 +16,6 @@ use cosmrs::AccountId;
pub trait DkgQueryClient {
async fn get_current_epoch(&self) -> Result<Epoch, 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(
&self,
address: &AccountId,
@@ -63,14 +62,6 @@ where
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.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(
&self,
address: &AccountId,
@@ -17,21 +17,18 @@ pub trait DkgSigningClient {
&self,
bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
async fn submit_dealing_bytes(
&self,
commitment: ContractSafeBytes,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
}
@@ -60,13 +57,11 @@ where
&self,
bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::RegisterDealer {
bte_key_with_proof: bte_key,
announce_address,
resharing,
};
self.client
@@ -84,13 +79,9 @@ where
async fn submit_dealing_bytes(
&self,
dealing_bytes: ContractSafeBytes,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitDealing {
dealing_bytes,
resharing,
};
let req = DkgExecuteMsg::CommitDealing { dealing_bytes };
self.client
.execute(
@@ -107,10 +98,9 @@ where
async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitVerificationKeyShare { share, resharing };
let req = DkgExecuteMsg::CommitVerificationKeyShare { share };
self.client
.execute(
@@ -21,22 +21,18 @@ pub enum ExecuteMsg {
RegisterDealer {
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
announce_address: String,
resharing: bool,
},
CommitDealing {
dealing_bytes: ContractSafeBytes,
resharing: bool,
},
CommitVerificationKeyShare {
share: VerificationKeyShare,
resharing: bool,
},
VerifyVerificationKeyShare {
owner: Addr,
resharing: bool,
},
SurpassedThreshold {},
@@ -49,7 +45,6 @@ pub enum ExecuteMsg {
pub enum QueryMsg {
GetCurrentEpochState {},
GetCurrentEpochThreshold {},
GetInitialDealers {},
GetDealerDetails {
dealer_address: String,
},
@@ -18,12 +18,6 @@ pub type EpochId = u64;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
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(
Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, JsonSchema,
)]
@@ -92,17 +86,15 @@ impl Epoch {
current_timestamp: Timestamp,
) -> Self {
let duration = match state {
EpochState::PublicKeySubmission { .. } => {
time_configuration.public_key_submission_time_secs
}
EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs,
EpochState::VerificationKeySubmission { .. } => {
EpochState::PublicKeySubmission => time_configuration.public_key_submission_time_secs,
EpochState::DealingExchange => time_configuration.dealing_exchange_time_secs,
EpochState::VerificationKeySubmission => {
time_configuration.verification_key_submission_time_secs
}
EpochState::VerificationKeyValidation { .. } => {
EpochState::VerificationKeyValidation => {
time_configuration.verification_key_validation_time_secs
}
EpochState::VerificationKeyFinalization { .. } => {
EpochState::VerificationKeyFinalization => {
time_configuration.verification_key_finalization_time_secs
}
EpochState::InProgress => time_configuration.in_progress_time_secs,
@@ -131,36 +123,28 @@ impl Epoch {
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[serde(rename_all = "snake_case")]
pub enum EpochState {
PublicKeySubmission { resharing: bool },
DealingExchange { resharing: bool },
VerificationKeySubmission { resharing: bool },
VerificationKeyValidation { resharing: bool },
VerificationKeyFinalization { resharing: bool },
PublicKeySubmission,
DealingExchange,
VerificationKeySubmission,
VerificationKeyValidation,
VerificationKeyFinalization,
InProgress,
}
impl Default for EpochState {
fn default() -> Self {
Self::PublicKeySubmission { resharing: false }
Self::PublicKeySubmission
}
}
impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EpochState::PublicKeySubmission { resharing } => {
write!(f, "PublicKeySubmission with resharing {resharing}")
}
EpochState::DealingExchange { resharing } => write!(f, "DealingExchange {resharing}"),
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::PublicKeySubmission => write!(f, "PublicKeySubmission"),
EpochState::DealingExchange => write!(f, "DealingExchange"),
EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"),
EpochState::VerificationKeyValidation => write!(f, "VerificationKeyValidation"),
EpochState::VerificationKeyFinalization => write!(f, "VerificationKeyFinalization"),
EpochState::InProgress => write!(f, "InProgress"),
}
}
@@ -169,19 +153,11 @@ impl Display for EpochState {
impl EpochState {
pub fn next(self) -> Option<Self> {
match self {
EpochState::PublicKeySubmission { resharing } => {
Some(EpochState::DealingExchange { resharing })
}
EpochState::DealingExchange { resharing } => {
Some(EpochState::VerificationKeySubmission { resharing })
}
EpochState::VerificationKeySubmission { resharing } => {
Some(EpochState::VerificationKeyValidation { resharing })
}
EpochState::VerificationKeyValidation { resharing } => {
Some(EpochState::VerificationKeyFinalization { resharing })
}
EpochState::VerificationKeyFinalization { .. } => Some(EpochState::InProgress),
EpochState::PublicKeySubmission => Some(EpochState::DealingExchange),
EpochState::DealingExchange => Some(EpochState::VerificationKeySubmission),
EpochState::VerificationKeySubmission => Some(EpochState::VerificationKeyValidation),
EpochState::VerificationKeyValidation => Some(EpochState::VerificationKeyFinalization),
EpochState::VerificationKeyFinalization => Some(EpochState::InProgress),
EpochState::InProgress => None,
}
}
@@ -30,12 +30,11 @@ pub struct PagedVKSharesResponse {
pub fn to_cosmos_msg(
owner: Addr,
resharing: bool,
coconut_dkg_addr: String,
multisig_addr: String,
expiration_time: Timestamp,
) -> StdResult<CosmosMsg> {
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner };
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_dkg_addr,
msg: to_binary(&verify_vk_share_req)?,
@@ -63,8 +62,7 @@ pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
funds: _,
})) = 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);
}
Binary file not shown.
+4 -10
View File
@@ -92,7 +92,6 @@ pub async fn obtain_aggregate_signature(
params: &Parameters,
attributes: &BandwidthVoucher,
coconut_api_clients: &[CoconutApiClient],
threshold: u64,
) -> Result<Signature, Error> {
if coconut_api_clients.is_empty() {
return Err(Error::NoValidatorsAvailable);
@@ -111,20 +110,15 @@ pub async fn obtain_aggregate_signature(
.collect();
for coconut_api_client in coconut_api_clients.iter() {
if let Ok(signature) = obtain_partial_credential(
let signature = obtain_partial_credential(
params,
attributes,
&coconut_api_client.api_client,
&coconut_api_client.verification_key,
)
.await
{
let share = SignatureShare::new(signature, coconut_api_client.node_id);
shares.push(share)
}
}
if shares.len() < threshold as usize {
return Err(Error::NotEnoughShares);
.await?;
let share = SignatureShare::new(signature, coconut_api_client.node_id);
shares.push(share)
}
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
-3
View File
@@ -29,7 +29,4 @@ pub enum Error {
#[error("Could not parse the key - {0}")]
ParsePublicKey(#[from] KeyRecoveryError),
#[error("Could not gather enough signature shares")]
NotEnoughShares,
}
-4
View File
@@ -93,10 +93,6 @@ impl SecretKey {
Self { x, ys }
}
pub fn into_raw(&self) -> (Scalar, Vec<Scalar>) {
(self.x, self.ys.clone())
}
/// Derive verification key using this secret key.
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
let g1 = params.gen1();
@@ -12,7 +12,7 @@ bs58 = "0.4"
serde = "1.0"
thiserror = "1"
crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
crypto = { path = "../../crypto" }
nymsphinx-addressing = { path = "../addressing" }
nymsphinx-params = { path = "../params" }
nymsphinx-types = { path = "../types" }
@@ -6,7 +6,7 @@ use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData};
use socks5_requests::{ConnectionId, NetworkData, SendRequest};
use socks5_requests::ConnectionId;
use std::{
collections::{HashMap, HashSet},
time::Duration,
@@ -36,38 +36,9 @@ pub type ControllerSender = mpsc::UnboundedSender<ControllerCommand>;
pub type ControllerReceiver = mpsc::UnboundedReceiver<ControllerCommand>;
pub enum ControllerCommand {
Insert {
connection_id: ConnectionId,
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,
}
}
Insert(ConnectionId, ConnectionSender),
Remove(ConnectionId),
Send(ConnectionId, Vec<u8>, bool),
}
struct ActiveConnection {
@@ -264,13 +235,13 @@ impl Controller {
loop {
tokio::select! {
command = self.receiver.next() => match command {
Some(ControllerCommand::Send{connection_id, data, is_closed}) => {
self.send_to_connection(connection_id, data, is_closed)
Some(ControllerCommand::Send(conn_id, data, is_closed)) => {
self.send_to_connection(conn_id, data, is_closed)
}
Some(ControllerCommand::Insert{connection_id, connection_sender}) => {
self.insert_connection(connection_id, connection_sender)
Some(ControllerCommand::Insert(conn_id, sender)) => {
self.insert_connection(conn_id, sender)
}
Some(ControllerCommand::Remove{ connection_id }) => self.remove_connection(connection_id),
Some(ControllerCommand::Remove(conn_id)) => self.remove_connection(conn_id),
None => {
log::trace!("SOCKS5 Controller: Stopping since channel closed");
break;
+1 -5
View File
@@ -7,9 +7,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
nymsphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
service-providers-common = { path = "../../../service-providers/common" }
thiserror = "1"
+7 -123
View File
@@ -1,128 +1,12 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use service_providers_common::interface;
use service_providers_common::interface::ServiceProviderMessagingError;
use thiserror::Error;
pub use request::*;
pub use response::*;
pub use version::*;
pub mod msg;
pub mod network_requester_response;
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);
}
}
}
pub use msg::*;
pub use network_requester_response::*;
pub use request::*;
pub use response::*;
+97
View File
@@ -0,0 +1,97 @@
// 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(),
}
}
}
@@ -0,0 +1,112 @@
// 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(_)));
}
}
+55 -178
View File
@@ -1,9 +1,7 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response};
use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError};
use service_providers_common::interface::{Serializable, ServiceProviderRequest};
use std::convert::TryFrom;
use thiserror::Error;
@@ -18,19 +16,19 @@ pub enum RequestFlag {
}
impl TryFrom<u8> for RequestFlag {
type Error = RequestDeserializationError;
type Error = RequestError;
fn try_from(value: u8) -> Result<RequestFlag, RequestDeserializationError> {
fn try_from(value: u8) -> Result<RequestFlag, RequestError> {
match value {
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
value => Err(RequestDeserializationError::UnknownRequestFlag { value }),
_ => Err(RequestError::UnknownRequestFlag),
}
}
}
#[derive(Debug, Error)]
pub enum RequestDeserializationError {
pub enum RequestError {
#[error("not enough bytes to recover the length of the address")]
AddressLengthTooShort,
@@ -43,8 +41,8 @@ pub enum RequestDeserializationError {
#[error("no data provided")]
NoData,
#[error("{value} is not a valid request flag")]
UnknownRequestFlag { value: u8 },
#[error("request of unknown type")]
UnknownRequestFlag,
#[error("too short return address")]
ReturnAddressTooShort,
@@ -53,13 +51,13 @@ pub enum RequestDeserializationError {
MalformedReturnAddress(RecipientFormattingError),
}
impl RequestDeserializationError {
impl RequestError {
pub fn is_malformed_return(&self) -> bool {
matches!(self, RequestDeserializationError::MalformedReturnAddress(_))
matches!(self, RequestError::MalformedReturnAddress(_))
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct ConnectRequest {
// TODO: is connection_id redundant now?
pub conn_id: ConnectionId,
@@ -67,128 +65,27 @@ pub struct ConnectRequest {
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
/// take an action for an application using a (probably local) Nym Socks5 proxy.
#[derive(Debug, Clone)]
pub enum Socks5RequestContent {
#[derive(Debug)]
pub enum Request {
/// Start a new TCP connection to the specified `RemoteAddress` and send
/// the request data up the connection.
/// All responses produced on this `ConnectionId` should come back to the specified `Recipient`
Connect(Box<ConnectRequest>),
/// Re-use an existing TCP connection, sending more request data up it.
Send(SendRequest),
Send(ConnectionId, Vec<u8>, bool),
}
impl Socks5RequestContent {
impl Request {
/// Construct a new Request::Connect instance
pub fn new_connect(
conn_id: ConnectionId,
remote_addr: RemoteAddress,
return_address: Option<Recipient>,
) -> Socks5RequestContent {
Socks5RequestContent::Connect(Box::new(ConnectRequest {
) -> Request {
Request::Connect(Box::new(ConnectRequest {
conn_id,
remote_addr,
return_address,
@@ -196,16 +93,8 @@ impl Socks5RequestContent {
}
/// Construct a new Request::Send instance
pub fn new_send(
conn_id: ConnectionId,
data: Vec<u8>,
local_closed: bool,
) -> Socks5RequestContent {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
})
pub fn new_send(conn_id: ConnectionId, data: Vec<u8>, local_closed: bool) -> Request {
Request::Send(conn_id, data, local_closed)
}
/// Deserialize the request type, connection id, destination address and port,
@@ -222,23 +111,23 @@ impl Socks5RequestContent {
/// The request_flag tells us whether this is a new connection request (`new_connect`),
/// an already-established connection we should send up (`new_send`), or
/// a request to close an established connection (`new_close`).
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
pub fn try_from_bytes(b: &[u8]) -> Result<Request, RequestError> {
// each request needs to at least contain flag and ConnectionId
if b.is_empty() {
return Err(RequestDeserializationError::NoData);
return Err(RequestError::NoData);
}
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
return Err(RequestError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
let connection_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
match RequestFlag::try_from(b[0])? {
RequestFlag::Connect => {
let connect_request_bytes = &b[9..];
// we need to be able to read at least 2 bytes that specify address length
if connect_request_bytes.len() < 2 {
return Err(RequestDeserializationError::AddressLengthTooShort);
return Err(RequestError::AddressLengthTooShort);
}
let address_length =
@@ -246,7 +135,7 @@ impl Socks5RequestContent {
as usize;
if connect_request_bytes.len() < 2 + address_length {
return Err(RequestDeserializationError::AddressTooShort);
return Err(RequestError::AddressTooShort);
}
let address_start = 2;
@@ -261,19 +150,19 @@ impl Socks5RequestContent {
None
} else {
if recipient_data_bytes.len() != Recipient::LEN {
return Err(RequestDeserializationError::ReturnAddressTooShort);
return Err(RequestError::ReturnAddressTooShort);
}
let mut return_bytes = [0u8; Recipient::LEN];
return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]);
Some(
Recipient::try_from_bytes(return_bytes)
.map_err(RequestDeserializationError::MalformedReturnAddress)?,
.map_err(RequestError::MalformedReturnAddress)?,
)
};
Ok(Socks5RequestContent::new_connect(
conn_id,
Ok(Request::new_connect(
connection_id,
remote_address,
return_address,
))
@@ -282,11 +171,7 @@ impl Socks5RequestContent {
let local_closed = b[9] != 0;
let data = b[10..].to_vec();
Ok(Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}))
Ok(Request::Send(connection_id, data, local_closed))
}
}
}
@@ -297,7 +182,7 @@ impl Socks5RequestContent {
pub fn into_bytes(self) -> Vec<u8> {
match self {
// connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN
Socks5RequestContent::Connect(req) => {
Request::Connect(req) => {
let remote_address_bytes = req.remote_addr.into_bytes();
let remote_address_bytes_len = remote_address_bytes.len() as u16;
@@ -312,10 +197,10 @@ impl Socks5RequestContent {
iter.collect()
}
}
Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8)
.chain(req.conn_id.to_be_bytes().into_iter())
.chain(std::iter::once(req.local_closed as u8))
.chain(req.data.into_iter())
Request::Send(conn_id, data, local_closed) => std::iter::once(RequestFlag::Send as u8)
.chain(conn_id.to_be_bytes().into_iter())
.chain(std::iter::once(local_closed as u8))
.chain(data.into_iter())
.collect(),
}
}
@@ -331,8 +216,8 @@ mod request_deserialization_tests {
#[test]
fn returns_error_when_zero_bytes() {
let request_bytes = Vec::new();
match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestDeserializationError::NoData => {}
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::NoData => {}
_ => unreachable!(),
}
}
@@ -340,8 +225,8 @@ mod request_deserialization_tests {
#[test]
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
match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestDeserializationError::ConnectionIdTooShort => {}
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::ConnectionIdTooShort => {}
_ => unreachable!(),
}
}
@@ -356,13 +241,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_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 Socks5RequestContent::try_from_bytes(&request_bytes1).unwrap_err() {
RequestDeserializationError::AddressLengthTooShort => {}
match Request::try_from_bytes(&request_bytes1).unwrap_err() {
RequestError::AddressLengthTooShort => {}
_ => unreachable!(),
}
match Socks5RequestContent::try_from_bytes(&request_bytes2).unwrap_err() {
RequestDeserializationError::AddressLengthTooShort => {}
match Request::try_from_bytes(&request_bytes2).unwrap_err() {
RequestError::AddressLengthTooShort => {}
_ => unreachable!(),
}
}
@@ -370,8 +255,8 @@ mod request_deserialization_tests {
#[test]
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
match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestDeserializationError::AddressTooShort => {}
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::AddressTooShort => {}
_ => unreachable!(),
}
}
@@ -410,8 +295,8 @@ mod request_deserialization_tests {
.chain(recipient_bytes.iter().take(40).cloned())
.collect();
match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() {
RequestDeserializationError::ReturnAddressTooShort => {}
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::ReturnAddressTooShort => {}
_ => unreachable!(),
}
}
@@ -453,7 +338,7 @@ mod request_deserialization_tests {
.cloned()
.chain(recipient_bytes.into_iter())
.collect();
assert!(Socks5RequestContent::try_from_bytes(&request_bytes)
assert!(Request::try_from_bytes(&request_bytes)
.unwrap_err()
.is_malformed_return());
}
@@ -491,9 +376,9 @@ mod request_deserialization_tests {
.chain(recipient_bytes.into_iter())
.collect();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Connect(req) => {
Request::Connect(req) => {
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!(
@@ -538,9 +423,9 @@ mod request_deserialization_tests {
.chain(recipient_bytes.into_iter())
.collect();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Connect(req) => {
Request::Connect(req) => {
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!(
@@ -561,13 +446,9 @@ mod request_deserialization_tests {
fn works_when_request_is_sized_properly_even_without_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 0 bytes request data
let request_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}) => {
Request::Send(conn_id, data, local_closed) => {
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(Vec::<u8>::new(), data);
assert!(!local_closed)
@@ -596,13 +477,9 @@ mod request_deserialization_tests {
]
.to_vec();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}) => {
Request::Send(conn_id, data, local_closed) => {
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(vec![255, 255, 255], data);
assert!(!local_closed)
+52 -345
View File
@@ -1,239 +1,46 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError};
use service_providers_common::interface::{Serializable, ServiceProviderResponse};
use thiserror::Error;
// 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 }),
}
}
}
use crate::ConnectionId;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ResponseDeserializationError {
pub enum ResponseError {
#[error("not enough bytes to recover the connection id")]
ConnectionIdTooShort,
#[error("{value} is not a valid response flag")]
UnknownResponseFlag { value: u8 },
#[error("no data provided")]
NoData,
#[error("message is not utf8 encoded: {source}")]
MalformedErrorMessage {
#[from]
source: std::string::FromUtf8Error,
},
}
#[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
/// A remote network response retrieved by the Socks5 service provider. This
/// can be serialized and sent back through the mixnet to the requesting
/// application.
#[derive(Debug)]
pub struct NetworkData {
pub struct Response {
pub data: Vec<u8>,
pub connection_id: ConnectionId,
pub is_closed: bool,
}
impl NetworkData {
impl Response {
/// Constructor for responses
pub fn new(connection_id: ConnectionId, data: Vec<u8>, is_closed: bool) -> Self {
NetworkData {
Response {
data,
connection_id,
is_closed,
}
}
pub fn new_closed_empty(connection_id: ConnectionId) -> Self {
NetworkData {
data: vec![],
connection_id,
is_closed: false,
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<NetworkData, ResponseDeserializationError> {
pub fn try_from_bytes(b: &[u8]) -> Result<Response, ResponseError> {
if b.is_empty() {
return Err(ResponseDeserializationError::NoData);
return Err(ResponseError::NoData);
}
let is_closed = b[0] != 0;
if b.len() < 9 {
return Err(ResponseDeserializationError::ConnectionIdTooShort);
return Err(ResponseError::ConnectionIdTooShort);
}
let mut connection_id_bytes = b.to_vec();
@@ -250,7 +57,7 @@ impl NetworkData {
connection_id_bytes[8],
]);
let response = NetworkData::new(connection_id, data, is_closed);
let response = Response::new(connection_id, data, is_closed);
Ok(response)
}
@@ -264,154 +71,54 @@ impl NetworkData {
}
}
#[derive(Debug)]
pub struct ConnectionError {
pub connection_id: ConnectionId,
pub network_requester_error: String,
}
impl ConnectionError {
pub fn new(connection_id: ConnectionId, network_requester_error: String) -> Self {
ConnectionError {
connection_id,
network_requester_error,
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<ConnectionError, ResponseDeserializationError> {
if b.is_empty() {
return Err(ResponseDeserializationError::NoData);
}
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,
})
}
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 tests {
mod constructing_socks5_responses_from_bytes {
use super::*;
#[cfg(test)]
mod constructing_socks5_data_responses_from_bytes {
use super::*;
#[test]
fn fails_when_zero_bytes_are_supplied() {
let response_bytes = Vec::new();
#[test]
fn fails_when_zero_bytes_are_supplied() {
let response_bytes = Vec::new();
assert_eq!(
ResponseDeserializationError::NoData,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[test]
fn fails_when_connection_id_bytes_are_too_short() {
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6];
assert_eq!(
ResponseDeserializationError::ConnectionIdTooShort,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[test]
fn works_when_there_is_no_data() {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7];
let expected = NetworkData::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
Vec::new(),
false,
);
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
assert_eq!(expected.connection_id, actual.connection_id);
assert_eq!(expected.data, actual.data);
assert_eq!(expected.is_closed, actual.is_closed);
}
#[test]
fn works_when_there_is_data() {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255];
let expected = NetworkData::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
vec![255, 255, 255],
false,
);
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
assert_eq!(expected.connection_id, actual.connection_id);
assert_eq!(expected.data, actual.data);
assert_eq!(expected.is_closed, actual.is_closed);
}
assert_eq!(
ResponseError::NoData,
Response::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[cfg(test)]
mod connection_error_response_serde_tests {
use super::*;
#[test]
fn fails_when_connection_id_bytes_are_too_short() {
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6];
assert_eq!(
ResponseError::ConnectionIdTooShort,
Response::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[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();
#[test]
fn works_when_there_is_no_data() {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7];
let expected = Response::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
Vec::new(),
false,
);
let actual = Response::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);
}
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 { .. }
));
}
#[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 = Response::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
vec![255, 255, 255],
false,
);
let actual = Response::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);
}
}
-21
View File
@@ -1,21 +0,0 @@
// 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.
+9 -17
View File
@@ -7,9 +7,7 @@ use crate::dealers::queries::{
use crate::dealers::transactions::try_add_dealer;
use crate::dealings::queries::query_dealings_paged;
use crate::dealings::transactions::try_commit_dealings;
use crate::epoch_state::queries::{
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
};
use crate::epoch_state::queries::{query_current_epoch, query_current_epoch_threshold};
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold};
use crate::error::ContractError;
@@ -77,17 +75,15 @@ pub fn execute(
ExecuteMsg::RegisterDealer {
bte_key_with_proof,
announce_address,
resharing,
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing),
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)
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address),
ExecuteMsg::CommitDealing { dealing_bytes } => {
try_commit_dealings(deps, info, dealing_bytes)
}
ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => {
try_verify_verification_key_share(deps, info, owner, resharing)
ExecuteMsg::CommitVerificationKeyShare { share } => {
try_commit_verification_key_share(deps, env, info, share)
}
ExecuteMsg::VerifyVerificationKeyShare { owner } => {
try_verify_verification_key_share(deps, info, owner)
}
ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env),
ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env),
@@ -101,7 +97,6 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
QueryMsg::GetCurrentEpochThreshold {} => {
to_binary(&query_current_epoch_threshold(deps.storage)?)?
}
QueryMsg::GetInitialDealers {} => to_binary(&query_initial_dealers(deps.storage)?)?,
QueryMsg::GetDealerDetails { dealer_address } => {
to_binary(&query_dealer_details(deps, dealer_address)?)?
}
@@ -243,7 +238,6 @@ mod tests {
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&vec![],
)
@@ -257,7 +251,6 @@ mod tests {
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&vec![],
)
@@ -273,7 +266,6 @@ mod tests {
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&vec![],
)
@@ -2,33 +2,26 @@
// SPDX-License-Identifier: Apache-2.0
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::error::ContractError;
use crate::state::STATE;
use crate::state::{State, STATE};
use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
// currently we only require that
// a) it's part of the signer group
// b) it isn't already a dealer
fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<(), ContractError> {
fn verify_dealer(deps: DepsMut<'_>, state: &State, dealer: &Addr) -> Result<(), ContractError> {
if dealers_storage::current_dealers()
.may_load(deps.storage, dealer)?
.is_some()
{
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
.group_addr
.is_voting_member(&deps.querier, dealer, height)?
.is_voting_member(&deps.querier, dealer, None)?
.ok_or(ContractError::Unauthorized {})?;
Ok(())
@@ -39,11 +32,11 @@ pub fn try_add_dealer(
info: MessageInfo,
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
announce_address: String,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?;
check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?;
let state = STATE.load(deps.storage)?;
verify_dealer(deps.branch(), &info.sender, resharing)?;
verify_dealer(deps.branch(), &state, &info.sender)?;
// if it was already a dealer in the past, assign the same node index
let node_index = if let Some(prior_details) =
@@ -76,63 +69,10 @@ pub fn try_add_dealer(
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::dealers::storage::current_dealers;
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::GROUP_MEMBERS;
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use coconut_dkg_common::types::TimeConfiguration;
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]
fn invalid_state() {
@@ -154,13 +94,12 @@ pub(crate) mod tests {
info.clone(),
bte_key_with_proof.clone(),
announce_address.clone(),
false,
)
.unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::DealingExchange { resharing: false }.to_string(),
current_state: EpochState::DealingExchange.to_string(),
expected_state: EpochState::default().to_string(),
}
);
@@ -3,7 +3,6 @@
use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use coconut_dkg_common::types::{ContractSafeBytes, EpochState};
@@ -13,9 +12,8 @@ pub fn try_commit_dealings(
deps: DepsMut<'_>,
info: MessageInfo,
dealing_bytes: ContractSafeBytes,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?;
check_epoch_state(deps.storage, EpochState::DealingExchange)?;
// ensure the sender is a dealer
if dealers_storage::current_dealers()
.may_load(deps.storage, &info.sender)?
@@ -23,14 +21,6 @@ pub fn try_commit_dealings(
{
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
// (we don't want to allow overwriting anything)
@@ -49,30 +39,29 @@ pub fn try_commit_dealings(
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture};
use crate::support::tests::fixtures::dealing_bytes_fixture;
use crate::support::tests::helpers;
use coconut_dkg_common::dealer::DealerDetails;
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use coconut_dkg_common::types::TimeConfiguration;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
#[test]
fn invalid_commit_dealing() {
let mut deps = helpers::init_contract();
let owner = Addr::unchecked("owner1");
let owner = Addr::unchecked("owner");
let mut env = mock_env();
let info = mock_info(owner.as_str(), &[]);
let dealing_bytes = dealing_bytes_fixture();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
.unwrap_err();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
expected_state: EpochState::DealingExchange { resharing: false }.to_string()
expected_state: EpochState::DealingExchange.to_string()
}
);
@@ -82,8 +71,8 @@ pub(crate) mod tests {
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
.unwrap_err();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err();
assert_eq!(ret, ContractError::NotADealer);
let dealer_details = DealerDetails {
@@ -96,41 +85,14 @@ pub(crate) mod tests {
.save(deps.as_mut().storage, &owner, &dealer_details)
.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 {
assert!(!dealings.has(deps.as_mut().storage, &owner));
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true);
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone());
assert!(ret.is_ok());
assert!(dealings.has(deps.as_mut().storage, &owner));
}
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
.unwrap_err();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err();
assert_eq!(
ret,
ContractError::AlreadyCommitted {
@@ -1,9 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
use crate::error::ContractError;
use coconut_dkg_common::types::{Epoch, InitialReplacementData};
use coconut_dkg_common::types::Epoch;
use cosmwasm_std::Storage;
pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
@@ -18,12 +18,6 @@ pub(crate) fn query_current_epoch_threshold(
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)]
pub(crate) mod test {
use super::*;
@@ -35,10 +29,7 @@ pub(crate) mod test {
fn query_state() {
let mut deps = init_contract();
let epoch = query_current_epoch(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
assert_eq!(epoch.state, EpochState::PublicKeySubmission);
assert_eq!(
epoch.finish_timestamp,
mock_env()
@@ -1,10 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::{Epoch, InitialReplacementData};
use coconut_dkg_common::types::Epoch;
use cw_storage_plus::Item;
pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch");
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::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
use coconut_dkg_common::types::{Epoch, EpochState};
use cosmwasm_std::{DepsMut, Env, Order, Response, Storage};
fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
THRESHOLD.remove(storage);
@@ -27,13 +27,13 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
Ok(())
}
fn dealers_still_active(
deps: &Deps<'_>,
dealers: impl Iterator<Item = Addr>,
) -> Result<usize, ContractError> {
fn dealers_still_active(deps: &DepsMut<'_>) -> Result<usize, ContractError> {
let state = STATE.load(deps.storage)?;
let mut still_active = 0;
for dealer_addr in dealers {
for dealer_addr in current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten()
{
if state
.group_addr
.is_voting_member(&deps.querier, &dealer_addr, None)?
@@ -45,36 +45,6 @@ fn dealers_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> {
let epoch = CURRENT_EPOCH.load(deps.storage)?;
if epoch.finish_timestamp > env.block.time {
@@ -89,20 +59,13 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
let next_epoch = if let Some(state) = current_epoch.state.next() {
// We are during DKG process
if let EpochState::DealingExchange { resharing } = state {
let current_dealers = current_dealers()
if state == EpochState::DealingExchange {
let current_dealer_count = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.collect::<Result<Vec<Addr>, _>>()?;
.count();
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
let threshold = (2 * current_dealer_count as u64 + 3 - 1) / 3;
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(
state,
@@ -110,9 +73,14 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
current_epoch.time_configuration,
env.block.time,
)
} else if dealers_eq_members(&deps)? {
} else if dealers_still_active(&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 epoch remains the same, as we use it as key for storing VKs
Epoch::new(
current_epoch.state,
current_epoch.epoch_id,
@@ -120,21 +88,10 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
env.block.time,
)
} else {
// 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 }
};
// Dealer set changed, we need to redo DKG from scratch
reset_epoch_state(deps.storage)?;
Epoch::new(
state,
EpochState::default(),
current_epoch.epoch_id + 1,
current_epoch.time_configuration,
env.block.time,
@@ -152,10 +109,7 @@ pub(crate) fn try_surpassed_threshold(
check_epoch_state(deps.storage, EpochState::InProgress)?;
let threshold = THRESHOLD.load(deps.storage)?;
let dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten();
if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize {
if dealers_still_active(&deps)? < threshold as usize {
reset_epoch_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new(
@@ -185,137 +139,27 @@ pub(crate) mod tests {
use rusty_fork::rusty_fork_test;
// Because of the global variable handling group, we need individual process for each 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]
fn still_active() {
let mut deps = init_contract();
{
let mut group = GROUP_MEMBERS.lock().unwrap();
group.push((
Member {
addr: "owner1".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner2".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner3".to_string(),
weight: 10,
},
1,
));
group.push(Member {
addr: "owner1".to_string(),
weight: 10,
});
group.push(Member {
addr: "owner2".to_string(),
weight: 10,
});
group.push(Member {
addr: "owner3".to_string(),
weight: 10,
});
}
assert_eq!(
0,
dealers_still_active(
&deps.as_ref(),
current_dealers()
.keys(&deps.storage, None, None, Order::Ascending)
.flatten()
)
.unwrap()
);
assert_eq!(0, dealers_still_active(&deps.as_mut()).unwrap());
for i in 0..3 as u64 {
let details = dealer_details_fixture(i + 1);
current_dealers()
@@ -323,13 +167,7 @@ pub(crate) mod tests {
.unwrap();
assert_eq!(
i as usize + 1,
dealers_still_active(
&deps.as_ref(),
current_dealers()
.keys(&deps.storage, None, None, Order::Ascending)
.flatten()
)
.unwrap()
dealers_still_active(&deps.as_mut()).unwrap()
);
}
}
@@ -341,41 +179,22 @@ pub(crate) mod tests {
{
let mut group = GROUP_MEMBERS.lock().unwrap();
group.push((
Member {
addr: "owner1".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner2".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner3".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner4".to_string(),
weight: 10,
},
1,
));
group.push(Member {
addr: "owner1".to_string(),
weight: 10,
});
group.push(Member {
addr: "owner2".to_string(),
weight: 10,
});
group.push(Member {
addr: "owner3".to_string(),
weight: 10,
});
}
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
assert_eq!(epoch.state, EpochState::PublicKeySubmission);
assert_eq!(
epoch.finish_timestamp,
env.block
@@ -392,37 +211,16 @@ pub(crate) mod tests {
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);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::DealingExchange { resharing: false }
);
assert_eq!(epoch.state, EpochState::DealingExchange);
assert_eq!(
epoch.finish_timestamp,
env.block
.time
.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
.block
@@ -436,10 +234,7 @@ pub(crate) mod tests {
env.block.time = env.block.time.plus_seconds(3);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::VerificationKeySubmission { resharing: false }
);
assert_eq!(epoch.state, EpochState::VerificationKeySubmission);
assert_eq!(
epoch.finish_timestamp,
env.block.time.plus_seconds(
@@ -463,10 +258,7 @@ pub(crate) mod tests {
env.block.time = env.block.time.plus_seconds(3);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::VerificationKeyValidation { resharing: false }
);
assert_eq!(epoch.state, EpochState::VerificationKeyValidation);
assert_eq!(
epoch.finish_timestamp,
env.block.time.plus_seconds(
@@ -490,10 +282,7 @@ pub(crate) mod tests {
env.block.time = env.block.time.plus_seconds(3);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::VerificationKeyFinalization { resharing: false }
);
assert_eq!(epoch.state, EpochState::VerificationKeyFinalization);
assert_eq!(
epoch.finish_timestamp,
env.block.time.plus_seconds(
@@ -538,6 +327,14 @@ pub(crate) mod tests {
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
env.block.time = env.block.time.plus_seconds(100);
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
@@ -551,14 +348,11 @@ pub(crate) mod tests {
);
assert_eq!(curr_epoch, expected_epoch);
// Group changed slightly, so re-run dkg in reshare mode
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = (
Member {
addr: "owner5".to_string(),
weight: 10,
},
1,
);
// Group changed, slightly, so reset dkg state
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = Member {
addr: "owner4".to_string(),
weight: 10,
};
env.block.time = env
.block
.time
@@ -567,154 +361,98 @@ pub(crate) mod tests {
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: true },
EpochState::default(),
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();
#[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();
group.push(Member {
addr: "owner1".to_string(),
weight: 10,
});
group.push(Member {
addr: "owner2".to_string(),
weight: 10,
});
group.push(Member {
addr: "owner3".to_string(),
weight: 10,
});
}
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));
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.as_mut().storage).unwrap();
let expected_epoch = Epoch::new(
EpochState::PublicKeySubmission { resharing: false },
prev_epoch.epoch_id + 1,
prev_epoch.time_configuration,
}
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,
);
assert_eq!(curr_epoch, expected_epoch);
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();
group.push((
Member {
addr: "owner1".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner2".to_string(),
weight: 10,
},
1,
));
group.push((
Member {
addr: "owner3".to_string(),
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));
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,
)
);
}
)
);
}
}
#[test]
-3
View File
@@ -40,9 +40,6 @@ pub enum ContractError {
#[error("This sender is not a dealer for the current epoch")]
NotADealer,
#[error("This sender is not a dealer for the current resharing epoch")]
NotAnInitialDealer,
#[error("This dealer has already committed {commitment}")]
AlreadyCommitted { commitment: String },
@@ -19,7 +19,7 @@ pub const GROUP_CONTRACT: &str = "group contract address";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
lazy_static! {
pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
pub static ref GROUP_MEMBERS: Mutex<Vec<Member>> = Mutex::new(vec![]);
}
fn querier_handler(query: &WasmQuery) -> QuerierResult {
@@ -29,14 +29,9 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
panic!("Not supported");
}
match from_binary(msg) {
Ok(Cw4QueryMsg::Member { addr, at_height }) => {
let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| {
Ok(Cw4QueryMsg::Member { addr, .. }) => {
let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|m| {
if m.addr == addr {
if let Some(height) = at_height {
if height != *h {
return None;
}
}
Some(m.weight)
} else {
None
@@ -45,12 +40,7 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
to_binary(&MemberResponse { weight }).unwrap()
}
Ok(Cw4QueryMsg::ListMembers { .. }) => {
let members = GROUP_MEMBERS
.lock()
.unwrap()
.iter()
.map(|m| m.0.clone())
.collect();
let members = GROUP_MEMBERS.lock().unwrap().to_vec();
to_binary(&MemberListResponse { members }).unwrap()
}
_ => panic!("Not supported"),
@@ -17,12 +17,8 @@ pub fn try_commit_verification_key_share(
env: Env,
info: MessageInfo,
share: VerificationKeyShare,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(
deps.storage,
EpochState::VerificationKeySubmission { resharing },
)?;
check_epoch_state(deps.storage, EpochState::VerificationKeySubmission)?;
// ensure the sender is a dealer
let details = dealers_storage::current_dealers()
.load(deps.storage, &info.sender)
@@ -49,7 +45,6 @@ pub fn try_commit_verification_key_share(
let msg = to_cosmos_msg(
info.sender,
resharing,
env.contract.address.to_string(),
STATE.load(deps.storage)?.multisig_addr.to_string(),
env.block
@@ -64,12 +59,8 @@ pub fn try_verify_verification_key_share(
deps: DepsMut<'_>,
info: MessageInfo,
owner: Addr,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(
deps.storage,
EpochState::VerificationKeyFinalization { resharing },
)?;
check_epoch_state(deps.storage, EpochState::VerificationKeyFinalization)?;
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
MULTISIG.assert_admin(deps.as_ref(), &info.sender)?;
vk_shares().update(deps.storage, (&owner, epoch_id), |vk_share| {
@@ -126,14 +117,8 @@ mod tests {
.save(deps.as_mut().storage, &dealer, &dealer_details)
.unwrap();
try_commit_verification_key_share(
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone())
.unwrap();
let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap();
assert_eq!(
vk_share,
@@ -160,15 +145,13 @@ mod tests {
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
expected_state: EpochState::VerificationKeySubmission { resharing: false }
.to_string()
expected_state: EpochState::VerificationKeySubmission.to_string()
}
);
env.block.time = env
@@ -186,7 +169,6 @@ mod tests {
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap_err();
assert_eq!(ret, ContractError::NotADealer);
@@ -202,21 +184,14 @@ mod tests {
.save(deps.as_mut().storage, &dealer, &dealer_details)
.unwrap();
try_commit_verification_key_share(
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone())
.unwrap();
let ret = try_commit_verification_key_share(
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap_err();
assert_eq!(
@@ -235,15 +210,13 @@ mod tests {
let owner = Addr::unchecked("owner");
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
let ret =
try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false)
.unwrap_err();
let ret = try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone())
.unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
expected_state: EpochState::VerificationKeyFinalization { resharing: false }
.to_string()
expected_state: EpochState::VerificationKeyFinalization.to_string()
}
);
@@ -268,13 +241,12 @@ mod tests {
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false)
.unwrap_err();
let ret =
try_verify_verification_key_share(deps.as_mut(), info, owner.clone()).unwrap_err();
assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {}));
let ret =
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
.unwrap_err();
let ret = try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone())
.unwrap_err();
assert_eq!(
ret,
ContractError::NoCommitForOwner {
@@ -312,14 +284,8 @@ mod tests {
dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details)
.unwrap();
try_commit_verification_key_share(
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone())
.unwrap();
env.block.time = env
.block
@@ -332,7 +298,6 @@ mod tests {
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
.unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()).unwrap();
}
}
@@ -103,7 +103,6 @@ fn dkg_proposal() {
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&vec![],
)
@@ -125,7 +124,6 @@ fn dkg_proposal() {
let msg = CommitVerificationKeyShare {
share: "share".to_string(),
resharing: false,
};
let res = app
.execute_contract(
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.8"
version = "1.1.9"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+10
View File
@@ -1,5 +1,15 @@
## UNRELEASED
- nothing yet
## [nym-explorer-v1.0.5](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.5) (2023-02-14)
- NE - link `Owner` field on the node detail page to the account details on NG explorer ([#2923])
- NE - Upgrade Sandbox and make below changes: ([#2332])
[#2923]: https://github.com/nymtech/nym/issues/2923
[#2332]: https://github.com/nymtech/nym/issues/2332
## [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])
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/network-explorer",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"license": "Apache-2.0",
"dependencies": {
+11 -1
View File
@@ -1,5 +1,5 @@
import * as React from 'react';
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
@@ -37,9 +37,19 @@ function formatCellValues(val: string | number, field: string) {
</Box>
);
}
if (field === 'bond') {
return unymToNym(val, 6);
}
if (field === 'owner') {
return (
<Link underline="none" color="inherit" target="_blank" href={`https://mixnet.explorers.guru/account/${val}`}>
{val}
</Link>
);
}
return val;
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.8"
version = "1.1.9"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -181,6 +181,7 @@ impl<St: Storage> ConnectionHandler<St> {
mut shutdown: TaskClient,
) {
debug!("Starting connection handler for {:?}", remote);
shutdown.mark_as_success();
let mut framed_conn = Framed::new(conn, SphinxCodec);
while !shutdown.is_shutdown() {
tokio::select! {
+3 -2
View File
@@ -4,7 +4,8 @@
"nym-wallet",
"nym-connect",
"nym-connect-android",
"sdk/typescript/**"
"sdk/typescript/examples/docs",
"sdk/typescript/packages/**"
],
"version": "0.0.0"
}
}
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.9"
version = "1.1.10"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -77,6 +77,7 @@ impl ConnectionHandler {
mut shutdown: TaskClient,
) {
debug!("Starting connection handler for {:?}", remote);
shutdown.mark_as_success();
let mut framed_conn = Framed::new(conn, SphinxCodec);
while !shutdown.is_shutdown() {
tokio::select! {
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-api"
version = "1.1.8"
version = "1.1.10"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+5 -1
View File
@@ -15,7 +15,11 @@ pub(crate) mod routes;
/// Merges the routes with http information and returns it to Rocket for serving
pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: routes::get_circulating_supply]
openapi_get_routes_spec![
settings: routes::get_full_circulating_supply,
routes::get_total_supply,
routes::get_circulating_supply
]
}
/// Spawn the circulating supply cache refresher.
+59 -4
View File
@@ -1,15 +1,30 @@
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
use crate::node_status_api::models::ErrorResponse;
use nym_api_requests::models::CirculatingSupplyResponse;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
use validator_client::nyxd::Coin;
// TODO: this is not the best place to put it, it should be more centralised,
// but for a quick fix, that's good enough for now...
// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent
// value from the mix denom here.
const UNYM_RATIO: f64 = 1000000.;
fn unym_coin_to_float_unym(coin: Coin) -> f64 {
// our total supply can't exceed 1B so an overflow here is impossible
// (if it happened, then we SHOULD crash)
coin.amount as f64 / UNYM_RATIO
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply")]
pub(crate) async fn get_circulating_supply(
pub(crate) async fn get_full_circulating_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<CirculatingSupplyResponse>, ErrorResponse> {
match cache.get_circulating_supply().await {
@@ -20,3 +35,43 @@ pub(crate) async fn get_circulating_supply(
)),
}
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply/total-supply-value")]
pub(crate) async fn get_total_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<f64>, ErrorResponse> {
let full_circulating_supply = match cache.get_circulating_supply().await {
Some(res) => res,
None => {
return Err(ErrorResponse::new(
"unavailable",
Status::InternalServerError,
))
}
};
Ok(Json(unym_coin_to_float_unym(
full_circulating_supply.total_supply.into(),
)))
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply/circulating-supply-value")]
pub(crate) async fn get_circulating_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<f64>, ErrorResponse> {
let full_circulating_supply = match cache.get_circulating_supply().await {
Some(res) => res,
None => {
return Err(ErrorResponse::new(
"unavailable",
Status::InternalServerError,
))
}
};
Ok(Json(unym_coin_to_float_unym(
full_circulating_supply.circulating_supply.into(),
)))
}
+2 -11
View File
@@ -4,9 +4,7 @@
use crate::coconut::error::Result;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId};
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use contracts_common::dealings::ContractSafeBytes;
use cw3::ProposalResponse;
@@ -28,7 +26,6 @@ pub trait Client {
async fn get_current_epoch(&self) -> Result<Epoch>;
async fn group_member(&self, addr: String) -> Result<MemberResponse>;
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>>;
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>>;
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse>;
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>>;
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>>;
@@ -41,16 +38,10 @@ pub trait Client {
&self,
bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String,
resharing: bool,
) -> Result<ExecuteResult>;
async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
resharing: bool,
) -> Result<ExecuteResult>;
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult>;
async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
resharing: bool,
) -> Result<ExecuteResult>;
}
+5 -16
View File
@@ -4,9 +4,7 @@
use crate::coconut::client::Client;
use crate::coconut::error::CoconutError;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, NodeIndex,
};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId, NodeIndex};
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use contracts_common::dealings::ContractSafeBytes;
use cw3::ProposalResponse;
@@ -61,12 +59,6 @@ impl DkgClient {
self.inner.get_current_epoch_threshold().await
}
pub(crate) async fn get_initial_dealers(
&self,
) -> Result<Option<InitialReplacementData>, CoconutError> {
self.inner.get_initial_dealers().await
}
pub(crate) async fn get_self_registered_dealer_details(
&self,
) -> Result<DealerDetailsResponse, CoconutError> {
@@ -110,11 +102,10 @@ impl DkgClient {
&self,
bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String,
resharing: bool,
) -> Result<NodeIndex, CoconutError> {
let res = self
.inner
.register_dealer(bte_key, announce_address, resharing)
.register_dealer(bte_key, announce_address)
.await?;
let node_index = find_attribute(&res.logs, "wasm", NODE_INDEX)
.ok_or(CoconutError::NodeIndexRecoveryError {
@@ -132,20 +123,18 @@ impl DkgClient {
pub(crate) async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
resharing: bool,
) -> Result<(), CoconutError> {
self.inner.submit_dealing(dealing_bytes, resharing).await?;
self.inner.submit_dealing(dealing_bytes).await?;
Ok(())
}
pub(crate) async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
resharing: bool,
) -> Result<ExecuteResult, CoconutError> {
let mut ret = self
.inner
.submit_verification_key_share(share.clone(), resharing)
.submit_verification_key_share(share.clone())
.await;
for _ in 0..Self::RETRIES {
if let Ok(res) = ret {
@@ -153,7 +142,7 @@ impl DkgClient {
}
ret = self
.inner
.submit_verification_key_share(share.clone(), resharing)
.submit_verification_key_share(share.clone())
.await;
}
ret
+9 -18
View File
@@ -101,19 +101,13 @@ impl<R: RngCore + Clone> DkgController<R> {
return;
}
let ret = match epoch.state {
EpochState::PublicKeySubmission { resharing } => {
public_key_submission(&self.dkg_client, &mut self.state, resharing).await
EpochState::PublicKeySubmission => {
public_key_submission(&self.dkg_client, &mut self.state).await
}
EpochState::DealingExchange { resharing } => {
dealing_exchange(
&self.dkg_client,
&mut self.state,
self.rng.clone(),
resharing,
)
.await
EpochState::DealingExchange => {
dealing_exchange(&self.dkg_client, &mut self.state, self.rng.clone()).await
}
EpochState::VerificationKeySubmission { resharing } => {
EpochState::VerificationKeySubmission => {
let keypair_path = pemstore::KeyPairPath::new(
self.secret_key_path.clone(),
self.verification_key_path.clone(),
@@ -122,17 +116,14 @@ impl<R: RngCore + Clone> DkgController<R> {
&self.dkg_client,
&mut self.state,
&keypair_path,
resharing,
)
.await
}
EpochState::VerificationKeyValidation { resharing } => {
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
.await
EpochState::VerificationKeyValidation => {
verification_key_validation(&self.dkg_client, &mut self.state).await
}
EpochState::VerificationKeyFinalization { resharing } => {
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
.await
EpochState::VerificationKeyFinalization => {
verification_key_finalization(&self.dkg_client, &mut self.state).await
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => {
+20 -144
View File
@@ -9,13 +9,11 @@ use contracts_common::dealings::ContractSafeBytes;
use dkg::bte::setup;
use dkg::Dealing;
use rand::RngCore;
use std::collections::VecDeque;
pub(crate) async fn dealing_exchange(
dkg_client: &DkgClient,
state: &mut State,
rng: impl RngCore + Clone,
resharing: bool,
) -> Result<(), CoconutError> {
if state.receiver_index().is_some() {
return Ok(());
@@ -23,54 +21,26 @@ pub(crate) async fn dealing_exchange(
let dealers = dkg_client.get_current_dealers().await?;
let threshold = dkg_client.get_current_epoch_threshold().await?;
let initial_dealers = dkg_client
.get_initial_dealers()
.await?
.map(|d| d.initial_dealers)
.unwrap_or_default();
let own_address = dkg_client.get_address().await.as_ref().to_string();
state.set_dealers(dealers);
state.set_threshold(threshold);
let receivers = state.current_dealers_by_idx();
let params = setup();
let dealer_index = state.node_index_value()?;
let receiver_index = receivers
.keys()
.position(|node_index| *node_index == dealer_index);
let prior_resharing_secrets = if let Some(sk) = state.coconut_secret_key().await {
// Double check that we are in resharing mode
if resharing {
let (x, mut scalars) = sk.into_raw();
if scalars.len() + 1 != TOTAL_DEALINGS {
return Err(CoconutError::CorruptedCoconutKeyPair);
}
// We can now erase the keypair from memory
state.set_coconut_keypair(None).await;
scalars.push(x);
scalars
} else {
log::warn!("Coconut key hasn't been reset in memory. The state might be corrupt");
vec![]
}
} else {
vec![]
};
let mut prior_resharing_secrets = VecDeque::from(prior_resharing_secrets);
if !resharing || initial_dealers.iter().any(|d| *d == own_address) {
let params = setup();
for _ in 0..TOTAL_DEALINGS {
let (dealing, _) = Dealing::create(
rng.clone(),
&params,
dealer_index,
state.threshold()?,
&receivers,
prior_resharing_secrets.pop_front(),
);
dkg_client
.submit_dealing(ContractSafeBytes::from(&dealing), resharing)
.await?;
}
for _ in 0..TOTAL_DEALINGS {
let (dealing, _) = Dealing::create(
rng.clone(),
&params,
dealer_index,
state.threshold()?,
&receivers,
None,
);
dkg_client
.submit_dealing(ContractSafeBytes::from(&dealing))
.await?;
}
info!("DKG: Finished submitting dealing");
@@ -87,11 +57,9 @@ pub(crate) mod tests {
use crate::coconut::tests::DummyClient;
use crate::coconut::KeyPair;
use coconut_dkg_common::dealer::DealerDetails;
use coconut_dkg_common::types::InitialReplacementData;
use cosmwasm_std::Addr;
use dkg::bte::keys::KeyPair as DkgKeyPair;
use dkg::bte::{Params, PublicKeyWithProof};
use nymcoconut::{ttp_keygen, Parameters};
use dkg::bte::Params;
use rand::rngs::OsRng;
use std::collections::HashMap;
use std::path::PathBuf;
@@ -100,11 +68,10 @@ pub(crate) mod tests {
use url::Url;
use validator_client::nyxd::AccountId;
const TEST_VALIDATORS_ADDRESS: [&str; 4] = [
const TEST_VALIDATORS_ADDRESS: [&str; 3] = [
"n1aq9kakfgwqcufr23lsv644apavcntrsqsk4yus",
"n1s9l3xr4g0rglvk4yctktmck3h4eq0gp6z2e20v",
"n19kl4py32vsk297dm93ezem992cdyzdy4zuc2x6",
"n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv",
];
fn insert_dealers(
@@ -154,7 +121,7 @@ pub(crate) mod tests {
state.set_node_index(Some(self_index));
let keypairs = insert_dealers(&params, &dealer_details_db);
dealing_exchange(&dkg_client, &mut state, OsRng, false)
dealing_exchange(&dkg_client, &mut state, OsRng)
.await
.unwrap();
@@ -175,7 +142,7 @@ pub(crate) mod tests {
.clone();
assert_eq!(dealings.len(), TOTAL_DEALINGS);
dealing_exchange(&dkg_client, &mut state, OsRng, false)
dealing_exchange(&dkg_client, &mut state, OsRng)
.await
.unwrap();
let new_dealings = dealings_db
@@ -219,22 +186,12 @@ pub(crate) mod tests {
let mut bytes = bs58::decode(details.bte_public_key_with_proof.clone())
.into_vec()
.unwrap();
// Find another value for last byte that still deserializes to a public key with proof
let initial_byte = *bytes.last_mut().unwrap();
loop {
let last_byte = bytes.last_mut().unwrap();
let (ret, _) = last_byte.overflowing_add(1);
*last_byte = ret;
// stop when we find that value, or if we do a full round trip of u8 values
// and can't find one, in which case this test is invalid
if PublicKeyWithProof::try_from_bytes(&bytes).is_ok() || ret == initial_byte {
break;
}
}
let last_byte = bytes.last_mut().unwrap();
*last_byte += 1;
details.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
});
dealing_exchange(&dkg_client, &mut state, OsRng, false)
dealing_exchange(&dkg_client, &mut state, OsRng)
.await
.unwrap();
assert_eq!(
@@ -247,85 +204,4 @@ pub(crate) mod tests {
ComplaintReason::InvalidBTEPublicKey
);
}
#[tokio::test]
#[ignore] // expensive test
async fn resharing_exchange_dealing() {
let self_index = 2;
let dealer_details_db = Arc::new(RwLock::new(HashMap::new()));
let dealings_db = Arc::new(RwLock::new(HashMap::new()));
let threshold_db = Arc::new(RwLock::new(Some(3)));
let initial_dealers_db = Arc::new(RwLock::new(Some(InitialReplacementData {
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
initial_height: Some(100),
})));
let dkg_client = DkgClient::new(
DummyClient::new(
AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").unwrap(),
)
.with_dealer_details(&dealer_details_db)
.with_dealings(&dealings_db)
.with_threshold(&threshold_db)
.with_initial_dealers_db(&initial_dealers_db),
);
let params = setup();
let mut keys = ttp_keygen(&Parameters::new(4).unwrap(), 3, 4).unwrap();
let coconut_keypair = KeyPair::new();
coconut_keypair.set(Some(keys.pop().unwrap())).await;
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
coconut_keypair.clone(),
);
state.set_node_index(Some(self_index));
let keypairs = insert_dealers(&params, &dealer_details_db);
dealing_exchange(&dkg_client, &mut state, OsRng, true)
.await
.unwrap();
assert_eq!(
state.current_dealers_by_idx().values().collect::<Vec<_>>(),
keypairs
.iter()
.map(|k| k.public_key().public_key())
.collect::<Vec<_>>()
);
assert_eq!(state.threshold().unwrap(), 3);
assert_eq!(state.receiver_index().unwrap(), 1);
let addr = dkg_client.get_address().await;
assert!(dealings_db.read().unwrap().get(addr.as_ref()).is_none());
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
coconut_keypair,
);
state.set_node_index(Some(self_index));
// Use a client that is in the initial dealers set
let dkg_client = DkgClient::new(
DummyClient::new(AccountId::from_str(TEST_VALIDATORS_ADDRESS[0]).unwrap())
.with_dealer_details(&dealer_details_db)
.with_dealings(&dealings_db)
.with_threshold(&threshold_db)
.with_initial_dealers_db(&initial_dealers_db),
);
dealing_exchange(&dkg_client, &mut state, OsRng, true)
.await
.unwrap();
let dealings = dealings_db
.read()
.unwrap()
.get(TEST_VALIDATORS_ADDRESS[0])
.unwrap()
.clone();
assert_eq!(dealings.len(), TOTAL_DEALINGS);
}
}
+5 -6
View File
@@ -9,10 +9,9 @@ use coconut_dkg_common::dealer::DealerType;
pub(crate) async fn public_key_submission(
dkg_client: &DkgClient,
state: &mut State,
resharing: bool,
) -> Result<(), CoconutError> {
if state.was_in_progress() {
state.reset_persistent(resharing).await;
state.reset_persistent().await;
}
if state.node_index().is_some() {
return Ok(());
@@ -24,14 +23,14 @@ pub(crate) async fn public_key_submission(
if dealer_details.dealer_type == DealerType::Past {
// If it was a dealer in a previous epoch, re-register it for this epoch
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
.register_dealer(bte_key, state.announce_address().to_string())
.await?;
}
details.assigned_index
} else {
// First time registration
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
.register_dealer(bte_key, state.announce_address().to_string())
.await?
};
state.set_node_index(Some(index));
@@ -75,7 +74,7 @@ pub(crate) mod tests {
.unwrap()
.details
.is_none());
public_key_submission(&dkg_client, &mut state, false)
public_key_submission(&dkg_client, &mut state)
.await
.unwrap();
let client_idx = dkg_client
@@ -89,7 +88,7 @@ pub(crate) mod tests {
// keeps the same index from chain, not calling register_dealer again
state.set_node_index(None);
public_key_submission(&dkg_client, &mut state, false)
public_key_submission(&dkg_client, &mut state)
.await
.unwrap();
assert_eq!(state.node_index().unwrap(), client_idx);
+9 -23
View File
@@ -9,7 +9,6 @@ use coconut_dkg_common::types::EpochState;
use cosmwasm_std::Addr;
use dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
use dkg::{NodeIndex, RecoveredVerificationKeys, Threshold};
use nymcoconut::SecretKey;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeMap;
@@ -72,18 +71,18 @@ pub(crate) trait ConsistentState {
fn proposal_id_value(&self) -> Result<u64, CoconutError>;
async fn is_consistent(&self, epoch_state: EpochState) -> Result<(), CoconutError> {
match epoch_state {
EpochState::PublicKeySubmission { .. } => {}
EpochState::DealingExchange { .. } => {
EpochState::PublicKeySubmission => {}
EpochState::DealingExchange => {
self.node_index_value()?;
}
EpochState::VerificationKeySubmission { .. } => {
EpochState::VerificationKeySubmission => {
self.receiver_index_value()?;
self.threshold()?;
}
EpochState::VerificationKeyValidation { .. } => {
EpochState::VerificationKeyValidation => {
self.coconut_keypair_is_some().await?;
}
EpochState::VerificationKeyFinalization { .. } => {
EpochState::VerificationKeyFinalization => {
self.proposal_id_value()?;
}
EpochState::InProgress => {}
@@ -242,10 +241,8 @@ impl State {
}
}
pub async fn reset_persistent(&mut self, resharing: bool) {
if !resharing {
self.coconut_keypair.set(None).await;
}
pub async fn reset_persistent(&mut self) {
self.coconut_keypair.set(None).await;
self.node_index = Default::default();
self.dealers = Default::default();
self.receiver_index = Default::default();
@@ -273,14 +270,6 @@ impl State {
self.coconut_keypair.get().await.is_some()
}
pub async fn coconut_secret_key(&self) -> Option<SecretKey> {
self.coconut_keypair
.get()
.await
.as_ref()
.map(|kp| kp.secret_key())
}
pub fn node_index(&self) -> Option<NodeIndex> {
self.node_index
}
@@ -335,11 +324,8 @@ impl State {
self.recovered_vks = recovered_vks;
}
pub async fn set_coconut_keypair(
&mut self,
coconut_keypair: Option<coconut_interface::KeyPair>,
) {
self.coconut_keypair.set(coconut_keypair).await
pub async fn set_coconut_keypair(&mut self, coconut_keypair: coconut_interface::KeyPair) {
self.coconut_keypair.set(Some(coconut_keypair)).await
}
pub fn set_node_index(&mut self, node_index: Option<NodeIndex>) {
+44 -243
View File
@@ -14,7 +14,6 @@ use cosmwasm_std::Addr;
use credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
use cw3::{ProposalResponse, Status};
use dkg::bte::{decrypt_share, setup};
use dkg::error::DkgError;
use dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold};
use nymcoconut::tests::helpers::transpose_matrix;
use nymcoconut::{check_vk_pairing, Base58, KeyPair, Parameters, SecretKey, VerificationKey};
@@ -27,21 +26,10 @@ async fn deterministic_filter_dealers(
dkg_client: &DkgClient,
state: &mut State,
threshold: Threshold,
resharing: bool,
) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
let mut dealings_maps = vec![];
let initial_dealers_by_addr = state.current_dealers_by_addr();
let initial_receivers = state.current_dealers_by_idx();
let initial_resharing_dealers = if resharing {
dkg_client
.get_initial_dealers()
.await?
.map(|d| d.initial_dealers)
.unwrap_or_default()
} else {
vec![]
};
let params = setup();
for idx in 0..TOTAL_DEALINGS {
@@ -78,15 +66,11 @@ async fn deterministic_filter_dealers(
}));
dealings_maps.push(dealings_map);
}
for (addr, _) in initial_dealers_by_addr.iter() {
// in resharing mode, we don't commit dealings from dealers outside the initial set
if !resharing || initial_resharing_dealers.contains(addr) {
for dealings_map in dealings_maps.iter() {
if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
break;
}
for dealings_map in dealings_maps.iter() {
if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
break;
}
}
}
@@ -106,16 +90,16 @@ fn derive_partial_keypair(
let mut scalars = vec![];
let mut recovered_vks = vec![];
for dealings_map in dealings_maps.into_iter() {
let (filtered_dealers, filtered_dealings): (Vec<_>, Vec<_>) = dealings_map
let filtered_dealings: Vec<_> = dealings_map
.into_iter()
.filter_map(|(idx, (addr, dealing))| {
.filter_map(|(_, (addr, dealing))| {
if filtered_dealers_by_addr.keys().any(|a| addr == *a) {
Some((idx, dealing))
Some(dealing)
} else {
None
}
})
.unzip();
.collect();
let recovered = try_recover_verification_keys(
&filtered_dealings,
threshold,
@@ -127,18 +111,19 @@ fn derive_partial_keypair(
.iter()
.map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
.collect::<Result<_, _>>()?;
let scalar = combine_shares(shares, &filtered_dealers)?;
let scalar = combine_shares(
shares,
&filtered_receivers_by_idx
.keys()
.copied()
.collect::<Vec<_>>(),
)?;
scalars.push(scalar);
}
state.set_recovered_vks(recovered_vks);
let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?;
let x = scalars.pop().ok_or(CoconutError::DkgError(
DkgError::NotEnoughDealingsAvailable {
available: 0,
required: 1,
},
))?;
let x = scalars.pop().unwrap();
let sk = SecretKey::create_from_raw(x, scalars);
let vk = sk.verification_key(&params);
@@ -149,21 +134,17 @@ pub(crate) async fn verification_key_submission(
dkg_client: &DkgClient,
state: &mut State,
keypair_path: &KeyPairPath,
resharing: bool,
) -> Result<(), CoconutError> {
if state.coconut_keypair_is_some().await {
return Ok(());
}
let threshold = state.threshold()?;
let dealings_maps =
deterministic_filter_dealers(dkg_client, state, threshold, resharing).await?;
let dealings_maps = deterministic_filter_dealers(dkg_client, state, threshold).await?;
let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?;
let vk_share = coconut_keypair.verification_key().to_bs58();
pemstore::store_keypair(&coconut_keypair, keypair_path)?;
let res = dkg_client
.submit_verification_key_share(vk_share, resharing)
.await?;
let res = dkg_client.submit_verification_key_share(vk_share).await?;
let proposal_id = find_attribute(&res.logs, "wasm", DKG_PROPOSAL_ID)
.ok_or(CoconutError::ProposalIdError {
reason: String::from("proposal id not found"),
@@ -174,7 +155,7 @@ pub(crate) async fn verification_key_submission(
reason: String::from("proposal id could not be parsed to u64"),
})?;
state.set_proposal_id(proposal_id);
state.set_coconut_keypair(Some(coconut_keypair)).await;
state.set_coconut_keypair(coconut_keypair).await;
info!("DKG: Submitted own verification key");
Ok(())
@@ -192,7 +173,6 @@ fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> {
pub(crate) async fn verification_key_validation(
dkg_client: &DkgClient,
state: &mut State,
_resharing: bool,
) -> Result<(), CoconutError> {
if state.voted_vks() {
return Ok(());
@@ -253,7 +233,6 @@ pub(crate) async fn verification_key_validation(
pub(crate) async fn verification_key_finalization(
dkg_client: &DkgClient,
state: &mut State,
_resharing: bool,
) -> Result<(), CoconutError> {
if state.executed_proposal() {
return Ok(());
@@ -278,11 +257,9 @@ pub(crate) mod tests {
use crate::coconut::tests::DummyClient;
use crate::coconut::KeyPair;
use coconut_dkg_common::dealer::DealerDetails;
use coconut_dkg_common::types::InitialReplacementData;
use coconut_dkg_common::verification_key::ContractVKShare;
use contracts_common::dealings::ContractSafeBytes;
use dkg::bte::keys::KeyPair as DkgKeyPair;
use nymcoconut::aggregate_verification_keys;
use rand::rngs::OsRng;
use rand::Rng;
use std::collections::HashMap;
@@ -299,7 +276,6 @@ pub(crate) mod tests {
proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>,
verification_share_db: Arc<RwLock<HashMap<String, ContractVKShare>>>,
threshold_db: Arc<RwLock<Option<Threshold>>>,
initial_dealers_db: Arc<RwLock<Option<InitialReplacementData>>>,
}
impl MockContractDb {
@@ -310,7 +286,6 @@ pub(crate) mod tests {
proposal_db: Arc::new(Default::default()),
verification_share_db: Arc::new(Default::default()),
threshold_db: Arc::new(RwLock::new(Some(2))),
initial_dealers_db: Arc::new(RwLock::new(Default::default())),
}
}
}
@@ -332,8 +307,7 @@ pub(crate) mod tests {
.with_dealings(&db.dealings_db)
.with_proposal_db(&db.proposal_db)
.with_verification_share(&db.verification_share_db)
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
.with_threshold(&db.threshold_db),
);
let keypair = DkgKeyPair::new(&params, OsRng);
let state = State::new(
@@ -346,21 +320,10 @@ pub(crate) mod tests {
clients_and_states.push((dkg_client, state));
}
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, false)
.await
.unwrap();
public_key_submission(dkg_client, state).await.unwrap();
}
clients_and_states
}
async fn prepare_clients_and_states_with_dealing(
db: &MockContractDb,
) -> Vec<(DkgClient, State)> {
let mut clients_and_states = prepare_clients_and_states(db).await;
for (dkg_client, state) in clients_and_states.iter_mut() {
dealing_exchange(dkg_client, state, OsRng, false)
.await
.unwrap();
dealing_exchange(dkg_client, state, OsRng).await.unwrap();
}
clients_and_states
}
@@ -368,13 +331,13 @@ pub(crate) mod tests {
async fn prepare_clients_and_states_with_submission(
db: &MockContractDb,
) -> Vec<(DkgClient, State)> {
let mut clients_and_states = prepare_clients_and_states_with_dealing(db).await;
let mut clients_and_states = prepare_clients_and_states(db).await;
for (dkg_client, state) in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, false)
verification_key_submission(dkg_client, state, &keypair_path)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
@@ -388,7 +351,7 @@ pub(crate) mod tests {
) -> Vec<(DkgClient, State)> {
let mut clients_and_states = prepare_clients_and_states_with_submission(db).await;
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, false)
verification_key_validation(dkg_client, state)
.await
.unwrap();
}
@@ -400,7 +363,7 @@ pub(crate) mod tests {
) -> Vec<(DkgClient, State)> {
let mut clients_and_states = prepare_clients_and_states_with_validation(db).await;
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_finalization(dkg_client, state, false)
verification_key_finalization(dkg_client, state)
.await
.unwrap();
}
@@ -411,9 +374,9 @@ pub(crate) mod tests {
#[ignore] // expensive test
async fn check_dealers_filter_all_good() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let mut clients_and_states = prepare_clients_and_states(&db).await;
for (dkg_client, state) in clients_and_states.iter_mut() {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
@@ -427,7 +390,7 @@ pub(crate) mod tests {
#[ignore] // expensive test
async fn check_dealers_filter_one_bad_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let mut clients_and_states = prepare_clients_and_states(&db).await;
// corrupt just one dealing
db.dealings_db
@@ -441,7 +404,7 @@ pub(crate) mod tests {
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
@@ -455,75 +418,11 @@ pub(crate) mod tests {
}
}
#[tokio::test]
#[ignore] // expensive test
async fn check_dealers_resharing_filter_one_missing_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states(&db).await;
// add all but the first dealing
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
dealing_exchange(dkg_client, state, OsRng, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
initial_height: None,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
let corrupted_status = state
.all_dealers()
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
.unwrap()
.as_ref()
.unwrap_err();
assert_eq!(*corrupted_status, ComplaintReason::MissingDealing);
}
}
#[tokio::test]
#[ignore] // expensive test
async fn check_dealers_resharing_filter_one_noninitial_missing_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states(&db).await;
// add all but the first dealing
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
dealing_exchange(dkg_client, state, OsRng, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![],
initial_height: None,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert!(state
.all_dealers()
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
.unwrap()
.as_ref()
.is_ok(),);
}
}
#[tokio::test]
#[ignore] // expensive test
async fn check_dealers_filter_all_bad_dealings() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let mut clients_and_states = prepare_clients_and_states(&db).await;
// corrupt all dealings of one address
db.dealings_db
@@ -537,7 +436,7 @@ pub(crate) mod tests {
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
@@ -558,7 +457,7 @@ pub(crate) mod tests {
#[ignore] // expensive test
async fn check_dealers_filter_malformed_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let mut clients_and_states = prepare_clients_and_states(&db).await;
// corrupt just one dealing
db.dealings_db
@@ -572,12 +471,12 @@ pub(crate) mod tests {
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
deterministic_filter_dealers(dkg_client, state, 2, false)
deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
// second filter will leave behind the bad dealer and surface why it was left out
// in the first place
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
@@ -595,7 +494,7 @@ pub(crate) mod tests {
#[ignore] // expensive test
async fn check_dealers_filter_dealing_verification_error() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let mut clients_and_states = prepare_clients_and_states(&db).await;
// corrupt just one dealing
db.dealings_db
@@ -614,12 +513,12 @@ pub(crate) mod tests {
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
deterministic_filter_dealers(dkg_client, state, 2, false)
deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
// second filter will leave behind the bad dealer and surface why it was left out
// in the first place
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
@@ -637,9 +536,9 @@ pub(crate) mod tests {
#[ignore] // expensive test
async fn partial_keypair_derivation() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let mut clients_and_states = prepare_clients_and_states(&db).await;
for (dkg_client, state) in clients_and_states.iter_mut() {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
assert!(derive_partial_keypair(state, 2, filtered).is_ok());
@@ -650,7 +549,7 @@ pub(crate) mod tests {
#[ignore] // expensive test
async fn partial_keypair_derivation_with_threshold() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let mut clients_and_states = prepare_clients_and_states(&db).await;
// corrupt just one dealing
db.dealings_db
@@ -664,7 +563,7 @@ pub(crate) mod tests {
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
.await
.unwrap();
assert!(derive_partial_keypair(state, 2, filtered).is_ok());
@@ -717,7 +616,7 @@ pub(crate) mod tests {
.and_modify(|share| share.share.push('x'));
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, false)
verification_key_validation(dkg_client, state)
.await
.unwrap();
}
@@ -759,7 +658,7 @@ pub(crate) mod tests {
.and_modify(|share| share.share = second_share);
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, false)
verification_key_validation(dkg_client, state)
.await
.unwrap();
}
@@ -797,102 +696,4 @@ pub(crate) mod tests {
assert_eq!(proposal.status, Status::Executed);
}
}
#[tokio::test]
#[ignore] // expensive test
async fn reshare_preserves_keys() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
let params = Parameters::new(4).unwrap();
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
let vk = state
.coconut_secret_key()
.await
.unwrap()
.verification_key(&params);
let index = state.node_index().unwrap();
vks.push(vk);
indices.push(index);
}
let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
let new_dkg_client = DkgClient::new(
DummyClient::new(
AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(),
)
.with_dealer_details(&db.dealer_details_db)
.with_dealings(&db.dealings_db)
.with_proposal_db(&db.proposal_db)
.with_verification_share(&db.verification_share_db)
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
KeyPair::new(),
);
let removed_dealer = clients_and_states.first().unwrap().0.get_address().await;
db.dealer_details_db
.write()
.unwrap()
.remove(removed_dealer.as_ref());
*db.dealings_db.write().unwrap() = Default::default();
*db.verification_share_db.write().unwrap() = Default::default();
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![
Addr::unchecked(clients_and_states[1].0.get_address().await.as_ref()),
Addr::unchecked(clients_and_states[2].0.get_address().await.as_ref()),
],
initial_height: None,
});
*clients_and_states.first_mut().unwrap() = (new_dkg_client, state);
clients_and_states[1].1.set_was_in_progress();
clients_and_states[2].1.set_was_in_progress();
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
dealing_exchange(dkg_client, state, OsRng, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, true)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
}
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
let vk = state
.coconut_secret_key()
.await
.unwrap()
.verification_key(&params);
let index = state.node_index().unwrap();
vks.push(vk);
indices.push(index);
}
let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
assert_eq!(initial_master_vk, reshared_master_vk);
}
}
-3
View File
@@ -100,9 +100,6 @@ pub enum CoconutError {
#[error("DKG has not finished yet in order to derive the coconut key")]
KeyPairNotDerivedYet,
#[error("The coconut keypair is corrupted")]
CorruptedCoconutKeyPair,
#[error("There was a problem with the proposal id: {reason}")]
ProposalIdError { reason: String },
}
+14 -60
View File
@@ -40,9 +40,7 @@ use coconut_dkg_common::dealer::{
ContractDealing, DealerDetails, DealerDetailsResponse, DealerType,
};
use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX};
use coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS,
};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId, TOTAL_DEALINGS};
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use contracts_common::dealings::ContractSafeBytes;
use crypto::asymmetric::{encryption, identity};
@@ -74,8 +72,6 @@ pub(crate) struct DummyClient {
threshold: Arc<RwLock<Option<Threshold>>>,
dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>,
group_db: Arc<RwLock<HashMap<String, MemberResponse>>>,
initial_dealers_db: Arc<RwLock<Option<InitialReplacementData>>>,
}
impl DummyClient {
@@ -90,8 +86,6 @@ impl DummyClient {
threshold: Arc::new(RwLock::new(None)),
dealings: Arc::new(RwLock::new(HashMap::new())),
verification_share: Arc::new(RwLock::new(HashMap::new())),
group_db: Arc::new(RwLock::new(HashMap::new())),
initial_dealers_db: Arc::new(RwLock::new(None)),
}
}
@@ -149,22 +143,6 @@ impl DummyClient {
self.verification_share = Arc::clone(verification_share);
self
}
pub fn _with_group_db(
mut self,
group_db: &Arc<RwLock<HashMap<String, MemberResponse>>>,
) -> Self {
self.group_db = Arc::clone(group_db);
self
}
pub fn with_initial_dealers_db(
mut self,
initial_dealers: &Arc<RwLock<Option<InitialReplacementData>>>,
) -> Self {
self.initial_dealers_db = Arc::clone(initial_dealers);
self
}
}
#[async_trait]
@@ -215,24 +193,14 @@ impl super::client::Client for DummyClient {
Ok(*self.epoch.read().unwrap())
}
async fn group_member(&self, addr: String) -> Result<MemberResponse> {
Ok(self
.group_db
.read()
.unwrap()
.get(&addr)
.cloned()
.unwrap_or(MemberResponse { weight: None }))
async fn group_member(&self, _addr: String) -> Result<MemberResponse> {
todo!()
}
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>> {
Ok(*self.threshold.read().unwrap())
}
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>> {
Ok(self.initial_dealers_db.read().unwrap().clone())
}
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
Ok(DealerDetailsResponse {
details: self
@@ -321,25 +289,17 @@ impl super::client::Client for DummyClient {
&self,
bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
announce_address: String,
_resharing: bool,
) -> Result<ExecuteResult> {
let mut dealer_details = self.dealer_details.write().unwrap();
let assigned_index =
if let Some(details) = dealer_details.get(self.validator_address.as_ref()) {
details.assigned_index
} else {
let assigned_index = OsRng.gen();
dealer_details.insert(
self.validator_address.to_string(),
DealerDetails {
address: Addr::unchecked(self.validator_address.to_string()),
bte_public_key_with_proof,
announce_address,
assigned_index,
},
);
assigned_index
};
let assigned_index = OsRng.gen();
self.dealer_details.write().unwrap().insert(
self.validator_address.to_string(),
DealerDetails {
address: Addr::unchecked(self.validator_address.to_string()),
bte_public_key_with_proof,
announce_address,
assigned_index,
},
);
Ok(ExecuteResult {
logs: vec![Log {
msg_index: 0,
@@ -352,11 +312,7 @@ impl super::client::Client for DummyClient {
})
}
async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
_resharing: bool,
) -> Result<ExecuteResult> {
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult> {
self.dealings
.write()
.unwrap()
@@ -379,7 +335,6 @@ impl super::client::Client for DummyClient {
async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
resharing: bool,
) -> Result<ExecuteResult> {
let dealer_details = self
.dealer_details
@@ -402,7 +357,6 @@ impl super::client::Client for DummyClient {
let proposal_id = OsRng.gen();
let verify_vk_share_req = coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare {
owner: Addr::unchecked(self.validator_address.as_ref()),
resharing,
};
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: String::new(),
+3 -13
View File
@@ -7,7 +7,6 @@ use crate::support::config::Config;
use anyhow::Result;
use async_trait::async_trait;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use coconut_dkg_common::types::InitialReplacementData;
use coconut_dkg_common::{
dealer::{ContractDealing, DealerDetails, DealerDetailsResponse},
types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId},
@@ -339,12 +338,6 @@ impl crate::coconut::client::Client for Client {
.await?)
}
async fn get_initial_dealers(
&self,
) -> crate::coconut::error::Result<Option<InitialReplacementData>> {
Ok(self.0.read().await.nyxd.get_initial_dealers().await?)
}
async fn get_self_registered_dealer_details(
&self,
) -> crate::coconut::error::Result<DealerDetailsResponse> {
@@ -420,42 +413,39 @@ impl crate::coconut::client::Client for Client {
&self,
bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String,
resharing: bool,
) -> Result<ExecuteResult, CoconutError> {
Ok(self
.0
.write()
.await
.nyxd
.register_dealer(bte_key, announce_address, resharing, None)
.register_dealer(bte_key, announce_address, None)
.await?)
}
async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
resharing: bool,
) -> Result<ExecuteResult, CoconutError> {
Ok(self
.0
.write()
.await
.nyxd
.submit_dealing_bytes(dealing_bytes, resharing, None)
.submit_dealing_bytes(dealing_bytes, None)
.await?)
}
async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
resharing: bool,
) -> crate::coconut::error::Result<ExecuteResult> {
Ok(self
.0
.write()
.await
.nyxd
.submit_verification_key_share(share, resharing, None)
.submit_verification_key_share(share, None)
.await?)
}
}
BIN
View File
Binary file not shown.
+4 -6
View File
@@ -21,11 +21,10 @@ https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux#
## Installation
Inside the `nym-connect-android` directory, run the following commands:
Inside the `nym-connect-android` directory, run the following command:
```
yarn install
yarn prewebpack:dev
```
## Development
@@ -35,7 +34,7 @@ or a real [device](https://developer.android.com/studio/run/device) connected.
Inside the `nym-connect-android/src-tauri` directory, run the following command:
```
yarn dev
WRY_ANDROID_PACKAGE=net.nymtech.nym_connect_android WRY_ANDROID_LIBRARY=nym_connect_android cargo tauri android dev
```
#### Debugging
@@ -44,11 +43,10 @@ https://next--tauri.netlify.app/next/guides/debugging/application#mobile
## Production
To build the APK, run the build commands.
To build the application bundles (APK and AAB files), run the build command.
```
yarn webpack:prod
WRY_ANDROID_PACKAGE=net.nymtech.nym_connect_android WRY_ANDROID_LIBRARY=nym_connect_android cargo tauri android build --debug --apk
WRY_ANDROID_PACKAGE=net.nymtech.nym_connect_android WRY_ANDROID_LIBRARY=nym_connect_android cargo tauri android build
```
# Storybook
+3 -3
View File
@@ -8,9 +8,9 @@
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
"webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js",
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
"tauri:dev": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect_android WRY_ANDROID_LIBRARY=nym_connect_android cargo tauri android dev",
"tauri:build": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect_android WRY_ANDROID_LIBRARY=nym_connect_android cargo tauri android build",
"dev": "run-p webpack:dev:onlyThis tauri:dev",
"tauri:dev": "RUST_DEBUG=1 yarn tauri dev",
"tauri:build": "yarn tauri build",
"dev": "run-p webpack:dev tauri:dev",
"prebuild": "yarn --cwd .. build",
"build": "run-s webpack:prod tauri:build",
"storybook": "start-storybook -p 6006",
+5 -5
View File
@@ -19,6 +19,9 @@ crate-type = ["staticlib", "cdylib", "rlib"]
# tauri-build = { version = "2.0.0-alpha.0", features = [] }
tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = [] }
# tauri-codegen = "2.0.0-alpha.0"
# tauri-macros = "2.0.0-alpha.0"
[dependencies]
anyhow = "1.0"
bip39 = "1.0"
@@ -38,7 +41,8 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_repr = "0.1"
tap = "1.0.1"
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
# TODO swithing to `rfd101` temporarily, untill https://github.com/tauri-apps/tauri/pull/6174 is merged
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "rfd101", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
tendermint-rpc = "0.23.0"
thiserror = "1.0"
tokio = { version = "1.24.1", features = ["sync", "time"] }
@@ -66,7 +70,3 @@ custom-protocol = ["tauri/custom-protocol"]
# opt-level = "s"
# lto = true
[profile.release]
strip = true
opt-level = "s"
lto = true
@@ -9,6 +9,8 @@
/.idea/assetWizardSettings.xml
.DS_Store
build
/buildSrc/src/main/java/net/nymtech/nym-connect-android/kotlin/BuildTask.kt
/buildSrc/src/main/java/net/nymtech/nym-connect-android/kotlin/RustPlugin.kt
/captures
.externalNativeBuild
.cxx
@@ -0,0 +1 @@
/src/main/java/net/nymtech/nym-connect-android/generated
@@ -0,0 +1,112 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("rustPlugin")
}
android {
compileSdk = 33
defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "net.nymtech.nym_connect_android"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
}
sourceSets.getByName("main") {
// Vulkan validation layers
val ndkHome = System.getenv("NDK_HOME")
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
}
buildTypes {
getByName("debug") {
manifestPlaceholders["usesCleartextTraffic"] = "true"
isDebuggable = true
isJniDebuggable = true
isMinifyEnabled = false
packagingOptions {
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
jniLibs.keepDebugSymbols.add("*/x86/*.so")
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
}
}
getByName("release") {
isMinifyEnabled = false
// proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
flavorDimensions.add("abi")
productFlavors {
create("universal") {
val abiList = findProperty("abiList") as? String
dimension = "abi"
ndk {
abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf(
"arm64-v8a", "armeabi-v7a", "x86", "x86_64",
)
}
}
create("arm64") {
dimension = "abi"
ndk {
abiFilters += listOf("arm64-v8a")
}
}
create("arm") {
dimension = "abi"
ndk {
abiFilters += listOf("armeabi-v7a")
}
}
create("x86") {
dimension = "abi"
ndk {
abiFilters += listOf("x86")
}
}
create("x86_64") {
dimension = "abi"
ndk {
abiFilters += listOf("x86_64")
}
}
}
assetPacks += mutableSetOf()
namespace = "net.nymtech.nym_connect_android"
}
rust {
rootDirRel = "../../../../"
targets = listOf("aarch64", "armv7", "i686", "x86_64")
arches = listOf("arm64", "arm", "x86", "x86_64")
}
dependencies {
implementation("androidx.webkit:webkit:1.5.0")
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("com.google.android.material:material:1.7.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
}
afterEvaluate {
android.applicationVariants.all {
tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
productFlavors.filter { it.name != "universal" }.forEach { _ ->
val archAndBuildType = name.capitalize()
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
}
}
}

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