Compare commits

...

31 Commits

Author SHA1 Message Date
Mark Sinclair 8e60f7423f Update nym-release-publish.yml 2022-11-30 10:26:42 +00:00
Mark Sinclair de7c1b760f Update nym-release-publish.yml 2022-11-30 10:07:34 +00:00
Mark Sinclair b467ce3080 Update nym-release-publish.yml 2022-11-30 10:03:12 +00:00
Mark Sinclair dea4d197bd Update nym-release-publish.yml 2022-11-30 09:58:58 +00:00
Dave Hrycyszyn 41b37984a4 Bumping nym-cli version, missed it last time 2022-11-29 17:51:37 +00:00
Dave Hrycyszyn b541d1a034 Merge branch 'master' into develop 2022-11-29 17:40:30 +00:00
Jon Häggblad a9be8a6abd Merge remote-tracking branch 'origin/release/v1.1.2' into develop 2022-11-29 16:49:33 +01:00
Jędrzej Stuczyński 8de9f36b69 Experiment/client refactoring (#1814)
* experimenting with extracting more common client code

* drying up the wasm client

* allowing some dead code for the time being

* fixed formatting in nym-connect

* made socks5 client inside nym-connect immutable

* made clippy a bit happier

* hidden away target locking for recv timeout
2022-11-29 15:48:12 +00:00
Jon Häggblad 24e2eee547 Merge remote-tracking branch 'origin/release/v1.1.2' into develop 2022-11-29 12:47:52 +01:00
Gala bdb724e9ca Merge pull request #1812 from nymtech/no-display-maintenance
No display maintenance banner
2022-11-29 09:51:55 +01:00
Gala 76ef50dc17 Merge branch 'develop' into no-display-maintenance 2022-11-29 09:32:55 +01:00
Gala f663623768 set flag to false 2022-11-29 09:32:22 +01:00
Jon Häggblad 136202f329 Merge remote-tracking branch 'origin/release/v1.1.2' into develop 2022-11-28 13:49:46 +01:00
Raphaël Walther d25848e6f8 Added nightly build workflow on second latest release 2022-11-28 10:41:17 +01:00
Raphaël Walther 0084ba221b Set build on latest release on schedule event 2022-11-25 16:03:43 +01:00
Jędrzej Stuczyński 186896bb37 Feature/gateway client protocol version (#1795)
* Introducing concept of gateway protocol version

* Remove version-based gateway filtering

* Fixed the unit test

* grammar
2022-11-25 13:29:42 +00:00
Mark Sinclair df90ff8658 nym-cli: improve error reporting/handling and changed vesting-schedule queries to use query client instead of signing client 2022-11-25 13:16:44 +00:00
Bogdan-Ștefan Neacşu bff079a3f8 Fix export dkg contract addr (#1800)
* Export dkg contract for mainnet when no config file present

* Remove redundant env files
2022-11-25 14:18:07 +02:00
Jon Häggblad 9c361385a7 websocket-requests: fix length check before deserialize (#1799) 2022-11-24 23:45:25 +01:00
Jon Häggblad a9983003d4 changelog: add missing entry for fixing message decrypt in gateway-client 2022-11-24 23:29:37 +01:00
Jon Häggblad e645d14005 Make connection_id optional in ClientRequest::Send (#1798) 2022-11-24 23:13:51 +01:00
Jędrzej Stuczyński cbf9db91ab Feature/use expect instead of panicking (#1797)
* Implementation of 'Debug' on 'RealMessage'

* expect with failed channel name instead of throwing empty panics

* Introduced Debug trait constraint in ProxyRunner

* Derive Debug for socks5_requests::Message
2022-11-24 17:02:31 +00:00
Jon Häggblad 8304146195 Fix decrypting stored received msg (#1786)
* Fix decrypting stored received msg

* rustfmt

* Moving binary message recovery to separate function

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-11-24 10:26:09 +01:00
Jon Häggblad c5c16cd6b0 client-core: add warning when delay multiplier is larger than 1 2022-11-23 21:00:48 +01:00
Jon Häggblad 258fa41271 Update wallet and connect lock files (#1793) 2022-11-23 20:59:12 +01:00
Jon Häggblad 0a41834fbe real_traffic_stream: reduce frequency of status print (#1794) 2022-11-23 16:56:09 +01:00
Mark Sinclair 9637afea85 Update contracts-build.yml 2022-11-23 15:51:21 +00:00
cgi-bin/ c8b454a085 Possibilty to change gateway ws listener (#1779)
* add: set gatewayListener

* Update types.ts

* Update worker.ts
2022-11-23 15:14:43 +00:00
Fran Arbanas 81f7457e0e Add step to release GH actions (#1792)
* feat: add a release step to nym contracts GH action

* feat: add shrinking the size of wasm
2022-11-23 15:13:18 +00:00
Jon Häggblad 63ae568cc2 rust: bump required version to 1.65 in some crates that need it 2022-11-23 15:52:58 +01:00
Jon Häggblad f3c1ff02e2 Network-requester: throttle inbound connections (#1789)
* Return and handle ClientRequest::LaneQueueLenghts

* Pass lane queue lengths to inbound future

* Remove unused self reference

* Request lane queue lengths periodically for all open connections

* Add timeouts

* Rename to ConnectionCommandSender and Receiver

* Rename to client_connection_tx/rx

* Fix wasm build

* Replace bool with enum
2022-11-23 12:03:58 +01:00
71 changed files with 1487 additions and 1412 deletions
+4 -4
View File
@@ -9,12 +9,12 @@ jobs:
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_release_matrix.json
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_release_matrix.json'
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-latest
@@ -174,7 +174,7 @@ jobs:
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: build
needs: [build,get_release]
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
@@ -192,7 +192,7 @@ jobs:
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
GIT_BRANCH: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
@@ -0,0 +1,203 @@
name: Nightly builds on second latest release
on:
schedule:
- cron: '24 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-latest
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features -- --ignored
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-latest' }}
with:
command: clean
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets --features=coconut -- -D warnings
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: [build,get_release]
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly-release"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -1,50 +0,0 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
}
]
+21 -2
View File
@@ -7,7 +7,11 @@ on:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
type: boolean
release_tag:
description: 'Release tag to update'
required: false
type: string
release:
types: [created]
@@ -19,11 +23,19 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest]
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
if: "${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '' }}"
with:
ref: ${{ github.event.inputs.release_tag }}
- uses: actions/checkout@v3
if: "${{ github.event.inputs.release_tag == '' }}"
- run: git log
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
@@ -66,6 +78,13 @@ jobs:
target/release/nym-cli
retention-days: 30
- uses: AButler/upload-release-assets@v2.0
# see https://stackoverflow.com/questions/71045674/how-to-check-if-an-optional-workflow-dispatch-input-is-set-or-not
if: "${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '' }}"
with:
files: 'target/release/nym-client;target/release/nym-gateway;target/release/nym-mixnode;target/release/nym-socks5-client;target/release/nym-validator-api;target/release/nym-network-requester;target/release/nym-network-statistics;target/release/nym-cli'
release-tag: ${{ inputs.release_tag }}
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
+5
View File
@@ -17,6 +17,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- clients,validator-api: take coconut signers from the chain instead of specifying them via CLI ([#1747])
- multisig contract: add DKG contract to the list of addresses that can create proposals ([#1747])
- socks5-client: wait closing inbound connection until data is sent, and throttle incoming data in general ([#1783])
- nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client
### Fixed
- gateway-client: fix decrypting stored messages on reconnect ([#1786])
### Fixed
Generated
+4 -1
View File
@@ -609,6 +609,7 @@ dependencies = [
"tempfile",
"thiserror",
"tokio",
"tokio-stream",
"topology",
"url",
"validator-client",
@@ -3122,7 +3123,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"anyhow",
"base64",
@@ -3139,6 +3140,7 @@ dependencies = [
"pretty_env_logger",
"serde",
"serde_json",
"tap",
"tokio",
"validator-client",
]
@@ -3164,6 +3166,7 @@ dependencies = [
"rand 0.6.5",
"serde",
"serde_json",
"tap",
"thiserror",
"time 0.3.14",
"toml",
+7 -3
View File
@@ -17,6 +17,7 @@ sled = { version = "0.34", optional = true }
tap = "1.0.1"
thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] }
tokio = { version = "1.21.2", features = ["time", "macros"]}
# internal
config = { path = "../../common/config" }
@@ -30,8 +31,11 @@ nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
task = { path = "../../common/task" }
tokio = { version = "1.21.2", features = ["time", "macros"]}
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
version = "0.1.9"
features = ["time"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
@@ -47,8 +51,8 @@ rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
version = "0.2.4"
features = ["futures"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
path = "../../common/task"
#[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
#path = "../../common/task"
[dev-dependencies]
tempfile = "3.1.0"
@@ -0,0 +1,441 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::KeyManager;
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
use crate::client::received_buffer::{
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
};
use crate::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
use crate::error::ClientCoreError;
use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::info;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
#[cfg(feature = "reply-surb")]
use std::path::PathBuf;
#[cfg(feature = "reply-surb")]
use tap::TapFallible;
use task::{ShutdownListener, ShutdownNotifier};
use url::Url;
// it's fine to do this disgusting compilation flag business here as this problem
// is going to go away in 1.2.0
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
pub struct ClientInput {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub connection_command_sender: ConnectionCommandSender,
pub input_sender: InputMessageSender,
}
pub struct ClientOutput {
pub received_buffer_request_sender: ReceivedBufferRequestSender,
}
pub enum ClientInputStatus {
AwaitingProducer { client_input: ClientInput },
Connected,
}
impl ClientInputStatus {
pub fn register_producer(&mut self) -> ClientInput {
match std::mem::replace(self, ClientInputStatus::Connected) {
ClientInputStatus::AwaitingProducer { client_input } => client_input,
ClientInputStatus::Connected => panic!("producer was already registered before"),
}
}
}
pub enum ClientOutputStatus {
AwaitingConsumer { client_output: ClientOutput },
Connected,
}
impl ClientOutputStatus {
pub fn register_consumer(&mut self) -> ClientOutput {
match std::mem::replace(self, ClientOutputStatus::Connected) {
ClientOutputStatus::AwaitingConsumer { client_output } => client_output,
ClientOutputStatus::Connected => panic!("consumer was already registered before"),
}
}
}
pub struct BaseClientBuilder<'a> {
// due to wasm limitations I had to split it like this : (
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
disabled_credentials: bool,
validator_api_endpoints: Vec<Url>,
#[cfg(feature = "reply-surb")]
reply_surb_keys_store_path: PathBuf,
bandwidth_controller: Option<BandwidthController>,
key_manager: KeyManager,
}
impl<'a> BaseClientBuilder<'a> {
pub fn new_from_base_config<T>(
base_config: &'a Config<T>,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
) -> BaseClientBuilder<'a> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: base_config.get_debug_config(),
disabled_credentials: base_config.get_disabled_credentials_mode(),
validator_api_endpoints: base_config.get_validator_api_endpoints(),
bandwidth_controller,
key_manager,
#[cfg(feature = "reply-surb")]
reply_surb_keys_store_path: base_config.get_reply_encryption_key_store_path(),
}
}
pub fn new(
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
disabled_credentials: bool,
validator_api_endpoints: Vec<Url>,
#[cfg(feature = "reply-surb")] reply_surb_keys_store_path: PathBuf,
) -> BaseClientBuilder<'a> {
BaseClientBuilder {
gateway_config,
debug_config,
disabled_credentials,
validator_api_endpoints,
bandwidth_controller,
key_manager,
#[cfg(feature = "reply-surb")]
reply_surb_keys_store_path,
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.debug_config.average_ack_delay,
self.debug_config.average_packet_delay,
self.debug_config.loop_cover_traffic_average_delay,
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = self.debug_config.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) {
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.debug_config.ack_wait_multiplier,
self.debug_config.ack_wait_addition,
self.debug_config.average_ack_delay,
self.debug_config.message_sending_average_delay,
self.debug_config.average_packet_delay,
self.debug_config.disable_main_poisson_packet_distribution,
self.as_mix_recipient(),
);
if let Some(size) = self.debug_config.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
lane_queue_lengths,
client_connection_rx,
#[cfg(feature = "reply-surb")]
reply_key_storage,
)
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
#[cfg(feature = "reply-surb")]
reply_key_storage,
)
.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: ShutdownListener,
) -> GatewayClient {
let gateway_id = self.gateway_config.gateway_id.clone();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.gateway_config.gateway_owner.clone();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
let shared_key = if self.key_manager.gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
None
};
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
shared_key,
mixnet_message_sender,
ack_sender,
self.debug_config.gateway_response_timeout,
self.bandwidth_controller.take(),
Some(shutdown),
);
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), ClientCoreError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.validator_api_endpoints.clone(),
self.debug_config.topology_refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology);
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
#[cfg(feature = "reply-surb")]
let reply_key_storage =
ReplyKeyStorage::load(&self.reply_surb_keys_store_path).tap_err(|err| {
log::error!("Failed to load reply key storage - is it perhaps already in use?");
log::error!("{:?}", err);
})?;
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
shutdown.subscribe(),
#[cfg(feature = "reply-surb")]
reply_key_storage.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
// controller that connections are closed.
let (client_connection_tx, client_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections (e.g socks5 for attached network-requesters)
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
shared_lane_queue_lengths.clone(),
client_connection_rx,
shutdown.subscribe(),
#[cfg(feature = "reply-surb")]
reply_key_storage,
);
if !self.debug_config.disable_loop_cover_traffic_stream {
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
Ok(BaseClient {
client_input: ClientInputStatus::AwaitingProducer {
client_input: ClientInput {
shared_lane_queue_lengths,
connection_command_sender: client_connection_tx,
input_sender,
},
},
client_output: ClientOutputStatus::AwaitingConsumer {
client_output: ClientOutput {
received_buffer_request_sender,
},
},
shutdown_notifier: shutdown,
})
}
}
pub struct BaseClient {
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
pub shutdown_notifier: ShutdownNotifier,
}
@@ -143,6 +143,16 @@ impl LoopCoverTrafficStream<OsRng> {
self.packet_size = packet_size;
}
fn set_next_delay(&mut self, amount: Duration) {
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(amount));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(amount));
self.next_delay = next_delay;
}
async fn on_new_message(&mut self) {
trace!("next cover message!");
@@ -202,12 +212,11 @@ impl LoopCoverTrafficStream<OsRng> {
tokio::task::yield_now().await;
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
// we should set initial delay only when we actually start the stream
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.next_delay = Box::pin(time::sleep(sampled));
self.set_next_delay(sampled);
spawn_future(async move {
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
@@ -228,19 +237,16 @@ impl LoopCoverTrafficStream<OsRng> {
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("LoopCoverTrafficStream: Exiting");
})
}
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
// we should set initial delay only when we actually start the stream
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.next_delay = Box::pin(wasm_timer::Delay::new(sampled));
self.set_next_delay(sampled);
spawn_future(async move {
debug!("Started LoopCoverTrafficStream without graceful shutdown support");
@@ -149,6 +149,10 @@ impl KeyManager {
)
}
pub fn gateway_key_set(&self) -> bool {
self.gateway_shared_key.is_some()
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
@@ -67,10 +67,7 @@ impl MixTrafficController {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
spawn_future(async move {
debug!("Started MixTrafficController with graceful shutdown support");
@@ -90,14 +87,11 @@ impl MixTrafficController {
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("MixTrafficController: Exiting");
})
}
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
spawn_future(async move {
debug!("Started MixTrafficController without graceful shutdown support");
+4
View File
@@ -1,3 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod base_client;
pub mod cover_traffic_stream;
pub mod inbound_messages;
pub mod key_manager;
@@ -70,10 +70,7 @@ impl AcknowledgementListener {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started AcknowledgementListener with graceful shutdown support");
while !shutdown.is_shutdown() {
@@ -90,13 +87,12 @@ impl AcknowledgementListener {
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("AcknowledgementListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started AcknowledgementListener without graceful shutdown support");
@@ -245,7 +245,6 @@ impl ActionController {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started ActionController with graceful shutdown support");
@@ -272,13 +271,15 @@ impl ActionController {
}
}
}
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("ActionController: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started ActionController without graceful shutdown support");
@@ -194,10 +194,7 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started InputMessageListener with graceful shutdown support");
while !shutdown.is_shutdown() {
@@ -216,13 +213,12 @@ where
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("InputMessageListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started InputMessageListener without graceful shutdown support");
while let Some(input_msg) = self.input_receiver.recv().await {
@@ -234,7 +234,6 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
@@ -280,7 +279,8 @@ where
});
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) fn start(self) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
@@ -125,10 +125,7 @@ where
.expect("BatchRealMessageReceiver has stopped receiving!");
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started RetransmissionRequestListener with graceful shutdown support");
while !shutdown.is_shutdown() {
@@ -145,13 +142,12 @@ where
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("RetransmissionRequestListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started RetransmissionRequestListener without graceful shutdown support");
@@ -42,7 +42,6 @@ impl SentNotificationListener {
.unwrap();
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started SentNotificationListener with graceful shutdown support");
@@ -66,7 +65,8 @@ impl SentNotificationListener {
log::debug!("SentNotificationListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started SentNotificationListener without graceful shutdown support");
@@ -113,9 +113,9 @@ impl RealMessagesController<OsRng> {
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
topology_access: TopologyAccessor,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
let rng = OsRng;
@@ -175,7 +175,6 @@ impl RealMessagesController<OsRng> {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
@@ -495,40 +495,61 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started OutQueueControl with graceful shutdown support");
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
let mut infrequent_status_timer = tokio::time::interval(Duration::from_secs(60));
#[cfg(not(target_arch = "wasm32"))]
{
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
let mut infrequent_status_timer = tokio::time::interval(Duration::from_secs(60));
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
_ = status_timer.tick() => {
self.log_status();
}
_ = infrequent_status_timer.tick() => {
self.log_status_infrequent();
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
_ = status_timer.tick() => {
self.log_status();
}
_ = infrequent_status_timer.tick() => {
self.log_status_infrequent();
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
}
#[cfg(target_arch = "wasm32")]
{
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("OutQueueControl: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started OutQueueControl without graceful shutdown support");
@@ -320,10 +320,7 @@ impl RequestReceiver {
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started RequestReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
@@ -342,13 +339,12 @@ impl RequestReceiver {
},
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("RequestReceiver: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
async fn run(&mut self) {
debug!("Started RequestReceiver without graceful shutdown support");
@@ -374,10 +370,7 @@ impl FragmentedMessageReceiver {
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
@@ -395,13 +388,12 @@ impl FragmentedMessageReceiver {
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("FragmentedMessageReceiver: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
async fn run(&mut self) {
debug!("Started FragmentedMessageReceiver without graceful shutdown support");
@@ -438,7 +430,6 @@ impl ReceivedMessagesBufferController {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut fragmented_message_receiver = self.fragmented_message_receiver;
let mut request_receiver = self.request_receiver;
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
use futures::StreamExt;
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
@@ -296,14 +297,22 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = tokio::time::sleep(self.refresh_rate) => {
_ = interval.next() => {
self.refresh().await;
},
_ = shutdown.recv() => {
@@ -311,21 +320,23 @@ impl TopologyRefresher {
},
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
use futures::StreamExt;
spawn_future(async move {
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while let Some(_) = interval.next().await {
while (interval.next().await).is_some() {
self.refresh().await;
}
})
+34 -20
View File
@@ -34,7 +34,7 @@ pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config<T> {
client: Client<T>,
@@ -42,17 +42,23 @@ pub struct Config<T> {
#[serde(default)]
logging: Logging,
#[serde(default)]
debug: Debug,
debug: DebugConfig,
}
impl<T: NymConfig> Config<T> {
pub fn new<S: Into<String>>(id: S) -> Self {
impl<T> Config<T> {
pub fn new<S: Into<String>>(id: S) -> Self
where
T: NymConfig,
{
let mut cfg = Config::default();
cfg.with_id(id);
cfg
}
pub fn with_id<S: Into<String>>(&mut self, id: S) {
pub fn with_id<S: Into<String>>(&mut self, id: S)
where
T: NymConfig,
{
let id = id.into();
// identity key setting
@@ -117,7 +123,7 @@ impl<T: NymConfig> Config<T> {
self.client.disabled_credentials_mode = disabled_credentials_mode;
}
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpoint) {
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
self.client.gateway_endpoint = gateway_endpoint;
}
@@ -203,7 +209,11 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_listener.clone()
}
pub fn get_gateway_endpoint(&self) -> &GatewayEndpoint {
pub fn get_gateway_endpoint_config(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
pub fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
@@ -212,6 +222,10 @@ impl<T: NymConfig> Config<T> {
}
// Debug getters
pub fn get_debug_config(&self) -> &DebugConfig {
&self.debug
}
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
}
@@ -257,7 +271,7 @@ impl<T: NymConfig> Config<T> {
}
pub fn get_use_extended_packet_size(&self) -> Option<ExtendedPacketSize> {
self.debug.use_extended_packet_size.clone()
self.debug.use_extended_packet_size
}
pub fn get_version(&self) -> &str {
@@ -277,7 +291,7 @@ impl<T: NymConfig> Default for Config<T> {
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
pub struct GatewayEndpoint {
pub struct GatewayEndpointConfig {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
@@ -289,10 +303,10 @@ pub struct GatewayEndpoint {
pub gateway_listener: String,
}
impl From<topology::gateway::Node> for GatewayEndpoint {
fn from(node: topology::gateway::Node) -> GatewayEndpoint {
impl From<topology::gateway::Node> for GatewayEndpointConfig {
fn from(node: topology::gateway::Node) -> GatewayEndpointConfig {
let gateway_listener = node.clients_address();
GatewayEndpoint {
GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_owner: node.owner,
gateway_listener,
@@ -300,7 +314,7 @@ impl From<topology::gateway::Node> for GatewayEndpoint {
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct Client<T> {
/// Version of the client for which this configuration was created.
#[serde(default = "missing_string_value")]
@@ -346,7 +360,7 @@ pub struct Client<T> {
reply_encryption_key_store_path: PathBuf,
/// Information regarding how the client should send data to gateway.
gateway_endpoint: GatewayEndpoint,
gateway_endpoint: GatewayEndpointConfig,
/// Path to the database containing bandwidth credentials of this client.
database_path: PathBuf,
@@ -416,13 +430,13 @@ impl<T: NymConfig> Client<T> {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Logging {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Debug {
pub struct DebugConfig {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
@@ -488,7 +502,7 @@ pub struct Debug {
pub use_extended_packet_size: Option<ExtendedPacketSize>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ExtendedPacketSize {
Extended8,
@@ -496,9 +510,9 @@ pub enum ExtendedPacketSize {
Extended32,
}
impl Default for Debug {
impl Default for DebugConfig {
fn default() -> Self {
Debug {
DebugConfig {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
+6
View File
@@ -1,6 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorageError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
@@ -16,6 +18,10 @@ pub enum ClientCoreError {
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[cfg(feature = "reply-surb")]
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[error("No gateway with id: {0}")]
NoGatewayWithId(String),
#[error("No gateways on network")]
-1
View File
@@ -90,7 +90,6 @@ async fn register_with_gateway(
gateway.owner.clone(),
our_identity.clone(),
timeout,
#[cfg(not(target_arch = "wasm32"))]
None,
);
gateway_client
+4
View File
@@ -27,6 +27,10 @@ impl SocketType {
_ => SocketType::None,
}
}
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
+146 -420
View File
@@ -1,205 +1,46 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::{
ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use client_core::client::real_messages_control;
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender,
ReceivedMessagesBufferController, ReconstructedMessagesReceiver,
};
use client_core::client::reply_key_storage::ReplyKeyStorage;
use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tap::TapFallible;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::{Config, SocketType};
use crate::client::config::Config;
use crate::error::ClientError;
use crate::websocket;
use client_connections::TransmissionLane;
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
use client_core::client::inbound_messages::InputMessage;
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::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use task::{wait_for_signal, ShutdownNotifier};
pub(crate) mod config;
pub struct NymClient {
pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates,
/// key filepaths, etc.
config: Config,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
/// through the mix network.
/// It is only available if the client started with the websocket listener disabled.
input_tx: Option<InputMessageSender>,
/// Channel used for obtaining reconstructed messages received from the mix network.
/// It is only available if the client started with the websocket listener disabled.
receive_tx: Option<ReconstructedMessagesReceiver>,
}
impl NymClient {
impl SocketClient {
pub fn new(config: Config) -> Self {
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
SocketClient {
config,
key_manager,
input_tx: None,
receive_tx: None,
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_loop_cover_traffic_average_delay(),
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: ShutdownListener,
) {
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
self.config.get_base().get_ack_wait_addition(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_disabled_main_poisson_packet_distribution(),
self.as_mix_recipient(),
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_key_storage,
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: ReplyKeyStorage,
shutdown: ShutdownListener,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
reply_key_storage,
)
.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: ShutdownListener,
) -> GatewayClient {
let gateway_id = self.config.get_base().get_gateway_id();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
@@ -212,163 +53,48 @@ impl NymClient {
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
Some(shutdown),
);
gateway_client
.set_disabled_credentials_mode(self.config.get_base().get_disabled_credentials_mode());
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
bandwidth_controller
}
fn start_websocket_listener(
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
shared_lane_queue_lengths: LaneQueueLengths,
client_connection_tx: ConnectionCommandSender,
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
self_address: Recipient,
) {
info!("Starting websocket listener...");
let ClientInput {
shared_lane_queue_lengths,
connection_command_sender,
input_sender,
} = client_input;
let received_buffer_request_sender = client_output.received_buffer_request_sender;
let websocket_handler = websocket::Handler::new(
msg_input,
client_connection_tx,
buffer_requester,
&self.as_mix_recipient(),
input_sender,
connection_command_sender,
received_buffer_request_sender,
self_address,
shared_lane_queue_lengths,
);
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
websocket::Listener::new(config.get_listening_port()).start(websocket_handler);
}
/// 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)
pub async fn send_message(
&mut self,
recipient: Recipient,
message: Vec<u8>,
with_reply_surb: bool,
) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb, lane);
self.input_tx
.as_ref()
.expect("start method was not called before!")
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
}
/// 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)
pub async fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
let input_msg = InputMessage::new_reply(reply_surb, message);
self.input_tx
.as_ref()
.expect("start method was not called before!")
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
}
/// 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)
/// Note: it waits for the first occurrence of messages being sent to ourselves. If you expect multiple
/// messages, you might have to call this function repeatedly.
// TODO: I guess this should really return something that `impl Stream<Item=ReconstructedMessage>`
pub async fn wait_for_messages(&mut self) -> Vec<ReconstructedMessage> {
use futures::StreamExt;
self.receive_tx
.as_mut()
.expect("start method was not called before!")
.next()
.await
.expect("buffer controller seems to have somehow died!")
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), ClientError> {
let shutdown = self.start().await?;
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
pub async fn run_socket_forever(self) -> Result<(), ClientError> {
let shutdown = self.start_socket().await?;
wait_for_signal().await;
println!(
@@ -388,117 +114,117 @@ impl NymClient {
Ok(())
}
pub async fn start(&mut self) -> Result<ShutdownNotifier, ClientError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.tap_err(|err| {
log::error!("Failed to load reply key storage - is it perhaps already in use?");
log::error!("{}", err);
})?;
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
shutdown.subscribe(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
// controller that connections are closed.
let (client_connection_tx, client_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections (e.g socks5 for attached network-requesters)
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
shared_lane_queue_lengths.clone(),
client_connection_rx,
shutdown.subscribe(),
);
if !self
.config
.get_base()
.get_disabled_loop_cover_traffic_stream()
{
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
pub async fn start_socket(self) -> Result<ShutdownNotifier, ClientError> {
if !self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
match self.config.get_socket_type() {
SocketType::WebSocket => self.start_websocket_listener(
received_buffer_request_sender,
input_sender,
shared_lane_queue_lengths,
client_connection_tx,
),
SocketType::None => {
// if we did not start the socket, it means we're running (supposedly) in the native mode
// and hence we should announce 'ourselves' to the buffer
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
);
// tell the buffer to start sending stuff to us
received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
let self_address = base_builder.as_mix_recipient();
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
self.receive_tx = Some(reconstructed_receiver);
self.input_tx = Some(input_sender);
}
}
Self::start_websocket_listener(&self.config, client_input, client_output, self_address);
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
info!("The address of this client is: {}", self_address);
Ok(shutdown)
Ok(started_client.shutdown_notifier)
}
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
if self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
let base_client = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
);
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();
// register our receiver
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
client_output
.received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
Ok(DirectClient {
client_input,
reconstructed_receiver,
_shutdown_notifier: started_client.shutdown_notifier,
})
}
}
pub struct DirectClient {
client_input: ClientInput,
reconstructed_receiver: ReconstructedMessagesReceiver,
// we need to keep reference to this guy otherwise things will start dropping
_shutdown_notifier: ShutdownNotifier,
}
impl DirectClient {
/// 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)
pub async fn send_message(
&mut self,
recipient: Recipient,
message: Vec<u8>,
with_reply_surb: bool,
) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb, lane);
self.client_input
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
}
/// 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)
pub async fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
let input_msg = InputMessage::new_reply(reply_surb, message);
self.client_input
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
}
/// 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)
/// Note: it waits for the first occurrence of messages being sent to ourselves. If you expect multiple
/// messages, you might have to call this function repeatedly.
// TODO: I guess this should really return something that `impl Stream<Item=ReconstructedMessage>`
pub async fn wait_for_messages(&mut self) -> Vec<ReconstructedMessage> {
use futures::StreamExt;
self.reconstructed_receiver
.next()
.await
.expect("buffer controller seems to have somehow died!")
}
}
+2 -2
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Args;
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
use config::NymConfig;
use crate::{
@@ -132,7 +132,7 @@ async fn setup_gateway(
register: bool,
user_chosen_gateway_id: Option<&str>,
config: &Config,
) -> Result<GatewayEndpoint, ClientCoreError> {
) -> Result<GatewayEndpointConfig, ClientCoreError> {
if register {
// Get the gateway details by querying the validator-api. Either pick one at random or use
// the chosen one if it's among the available ones.
+2 -2
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{
client::{config::Config, NymClient},
client::{config::Config, SocketClient},
commands::{override_config, OverrideConfig},
error::ClientError,
};
@@ -98,5 +98,5 @@ pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
return Err(ClientError::FailedLocalVersionCheck);
}
NymClient::new(config).run_forever().await
SocketClient::new(config).run_socket_forever().await
}
+4 -14
View File
@@ -1,25 +1,15 @@
use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError};
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
use client_core::error::ClientCoreError;
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
#[error("Attempted to start the client in invalid socket mode")]
InvalidSocketMode,
}
+2 -2
View File
@@ -73,14 +73,14 @@ impl Handler {
msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: &Recipient,
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
) -> Self {
Handler {
msg_input,
client_connection_tx,
buffer_requester,
self_full_address: *self_full_address,
self_full_address,
socket: None,
received_response_type: Default::default(),
lane_queue_lengths,
+46 -325
View File
@@ -1,43 +1,21 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
};
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
};
use client_core::client::reply_key_storage::ReplyKeyStorage;
use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tap::TapFallible;
use std::error::Error;
use task::{wait_for_signal_and_error, ShutdownListener, ShutdownNotifier};
pub mod config;
@@ -72,132 +50,7 @@ impl NymClient {
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_loop_cover_traffic_average_delay(),
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
client_connection_rx: ConnectionCommandReceiver,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
) {
let mut controller_config = client_core::client::real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
self.config.get_base().get_ack_wait_addition(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_disabled_main_poisson_packet_distribution(),
self.as_mix_recipient(),
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_key_storage,
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: ReplyKeyStorage,
shutdown: ShutdownListener,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
reply_key_storage,
)
.start_with_shutdown(shutdown);
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: ShutdownListener,
) -> GatewayClient {
let gateway_id = self.config.get_base().get_gateway_id();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
@@ -210,108 +63,44 @@ impl NymClient {
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
Some(shutdown),
);
gateway_client
.set_disabled_credentials_mode(self.config.get_base().get_disabled_credentials_mode());
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), Socks5ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
bandwidth_controller
}
fn start_socks5_listener(
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
lane_queue_lengths: LaneQueueLengths,
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
self_address: Recipient,
mut shutdown: ShutdownListener,
) {
info!("Starting socks5 listener...");
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
let allowed_users: Vec<User> = Vec::new();
let ClientInput {
shared_lane_queue_lengths,
connection_command_sender,
input_sender,
} = client_input;
let received_buffer_request_sender = client_output.received_buffer_request_sender;
let authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = SphinxSocksServer::new(
self.config.get_listening_port(),
config.get_listening_port(),
authenticator,
self.config.get_provider_mix_address(),
self.as_mix_recipient(),
lane_queue_lengths,
config.get_provider_mix_address(),
self_address,
shared_lane_queue_lengths,
shutdown.clone(),
);
tokio::spawn(async move {
@@ -323,7 +112,11 @@ impl NymClient {
// TODO: replace this by a generic solution, such as a task manager that stores all
// JoinHandles of all spawned tasks.
if let Err(res) = sphinx_socks
.serve(msg_input, buffer_requester, client_connection_tx)
.serve(
input_sender,
received_buffer_request_sender,
connection_command_sender,
)
.await
{
shutdown.send_we_stopped(Box::new(res));
@@ -332,7 +125,7 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), Box<dyn Error + Send>> {
pub async fn run_forever(self) -> Result<(), Box<dyn Error + Send>> {
let mut shutdown = self
.start()
.await
@@ -352,7 +145,7 @@ impl NymClient {
// Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen(
&mut self,
self,
mut receiver: Socks5ControlMessageReceiver,
) -> Result<(), Box<dyn Error + Send>> {
// Start the main task
@@ -395,101 +188,29 @@ impl NymClient {
res
}
pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.tap_err(|err| {
log::error!("Failed to load reply key storage - is it perhaps already in use?");
log::error!("{}", err);
})?;
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
shutdown.subscribe(),
pub async fn start(self) -> Result<ShutdownNotifier, Socks5ClientError> {
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
let self_address = base_builder.as_mix_recipient();
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channel for announcing closed (socks5) connections by the controller.
// This will be forwarded to `OutQueueControl`
let (client_connection_tx, client_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
client_connection_rx,
shared_lane_queue_lengths.clone(),
shutdown.subscribe(),
);
if !self
.config
.get_base()
.get_disabled_loop_cover_traffic_stream()
{
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
self.start_socks5_listener(
received_buffer_request_sender,
input_sender,
client_connection_tx,
shared_lane_queue_lengths,
shutdown.subscribe(),
Self::start_socks5_listener(
&self.config,
client_input,
client_output,
self_address,
started_client.shutdown_notifier.subscribe(),
);
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
info!("The address of this client is: {}", self_address);
Ok(shutdown)
Ok(started_client.shutdown_notifier)
}
}
+2 -2
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Args;
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
use config::NymConfig;
use crate::{
@@ -131,7 +131,7 @@ async fn setup_gateway(
register: bool,
user_chosen_gateway_id: Option<&str>,
config: &Config,
) -> Result<GatewayEndpoint, ClientCoreError> {
) -> Result<GatewayEndpointConfig, ClientCoreError> {
if register {
// Get the gateway details by querying the validator-api. Either pick one at random or use
// the chosen one if it's among the available ones.
+1 -12
View File
@@ -1,7 +1,4 @@
use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError};
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
use client_core::error::ClientCoreError;
use crate::socks::types::SocksProxyError;
@@ -9,16 +6,8 @@ use crate::socks::types::SocksProxyError;
pub enum Socks5ClientError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[error("SOCKS proxy error")]
SocksProxyError(SocksProxyError),
@@ -105,6 +105,7 @@ impl MixnetResponseListener {
}
}
}
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
.await
.expect("Task stopped without shutdown called");
+1 -1
View File
@@ -39,7 +39,7 @@ topology = { path = "../../common/topology" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
task = { path = "../../common/task" }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+1 -1
View File
@@ -24,7 +24,7 @@
},
"../pkg": {
"name": "@nymproject/nym-client-wasm",
"version": "1.0.0",
"version": "1.1.0",
"license": "Apache-2.0"
},
"node_modules/@discoveryjs/json-ext": {
-7
View File
@@ -61,16 +61,9 @@ async function main() {
// sets up better stack traces in case of in-rust panics
set_panic_hook();
console.error("the current mainnet is not compatible with v2! - either use the pre-merge branch or explicitly set the client to use one of V2 QA networks")
return
// validator server we will use to get topology from
// MAINNET (V1):
const validator = 'https://validator.nymtech.net/api'; //"http://localhost:8081";
const preferredGateway = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// QA (V2):
// const validator = 'https://qa-validator-api.nymtech.net/api'; //"http://localhost:8081";
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
+6 -3
View File
@@ -4,12 +4,15 @@
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
#![allow(clippy::drop_non_drop)]
use client_core::config::{Debug as ConfigDebug, ExtendedPacketSize, GatewayEndpoint};
use client_core::config::{DebugConfig as ConfigDebug, ExtendedPacketSize, GatewayEndpointConfig};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
/// ID specifies the human readable ID of this particular client.
pub(crate) id: String,
@@ -19,7 +22,7 @@ pub struct Config {
pub(crate) disabled_credentials_mode: bool,
/// Information regarding how the client should send data to gateway.
pub(crate) gateway_endpoint: GatewayEndpoint,
pub(crate) gateway_endpoint: GatewayEndpointConfig,
pub(crate) debug: ConfigDebug,
}
@@ -30,7 +33,7 @@ impl Config {
pub fn new(
id: String,
validator_server: String,
gateway_endpoint: GatewayEndpoint,
gateway_endpoint: GatewayEndpointConfig,
debug: Option<Debug>,
) -> Self {
Config {
+88 -310
View File
@@ -2,51 +2,44 @@
// SPDX-License-Identifier: Apache-2.0
use self::config::Config;
use client_connections::{ConnectionCommandReceiver, LaneQueueLengths, TransmissionLane};
use client_core::client::{
cover_traffic_stream::LoopCoverTrafficStream,
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
key_manager::KeyManager,
mix_traffic::{BatchMixMessageSender, MixTrafficController},
real_messages_control::{self, RealMessagesController},
received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender,
ReceivedMessagesBufferController,
},
topology_control::{TopologyAccessor, TopologyRefresher, TopologyRefresherConfig},
};
use crate::client::response_pusher::ResponsePusher;
use client_connections::TransmissionLane;
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
use client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use nymsphinx::addressing::clients::Recipient;
use rand::rngs::OsRng;
use task::ShutdownNotifier;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::console_log;
use wasm_utils::{console_error, console_log};
pub mod config;
mod response_pusher;
#[wasm_bindgen]
pub struct NymClient {
config: Config,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
// due to disgusting workaround I had to wrap the key_manager in an Option
// so that the interface wouldn't change (i.e. both `start` and `new` would still return a `NymClient`)
key_manager: Option<KeyManager>,
self_address: Option<String>,
// TODO: this should be stored somewhere persistently
// received_keys: HashSet<SURBEncryptionKey>,
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
/// through the mix network.
input_tx: Option<InputMessageSender>,
client_input: Option<ClientInput>,
// callbacks
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
on_gateway_connect: Option<js_sys::Function>,
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_shutdown: Option<ShutdownNotifier>,
}
#[wasm_bindgen]
@@ -55,14 +48,19 @@ impl NymClient {
pub fn new(config: Config) -> Self {
Self {
config,
key_manager: Self::setup_key_manager(),
key_manager: Some(Self::setup_key_manager()),
on_message: None,
on_binary_message: None,
on_gateway_connect: None,
input_tx: None,
client_input: None,
self_address: None,
_shutdown: None,
}
}
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
// perhaps this should be public?
fn setup_key_manager() -> KeyManager {
let mut rng = OsRng;
@@ -84,298 +82,26 @@ impl NymClient {
}
fn as_mix_recipient(&self) -> Recipient {
// another disgusting (and hopefully temporary) workaround
let key_manager_ref = self
.key_manager
.as_ref()
.expect("attempting to call 'as_mix_recipient' after 'start'");
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
*key_manager_ref.identity_keypair().public_key(),
*key_manager_ref.encryption_keypair().public_key(),
identity::PublicKey::from_base58_string(&self.config.gateway_endpoint.gateway_id)
.expect("no gateway has been selected"),
)
}
pub fn self_address(&self) -> String {
self.as_mix_recipient().to_string()
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
) {
console_log!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.debug.average_ack_delay,
self.config.debug.average_packet_delay,
self.config.debug.loop_cover_traffic_average_delay,
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = &self.config.debug.use_extended_packet_size {
stream.set_custom_packet_size(size.clone().into());
if let Some(address) = &self.self_address {
address.clone()
} else {
self.as_mix_recipient().to_string()
}
stream.start();
}
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
client_connection_rx: ConnectionCommandReceiver,
lane_queue_lengths: LaneQueueLengths,
) {
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.debug.ack_wait_multiplier,
self.config.debug.ack_wait_addition,
self.config.debug.average_ack_delay,
self.config.debug.message_sending_average_delay,
self.config.debug.average_packet_delay,
self.config.debug.disable_main_poisson_packet_distribution,
self.as_mix_recipient(),
);
if let Some(size) = &self.config.debug.use_extended_packet_size {
controller_config.set_custom_packet_size(size.clone().into());
}
console_log!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
lane_queue_lengths,
client_connection_rx,
)
.start();
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
) {
console_log!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
)
.start()
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
) -> GatewayClient {
let gateway_id = self.config.gateway_endpoint.gateway_id.clone();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_owner = self.config.gateway_endpoint.gateway_owner.clone();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_address = self.config.gateway_endpoint.gateway_listener.clone();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
None,
mixnet_message_sender,
ack_sender,
self.config.debug.gateway_response_timeout,
None,
);
gateway_client.set_disabled_credentials_mode(self.config.disabled_credentials_mode);
let shared_keys = gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
self.key_manager.insert_gateway_shared_key(shared_keys);
match self.on_gateway_connect.as_ref() {
Some(callback) => {
callback
.call0(&JsValue::null())
.expect("on connect callback failed!");
}
None => console_log!("Gateway connection established - no callback specified"),
};
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
vec![self.config.validator_api_url.clone()],
self.config.debug.topology_refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
console_log!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
);
}
console_log!("Starting topology refresher...");
// TODO: re-enable
topology_refresher.start();
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(gateway_client: GatewayClient) -> BatchMixMessageSender {
console_log!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start();
mix_tx
}
// TODO: this procedure is extremely overcomplicated, because it's based off native client's behaviour
// which doesn't fully apply in this case
fn start_reconstructed_pusher(
&mut self,
received_buffer_request_sender: ReceivedBufferRequestSender,
) {
let on_message = self.on_message.take();
let on_binary_message = self.on_binary_message.take();
spawn_local(async move {
let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
let this = JsValue::null();
while let Some(reconstructed) = reconstructed_receiver.next().await {
for msg in reconstructed {
if let Some(ref callback_binary) = on_binary_message {
let arg1 = serde_wasm_bindgen::to_value(&msg.message).unwrap();
callback_binary
.call1(&this, &arg1)
.expect("on binary message failed!");
}
if let Some(ref callback) = on_message {
if msg.reply_surb.is_some() {
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
}
let stringified = String::from_utf8_lossy(&msg.message).into_owned();
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!");
}
}
}
});
}
pub async fn start(mut self) -> NymClient {
console_log!("Starting wasm client '{}'", self.config.id);
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
// Channel that the real traffix controller can listed to for closing connections.
// Currently unused in the wasm client.
let (_client_connection_tx, client_connection_rx) = mpsc::unbounded();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender = Self::start_mix_traffic_controller(gateway_client);
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
client_connection_rx,
shared_lane_queue_lengths,
);
if !self.config.debug.disable_loop_cover_traffic_stream {
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
}
self.start_reconstructed_pusher(received_buffer_request_sender);
self.input_tx = Some(input_sender);
self
}
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
@@ -395,13 +121,65 @@ impl NymClient {
let input_msg = InputMessage::new_fresh(recipient, message, false, lane);
self.input_tx
self.client_input
.as_ref()
.expect("start method was not called before!")
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
self
}
fn start_reconstructed_pusher(
client_output: ClientOutput,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
) {
ResponsePusher::new(client_output, on_message, on_binary_message).start()
}
pub async fn start(mut self) -> NymClient {
console_log!("Starting the wasm client");
let base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager.take().unwrap(),
None,
true,
vec![self.config.validator_api_url.clone()],
);
self.self_address = Some(base_builder.as_mix_recipient().to_string());
let mut started_client = match base_builder.start_base().await {
Ok(base_client) => base_client,
Err(err) => {
console_error!("failed to start base client components - {}", err);
// proper error handling is left here as an exercise for the reader (hi Mark : ))
panic!("failed to start base client components - {err}")
}
};
match self.on_gateway_connect.as_ref() {
Some(callback) => {
callback
.call0(&JsValue::null())
.expect("on connect callback failed!");
}
None => console_log!("Gateway connection established - no callback specified"),
};
// those should be moved to a completely different struct, but I don't want to break compatibility for now
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let on_message = self.on_message.take();
let on_binary_message = self.on_binary_message.take();
Self::start_reconstructed_pusher(client_output, on_message, on_binary_message);
self.client_input = Some(client_input);
self._shutdown = Some(started_client.shutdown_notifier);
self
}
}
@@ -0,0 +1,71 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::client::base_client::ClientOutput;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use futures::channel::mpsc;
use futures::StreamExt;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::console_log;
pub(crate) struct ResponsePusher {
reconstructed_receiver: ReconstructedMessagesReceiver,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
}
impl ResponsePusher {
pub(crate) fn new(
client_output: ClientOutput,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
) -> Self {
if on_message.is_none() && on_binary_message.is_none() {
// exercise for the reader : )
panic!("neither 'on_message' nor 'on_binary_message' was set!")
}
// register our output
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
client_output
.received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
ResponsePusher {
reconstructed_receiver,
on_message,
on_binary_message,
}
}
pub(crate) fn start(mut self) {
spawn_local(async move {
let this = JsValue::null();
while let Some(reconstructed) = self.reconstructed_receiver.next().await {
for msg in reconstructed {
if let Some(ref callback_binary) = self.on_binary_message {
let arg1 = serde_wasm_bindgen::to_value(&msg.message).unwrap();
callback_binary
.call1(&this, &arg1)
.expect("on binary message failed!");
}
if let Some(ref callback) = self.on_message {
if msg.reply_surb.is_some() {
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
}
let stringified = String::from_utf8_lossy(&msg.message).into_owned();
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!");
}
}
}
})
}
}
+5 -5
View File
@@ -1,15 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::config::GatewayEndpoint;
use client_core::config::GatewayEndpointConfig;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpoint {
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpointConfig {
let validator_client = validator_client::client::ApiClient::new(api_server.parse().unwrap());
let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("failed to obtain list of all gateways - {}", err),
Err(err) => panic!("failed to obtain list of all gateways - {err}"),
Ok(gateways) => gateways,
};
@@ -18,7 +18,7 @@ pub async fn get_gateway(api_server: String, preferred: Option<String>) -> Gatew
.iter()
.find(|g| g.gateway.identity_key == preferred)
{
return GatewayEndpoint {
return GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
@@ -33,7 +33,7 @@ pub async fn get_gateway(api_server: String, preferred: Option<String>) -> Gatew
.first()
.expect("current topology holds no gateways");
GatewayEndpoint {
GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
+1 -3
View File
@@ -26,6 +26,7 @@ network-defaults = { path = "../../network-defaults" }
nymsphinx = { path = "../../nymsphinx" }
pemstore = { path = "../../pemstore" }
validator-client = { path = "../validator-client", optional = true }
task = { path = "../../task" }
[dependencies.tungstenite]
@@ -47,9 +48,6 @@ version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage]
path = "../../credential-storage"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
path = "../../task"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2"
@@ -26,8 +26,15 @@ use {
},
};
// TODO: make it nicer for wasm (I don't want to touch it for this experiment)
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[derive(Clone)]
pub struct BandwidthController<St: Storage> {
pub struct BandwidthController<St: Storage = PersistentStorage> {
#[allow(dead_code)]
storage: St,
#[cfg(feature = "coconut")]
+55 -35
View File
@@ -14,7 +14,7 @@ use futures::{FutureExt, SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION};
use log::*;
use network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
use nymsphinx::forwarding::packet::MixPacket;
@@ -22,6 +22,7 @@ use rand::rngs::OsRng;
use std::convert::TryFrom;
use std::sync::Arc;
use std::time::Duration;
use task::ShutdownListener;
use tungstenite::protocol::Message;
#[cfg(feature = "coconut")]
@@ -30,8 +31,6 @@ use coconut_interface::Credential;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use task::ShutdownListener;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(target_arch = "wasm32")]
@@ -67,8 +66,9 @@ pub struct GatewayClient {
/// Delay between each subsequent reconnection attempt.
reconnection_backoff: Duration,
#[cfg(not(target_arch = "wasm32"))]
/// Listen to shutdown messages.
// TODO: fix this
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
shutdown: Option<ShutdownListener>,
}
@@ -85,7 +85,7 @@ impl GatewayClient {
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
shutdown: Option<ShutdownListener>,
) -> Self {
GatewayClient {
authenticated: false,
@@ -97,18 +97,12 @@ impl GatewayClient {
local_identity,
shared_key,
connection: SocketState::NotConnected,
packet_router: PacketRouter::new(
ack_sender,
mixnet_message_sender,
#[cfg(not(target_arch = "wasm32"))]
shutdown.clone(),
),
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
response_timeout_duration,
bandwidth_controller,
should_reconnect_on_failure: true,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
#[cfg(not(target_arch = "wasm32"))]
shutdown,
}
}
@@ -136,7 +130,7 @@ impl GatewayClient {
gateway_owner: String,
local_identity: Arc<identity::KeyPair>,
response_timeout_duration: Duration,
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
shutdown: Option<ShutdownListener>,
) -> Self {
use futures::channel::mpsc;
@@ -144,12 +138,7 @@ impl GatewayClient {
// perfectly fine here, because it's not meant to be used
let (ack_tx, _) = mpsc::unbounded();
let (mix_tx, _) = mpsc::unbounded();
let packet_router = PacketRouter::new(
ack_tx,
mix_tx,
#[cfg(not(target_arch = "wasm32"))]
shutdown.clone(),
);
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
GatewayClient {
authenticated: false,
@@ -167,7 +156,6 @@ impl GatewayClient {
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
#[cfg(not(target_arch = "wasm32"))]
shutdown,
}
}
@@ -403,13 +391,10 @@ impl GatewayClient {
.batch_send_without_response(messages)
.await
{
error!("failed to batch send messages - {}...", err);
error!("failed to batch send messages - {err}...");
// we must ensure we do not leave the task still active
if let Err(err) = self.recover_socket_connection().await {
error!(
"... and the delegated stream has also errored out - {}",
err
)
error!("... and the delegated stream has also errored out - {err}")
}
Err(err)
} else {
@@ -429,13 +414,10 @@ impl GatewayClient {
SocketState::Available(ref mut conn) => Ok(conn.send(msg).await?),
SocketState::PartiallyDelegated(ref mut partially_delegated) => {
if let Err(err) = partially_delegated.send_without_response(msg).await {
error!("failed to send message without response - {}...", err);
error!("failed to send message without response - {err}...");
// we must ensure we do not leave the task still active
if let Err(err) = self.recover_socket_connection().await {
error!(
"... and the delegated stream has also errored out - {}",
err
)
error!("... and the delegated stream has also errored out - {err}")
}
Err(err)
} else {
@@ -447,6 +429,33 @@ impl GatewayClient {
}
}
fn check_gateway_protocol(
&self,
gateway_protocol: Option<u8>,
) -> Result<(), GatewayClientError> {
// right now there are no failure cases here, but this might change in the future
match gateway_protocol {
None => {
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
// note: in 1.2.0 we will have to return a hard error here
Ok(())
}
Some(v) if v != PROTOCOL_VERSION => {
let err = GatewayClientError::IncompatibleProtocol {
gateway: Some(v),
current: PROTOCOL_VERSION,
};
error!("{err}");
Err(err)
}
Some(_) => {
info!("the gateway is using exactly the same protocol version as we are. We're good to continue!");
Ok(())
}
}
}
async fn register(&mut self) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -469,11 +478,20 @@ impl GatewayClient {
.map_err(GatewayClientError::RegistrationFailure),
_ => unreachable!(),
}?;
self.authenticated = match self.read_control_response().await? {
ServerResponse::Register { status } => Ok(status),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
ServerResponse::Register {
protocol_version,
status,
} => (status, protocol_version),
ServerResponse::Error { message } => {
return Err(GatewayClientError::GatewayError(message))
}
_ => return Err(GatewayClientError::UnexpectedResponse),
};
self.check_gateway_protocol(gateway_protocol)?;
self.authenticated = authentication_status;
if self.authenticated {
self.shared_key = Some(Arc::new(shared_key));
}
@@ -512,9 +530,11 @@ impl GatewayClient {
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate {
protocol_version,
status,
bandwidth_remaining,
} => {
self.check_gateway_protocol(protocol_version)?;
self.authenticated = status;
self.bandwidth_remaining = bandwidth_remaining;
Ok(())
@@ -85,6 +85,9 @@ pub enum GatewayClientError {
#[error("Failed to send mixnet message")]
MixnetMsgSenderFailedToSend,
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
}
impl GatewayClientError {
@@ -4,15 +4,13 @@
// JS: I personally don't like this name very much, but could not think of anything better.
// I will gladly take any suggestions on how to rename this.
use crate::error::GatewayClientError;
use futures::channel::mpsc;
use log::*;
use nymsphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
use nymsphinx::params::packet_sizes::PacketSize;
#[cfg(not(target_arch = "wasm32"))]
use task::ShutdownListener;
use crate::error::GatewayClientError;
pub type MixnetMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
pub type MixnetMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
@@ -23,7 +21,6 @@ pub type AcknowledgementReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
pub struct PacketRouter {
ack_sender: AcknowledgementSender,
mixnet_message_sender: MixnetMessageSender,
#[cfg(not(target_arch = "wasm32"))]
shutdown: Option<ShutdownListener>,
}
@@ -31,12 +28,11 @@ impl PacketRouter {
pub fn new(
ack_sender: AcknowledgementSender,
mixnet_message_sender: MixnetMessageSender,
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
shutdown: Option<ShutdownListener>,
) -> Self {
PacketRouter {
ack_sender,
mixnet_message_sender,
#[cfg(not(target_arch = "wasm32"))]
shutdown,
}
}
@@ -86,7 +82,6 @@ impl PacketRouter {
if !received_messages.is_empty() {
trace!("routing 'real'");
if let Err(err) = self.mixnet_message_sender.unbounded_send(received_messages) {
#[cfg(not(target_arch = "wasm32"))]
if let Some(shutdown) = &mut self.shutdown {
if shutdown.is_shutdown_poll() {
// This should ideally not happen, but it's ok
+1
View File
@@ -22,6 +22,7 @@ thiserror = "1"
time = { version = "0.3.6", features = ["parsing", "formatting"] }
toml = "0.5.6"
url = "2.2"
tap = "1"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmwasm-std = { version = "1.0.0" }
+6
View File
@@ -15,4 +15,10 @@ pub enum ContextError {
// TODO: improve this to return known errors
#[error("failed to create client - {0}")]
NymdError(String),
#[error("{0}")]
NymdErrorPassthrough(#[from] validator_client::nymd::error::NymdError),
#[error("{0}")]
ValidatorClientError(#[from] validator_client::ValidatorClientError),
}
+5 -4
View File
@@ -6,6 +6,7 @@ use network_defaults::{
var_names::{API_VALIDATOR, MIXNET_CONTRACT_ADDRESS, NYMD_VALIDATOR, VESTING_CONTRACT_ADDRESS},
NymNetworkDetails,
};
use tap::prelude::*;
use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient};
pub use validator_client::validator_api::Client as ValidatorApiClient;
@@ -58,7 +59,7 @@ pub fn create_signing_client(
network_details: &NymNetworkDetails,
) -> Result<SigningClient, ContextError> {
let client_config = nymd::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
// get mnemonic
let mnemonic = match std::env::var("MNEMONIC") {
@@ -87,7 +88,7 @@ pub fn create_query_client(
network_details: &NymNetworkDetails,
) -> Result<QueryClient, ContextError> {
let client_config = nymd::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
let nymd_url = network_details
.endpoints
@@ -107,7 +108,7 @@ pub fn create_signing_client_with_validator_api(
network_details: &NymNetworkDetails,
) -> Result<SigningClientWithValidatorAPI, ContextError> {
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
// get mnemonic
let mnemonic = match std::env::var("MNEMONIC") {
@@ -129,7 +130,7 @@ pub fn create_query_client_with_validator_api(
network_details: &NymNetworkDetails,
) -> Result<QueryClientWithValidatorAPI, ContextError> {
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
match validator_client::client::Client::new_query(client_config) {
Ok(client) => Ok(client),
@@ -3,11 +3,11 @@
use clap::Parser;
use cosmrs::AccountId;
use log::info;
use log::{error, info};
use validator_client::nymd::{Coin, VestingQueryClient};
use crate::context::SigningClient;
use crate::context::QueryClient;
use crate::utils::show_error;
use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
@@ -18,8 +18,16 @@ pub struct Args {
pub address: Option<AccountId>,
}
pub async fn balance(args: Args, client: SigningClient) {
let account_id = args.address.unwrap_or_else(|| client.address().clone());
pub async fn balance(args: Args, client: QueryClient, address_from_mnemonic: Option<AccountId>) {
if args.address.is_none() && address_from_mnemonic.is_none() {
error!("Please specify an account address or a mnemonic to get the balance for");
return;
}
let account_id = args
.address
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
let vesting_address = account_id.to_string();
let denom = client.current_chain_details().mix_denom.base.as_str();
@@ -4,11 +4,11 @@
use clap::Parser;
use cosmrs::AccountId;
use cosmwasm_std::Coin as CosmWasmCoin;
use log::info;
use log::{error, info};
use validator_client::nymd::{Coin, VestingQueryClient};
use crate::context::SigningClient;
use crate::context::QueryClient;
use crate::utils::show_error;
use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
@@ -19,8 +19,18 @@ pub struct Args {
pub address: Option<AccountId>,
}
pub async fn query(args: Args, client: SigningClient) {
let account_id = args.address.unwrap_or_else(|| client.address().clone());
pub async fn query(args: Args, client: QueryClient, address_from_mnemonic: Option<AccountId>) {
if args.address.is_none() && address_from_mnemonic.is_none() {
error!("Please specify an account address or a mnemonic to get the balance for");
return;
}
let account_id = args
.address
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
info!("Checking account {} for a vesting schedule...", account_id);
let vesting_address = account_id.to_string();
let denom = client.current_chain_details().mix_denom.base.as_str();
-20
View File
@@ -1,20 +0,0 @@
CONFIGURED=true
RUST_LOG=info
RUST_BACKTRACE=1
BECH32_PREFIX=n
MIX_DENOM=unym
MIX_DENOM_DISPLAY=nym
STAKE_DENOM=unyx
STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://127.0.0.1:8090"
NYMD_VALIDATOR="https://rpc.nyx.nodes.guru/"
API_VALIDATOR="https://validator.nymtech.net/api/"
-20
View File
@@ -1,20 +0,0 @@
CONFIGURED=true
RUST_LOG=info
RUST_BACKTRACE=1
BECH32_PREFIX=n
MIX_DENOM=unym
MIX_DENOM_DISPLAY=nym
STAKE_DENOM=unyx
STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep
VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw
MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
NYMD_VALIDATOR="https://qa-validator.nymtech.net"
API_VALIDATOR="https://qa-validator-api.nymtech.net/api"
+8
View File
@@ -85,6 +85,10 @@ pub fn export_to_env() {
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_to_default(
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
COCONUT_DKG_CONTRACT_ADDRESS,
);
set_var_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
@@ -125,6 +129,10 @@ pub fn export_to_env_if_not_set() {
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
COCONUT_DKG_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
@@ -254,6 +254,7 @@ impl Controller {
},
}
}
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
.await
.expect("Task stopped without shutdown called");
+5 -1
View File
@@ -9,7 +9,11 @@ edition = "2021"
futures = "0.3"
log = "0.4"
thiserror = "1.0.37"
tokio = { version = "1.21.2", features = ["macros", "signal", "time", "sync"] }
tokio = { version = "1.21.2", features = ["macros", "sync"] }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
version = "1.21.2"
features = ["signal", "time"]
[dev-dependencies]
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] }
+2
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
pub mod shutdown;
#[cfg(not(target_arch = "wasm32"))]
pub mod signal;
pub use shutdown::{ShutdownListener, ShutdownNotifier};
#[cfg(not(target_arch = "wasm32"))]
pub use signal::{wait_for_signal, wait_for_signal_and_error};
+16
View File
@@ -31,6 +31,7 @@ pub struct ShutdownNotifier {
// track of which tasks we are still waiting for.
notify_tx: watch::Sender<()>,
notify_rx: Option<watch::Receiver<()>>,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
shutdown_timer_secs: u64,
// If any task failed, it needs to report separately
@@ -113,6 +114,11 @@ impl ShutdownNotifier {
drop(notify_rx);
}
// in wasm we'll never get our shutdown anyway...
#[cfg(target_arch = "wasm32")]
futures::future::pending::<()>().await;
#[cfg(not(target_arch = "wasm32"))]
tokio::select! {
_ = self.notify_tx.closed() => {
log::info!("All registered tasks succesfully shutdown");
@@ -149,6 +155,9 @@ pub struct ShutdownListener {
}
impl ShutdownListener {
#[cfg(not(target_arch = "wasm32"))]
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
fn new(
notify: watch::Receiver<()>,
return_error: ErrorSender,
@@ -175,6 +184,13 @@ impl ShutdownListener {
self.shutdown = true;
}
pub async fn recv_timeout(&mut self) {
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Self::SHUTDOWN_TIMEOUT, self.recv())
.await
.expect("Task stopped without shutdown called");
}
pub fn is_shutdown_poll(&mut self) -> bool {
if self.shutdown {
return true;
+3 -7
View File
@@ -209,18 +209,14 @@ impl NymTopology {
#[must_use]
pub fn filter_system_version(&self, expected_version: &str) -> Self {
self.filter_node_versions(expected_version, expected_version)
self.filter_node_versions(expected_version)
}
#[must_use]
pub fn filter_node_versions(
&self,
expected_mix_version: &str,
expected_gateway_version: &str,
) -> Self {
pub fn filter_node_versions(&self, expected_mix_version: &str) -> Self {
NymTopology {
mixes: self.mixes.filter_by_version(expected_mix_version),
gateways: self.gateways.filter_by_version(expected_gateway_version),
gateways: self.gateways.clone(),
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ export const MobileNav: React.FC<{ children: React.ReactNode }> = ({ children }:
const { navState, updateNavState } = useMainContext();
const [drawerOpen, setDrawerOpen] = React.useState(false);
// Set maintenance banner to false by default to don't display it
const [openMaintenance, setOpenMaintenance] = React.useState(true);
const [openMaintenance, setOpenMaintenance] = React.useState(false);
const toggleDrawer = () => {
setDrawerOpen(!drawerOpen);
+1 -1
View File
@@ -235,7 +235,7 @@ export const Nav: React.FC = ({ children }) => {
const [drawerIsOpen, setDrawerToOpen] = React.useState(false);
const [fixedOpen, setFixedOpen] = React.useState(false);
// Set maintenance banner to false by default to don't display it
const [openMaintenance, setOpenMaintenance] = React.useState(true);
const [openMaintenance, setOpenMaintenance] = React.useState(false);
const theme = useTheme();
const setToActive = (id: number) => {
+5 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crypto::generic_array;
@@ -12,6 +12,10 @@ pub mod iv;
pub mod registration;
pub mod types;
/// Defines the current version of the communication protocol between gateway and clients.
/// It has to be incremented for any breaking change.
pub const PROTOCOL_VERSION: u8 = 1;
pub type GatewayMac = HmacOutput<GatewayIntegrityHmacAlgorithm>;
// TODO: could using `Mac` trait here for OutputSize backfire?
@@ -210,7 +210,10 @@ impl<'a, S> State<'a, S> {
match msg {
WsMessage::Text(ws_msg) => match types::RegistrationHandshake::try_from(ws_msg) {
Ok(reg_handshake_msg) => return match reg_handshake_msg {
types::RegistrationHandshake::HandshakePayload { data } => Ok(data),
// hehe, that's a bit disgusting that the type system requires we explicitly ignore the
// protocol_version field that we actually never attach at this point
// yet another reason for the overdue refactor
types::RegistrationHandshake::HandshakePayload { data, .. } => Ok(data),
types::RegistrationHandshake::HandshakeError { message } => Err(HandshakeError::RemoteError(message)),
},
Err(_) => error!("Received a non-handshake message during the registration handshake! It's getting dropped."),
+48 -7
View File
@@ -4,7 +4,7 @@
use crate::authentication::encrypted_address::EncryptedAddressBytes;
use crate::iv::IV;
use crate::registration::handshake::SharedKeys;
use crate::GatewayMacSize;
use crate::{GatewayMacSize, PROTOCOL_VERSION};
use crypto::generic_array::typenum::Unsigned;
use crypto::hmac::recompute_keyed_hmac_and_verify_tag;
use crypto::symmetric::stream_cipher;
@@ -28,13 +28,22 @@ use credentials::token::bandwidth::TokenCredential;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum RegistrationHandshake {
HandshakePayload { data: Vec<u8> },
HandshakeError { message: String },
HandshakePayload {
#[serde(default)]
protocol_version: Option<u8>,
data: Vec<u8>,
},
HandshakeError {
message: String,
},
}
impl RegistrationHandshake {
pub fn new_payload(data: Vec<u8>) -> Self {
RegistrationHandshake::HandshakePayload { data }
RegistrationHandshake::HandshakePayload {
protocol_version: Some(PROTOCOL_VERSION),
data,
}
}
pub fn new_error<S: Into<String>>(message: S) -> Self {
@@ -115,12 +124,16 @@ pub enum ClientControlRequest {
// TODO: should this also contain a MAC considering that at this point we already
// have the shared key derived?
Authenticate {
#[serde(default)]
protocol_version: Option<u8>,
address: String,
enc_address: String,
iv: String,
},
#[serde(alias = "handshakePayload")]
RegisterHandshakeInitRequest {
#[serde(default)]
protocol_version: Option<u8>,
data: Vec<u8>,
},
BandwidthCredential {
@@ -137,6 +150,7 @@ impl ClientControlRequest {
iv: IV,
) -> Self {
ClientControlRequest::Authenticate {
protocol_version: Some(PROTOCOL_VERSION),
address: address.as_base58_string(),
enc_address: enc_address.to_base58_string(),
iv: iv.to_base58_string(),
@@ -223,10 +237,14 @@ impl TryInto<String> for ClientControlRequest {
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ServerResponse {
Authenticate {
#[serde(default)]
protocol_version: Option<u8>,
status: bool,
bandwidth_remaining: i64,
},
Register {
#[serde(default)]
protocol_version: Option<u8>,
status: bool,
},
Bandwidth {
@@ -381,14 +399,37 @@ mod tests {
#[test]
fn handshake_payload_can_be_deserialized_into_register_handshake_init_request() {
let handshake_data = vec![1, 2, 3, 4, 5, 6];
let handshake_payload = RegistrationHandshake::HandshakePayload {
let handshake_payload_with_protocol = RegistrationHandshake::HandshakePayload {
protocol_version: Some(42),
data: handshake_data.clone(),
};
let serialized = serde_json::to_string(&handshake_payload).unwrap();
let serialized = serde_json::to_string(&handshake_payload_with_protocol).unwrap();
let deserialized = ClientControlRequest::try_from(serialized).unwrap();
match deserialized {
ClientControlRequest::RegisterHandshakeInitRequest { data } => {
ClientControlRequest::RegisterHandshakeInitRequest {
protocol_version,
data,
} => {
assert_eq!(protocol_version, Some(42));
assert_eq!(data, handshake_data)
}
_ => unreachable!("this branch shouldn't have been reached!"),
}
let handshake_payload_without_protocol = RegistrationHandshake::HandshakePayload {
protocol_version: None,
data: handshake_data.clone(),
};
let serialized = serde_json::to_string(&handshake_payload_without_protocol).unwrap();
let deserialized = ClientControlRequest::try_from(serialized).unwrap();
match deserialized {
ClientControlRequest::RegisterHandshakeInitRequest {
protocol_version,
data,
} => {
assert!(protocol_version.is_none());
assert_eq!(data, handshake_data)
}
_ => unreachable!("this branch shouldn't have been reached!"),
@@ -18,7 +18,7 @@ use gateway_requests::iv::{IVConversionError, IV};
use gateway_requests::registration::handshake::error::HandshakeError;
use gateway_requests::registration::handshake::{gateway_handshake, SharedKeys};
use gateway_requests::types::{ClientControlRequest, ServerResponse};
use gateway_requests::BinaryResponse;
use gateway_requests::{BinaryResponse, PROTOCOL_VERSION};
use log::*;
use mixnet_client::forwarder::MixForwardingSender;
use nymsphinx::DestinationAddressBytes;
@@ -55,6 +55,9 @@ enum InitialAuthenticationError {
#[error("Experienced connection error - {0}")]
ConnectionError(#[from] WsError),
#[error("Attempted to negotiate connection with client using incompatible protocol version. Ours is {current} and the client reports {client:?}")]
IncompatibleProtocol { client: Option<u8>, current: u8 },
}
impl InitialAuthenticationError {
@@ -279,10 +282,7 @@ where
// push them to the client
if let Err(err) = self.push_packets_to_client(shared_keys, messages).await {
warn!(
"We failed to send stored messages to fresh client - {}",
err
);
warn!("We failed to send stored messages to fresh client - {err}",);
return Err(InitialAuthenticationError::ConnectionError(err));
} else {
// if it was successful - remove them from the store
@@ -342,6 +342,33 @@ where
}
}
fn check_client_protocol(
&self,
client_protocol: Option<u8>,
) -> Result<(), InitialAuthenticationError> {
// right now there are no failure cases here, but this might change in the future
match client_protocol {
None => {
warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
// note: in 1.2.0 we will have to return a hard error here
Ok(())
}
Some(v) if v != PROTOCOL_VERSION => {
let err = InitialAuthenticationError::IncompatibleProtocol {
client: Some(v),
current: PROTOCOL_VERSION,
};
error!("{err}");
Err(err)
}
Some(_) => {
info!("the client is using exactly the same protocol version as we are. We're good to continue!");
Ok(())
}
}
}
/// Using the received challenge data, i.e. client's address as well the ciphertext of it plus
/// a fresh IV, attempts to authenticate the client by checking whether the ciphertext matches
/// the expected value if encrypted with the shared key.
@@ -389,6 +416,7 @@ where
/// * `iv`: fresh IV received with the request.
async fn handle_authenticate(
&mut self,
client_protocol_version: Option<u8>,
address: String,
enc_address: String,
iv: String,
@@ -396,6 +424,8 @@ where
where
S: AsyncRead + AsyncWrite + Unpin,
{
self.check_client_protocol(client_protocol_version)?;
let address = DestinationAddressBytes::try_from_base58_string(address)
.map_err(|err| InitialAuthenticationError::MalformedClientAddress(err.to_string()))?;
let encrypted_address = EncryptedAddressBytes::try_from_base58_string(enc_address)?;
@@ -420,6 +450,7 @@ where
Ok(InitialAuthResult::new(
client_details,
ServerResponse::Authenticate {
protocol_version: Some(PROTOCOL_VERSION),
status,
bandwidth_remaining,
},
@@ -474,11 +505,14 @@ where
/// * `init_data`: init payload of the registration handshake.
async fn handle_register(
&mut self,
client_protocol_version: Option<u8>,
init_data: Vec<u8>,
) -> Result<InitialAuthResult, InitialAuthenticationError>
where
S: AsyncRead + AsyncWrite + Unpin + Send,
{
self.check_client_protocol(client_protocol_version)?;
let remote_identity = Self::extract_remote_identity_from_register_init(&init_data)?;
let remote_address = remote_identity.derive_destination_address();
@@ -493,7 +527,10 @@ where
Ok(InitialAuthResult::new(
Some(client_details),
ServerResponse::Register { status },
ServerResponse::Register {
protocol_version: Some(PROTOCOL_VERSION),
status,
},
))
}
@@ -513,13 +550,18 @@ where
if let Ok(request) = ClientControlRequest::try_from(raw_request) {
match request {
ClientControlRequest::Authenticate {
protocol_version,
address,
enc_address,
iv,
} => self.handle_authenticate(address, enc_address, iv).await,
ClientControlRequest::RegisterHandshakeInitRequest { data } => {
self.handle_register(data).await
} => {
self.handle_authenticate(protocol_version, address, enc_address, iv)
.await
}
ClientControlRequest::RegisterHandshakeInitRequest {
protocol_version,
data,
} => self.handle_register(protocol_version, data).await,
// won't accept anything else (like bandwidth) without prior authentication
_ => Err(InitialAuthenticationError::InvalidRequest),
}
+1
View File
@@ -652,6 +652,7 @@ dependencies = [
"task",
"thiserror",
"tokio",
"tokio-stream",
"topology",
"url",
"validator-client",
+2 -2
View File
@@ -1,6 +1,6 @@
use std::path::PathBuf;
use client_core::config::GatewayEndpoint;
use client_core::config::GatewayEndpointConfig;
use std::sync::Arc;
use tap::TapFallible;
use tokio::sync::RwLock;
@@ -169,7 +169,7 @@ async fn setup_gateway(
register: bool,
user_chosen_gateway_id: Option<&str>,
config: &Socks5Config,
) -> Result<GatewayEndpoint> {
) -> Result<GatewayEndpointConfig> {
if register {
// Get the gateway details by querying the validator-api. Either pick one at random or use
// the chosen one if it's among the available ones.
+7 -3
View File
@@ -1,4 +1,4 @@
use client_core::config::GatewayEndpoint;
use client_core::config::GatewayEndpointConfig;
use futures::channel::mpsc;
use std::sync::Arc;
use tap::TapFallible;
@@ -25,13 +25,17 @@ pub enum Socks5StatusMessage {
/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`.
pub fn start_nym_socks5_client(
id: &str,
) -> Result<(Socks5ControlMessageSender, StatusReceiver, GatewayEndpoint)> {
) -> Result<(
Socks5ControlMessageSender,
StatusReceiver,
GatewayEndpointConfig,
)> {
log::info!("Loading config from file: {id}");
let config = Socks5Config::load_from_file(Some(id))
.tap_err(|_| log::warn!("Failed to load configuration file"))?;
let used_gateway = config.get_base().get_gateway_endpoint().clone();
let mut socks5_client = Socks5NymClient::new(config);
let socks5_client = Socks5NymClient::new(config);
log::info!("Starting socks5 client");
// Channel to send control messages to the socks5 client
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.0"
version = "1.1.1"
authors = ["Nym Technologies SA"]
edition = "2021"
@@ -18,6 +18,7 @@ serde_json = "1"
tokio = { version = "1.11", features = [ "net", "rt-multi-thread", "macros", "signal"] }
bip39 = "1.0.1"
anyhow = "1"
tap = "1"
nym-cli-commands = { path = "../../common/commands" }
logging = { path = "../../common/logging"}
+7 -3
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use network_defaults::NymNetworkDetails;
use nym_cli_commands::context::{create_signing_client, ClientArgs};
use nym_cli_commands::context::{create_query_client, create_signing_client, ClientArgs};
pub(crate) async fn execute(
global_args: ClientArgs,
@@ -19,18 +19,22 @@ pub(crate) async fn execute(
.await
}
Some(nym_cli_commands::validator::vesting::VestingScheduleCommands::Query(args)) => {
let address_from_args = args.address.clone();
nym_cli_commands::validator::vesting::query_vesting_schedule::query(
args,
create_signing_client(global_args, network_details)?,
create_query_client(network_details)?,
address_from_args,
)
.await
}
Some(nym_cli_commands::validator::vesting::VestingScheduleCommands::VestedBalance(
args,
)) => {
let address_from_args = args.address.clone();
nym_cli_commands::validator::vesting::balance::balance(
args,
create_signing_client(global_args, network_details)?,
create_query_client(network_details)?,
address_from_args,
)
.await
}