Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e60f7423f | |||
| de7c1b760f | |||
| b467ce3080 | |||
| dea4d197bd | |||
| 41b37984a4 | |||
| b541d1a034 | |||
| a9be8a6abd | |||
| 8de9f36b69 | |||
| 24e2eee547 | |||
| bdb724e9ca | |||
| 76ef50dc17 | |||
| f663623768 | |||
| 136202f329 | |||
| d25848e6f8 | |||
| 0084ba221b | |||
| 186896bb37 | |||
| df90ff8658 | |||
| bff079a3f8 | |||
| 9c361385a7 | |||
| a9983003d4 | |||
| e645d14005 | |||
| cbf9db91ab | |||
| 8304146195 | |||
| c5c16cd6b0 | |||
| 258fa41271 | |||
| 0a41834fbe | |||
| 9637afea85 | |||
| c8b454a085 | |||
| 81f7457e0e | |||
| 63ae568cc2 | |||
| f3c1ff02e2 |
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-7
@@ -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");
|
||||
|
||||
|
||||
+3
-2
@@ -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");
|
||||
|
||||
|
||||
+3
-7
@@ -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;
|
||||
|
||||
+3
-7
@@ -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");
|
||||
|
||||
|
||||
+2
-2
@@ -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,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,
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -90,7 +90,6 @@ async fn register_with_gateway(
|
||||
gateway.owner.clone(),
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
None,
|
||||
);
|
||||
gateway_client
|
||||
|
||||
@@ -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
@@ -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,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,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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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,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");
|
||||
|
||||
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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!(
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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/"
|
||||
@@ -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"
|
||||
@@ -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");
|
||||
|
||||
@@ -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,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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
Generated
+1
@@ -652,6 +652,7 @@ dependencies = [
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user