Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a096f9d54e | |||
| 5456b16e3b | |||
| 3b5eab5342 | |||
| 00f17c376a | |||
| 5283329914 | |||
| 29dd121282 | |||
| cd1d9d1a0d | |||
| b694e675e6 | |||
| 39cb6f6ec0 | |||
| ebaf99fadb | |||
| 85515fe16e | |||
| ec60966a85 | |||
| d1df407bac | |||
| 9ff4051b0f | |||
| fea9ec0f9f | |||
| d48d094672 | |||
| e1b511e788 | |||
| 1500d27ce5 | |||
| a0eba073ec | |||
| 2c3e716ba1 | |||
| b76051832f | |||
| c966680bba | |||
| b08dace119 | |||
| 58255857e5 | |||
| edf38e6356 | |||
| e258f62a26 | |||
| 940427776c | |||
| 2f7d9254b8 | |||
| 67321d7d04 | |||
| e38a20fb58 | |||
| 8e45e3e995 | |||
| 2f63d916b6 | |||
| 0b465e1f09 | |||
| f93aab2f5b | |||
| 7ff06cfe3e | |||
| 8c7e7dfcd1 | |||
| 31ca88fd7f | |||
| 51b29a9dcf | |||
| d58c2da463 | |||
| 5644726a7b | |||
| e9ba34ba52 | |||
| a33e848d6d | |||
| 3da9d2944b | |||
| e2c9f69ab9 | |||
| 8a21f183f9 | |||
| cb2a89dceb | |||
| 502e23b42c | |||
| 2676c68b77 | |||
| c5252f348d | |||
| bd8cea3ba3 | |||
| 7ddc8e80e6 | |||
| 03a974ec34 | |||
| 5c841de6ae | |||
| 9a8218905e | |||
| f6fca0ad37 | |||
| 9a8c5c3708 | |||
| 52136d727b | |||
| edd5c363bb | |||
| 028bee804b | |||
| a16c566719 | |||
| 667d5f3033 | |||
| b51aac6047 | |||
| a21be84833 | |||
| 49f7c48b1b | |||
| 6943271942 | |||
| a872b478d2 | |||
| 11051ba929 | |||
| 10e28c6e5c | |||
| 78af0bff71 | |||
| 3d2dee3e1c | |||
| 25212f23ad | |||
| 96c0256154 | |||
| 033333bb52 | |||
| bf207abe55 | |||
| 4069b0349d | |||
| bca20e1dfd | |||
| 1bc94d8fd4 | |||
| a269bd8289 | |||
| 52d06785fb | |||
| 1507938c65 | |||
| 41b37984a4 | |||
| b541d1a034 | |||
| bc25288d08 | |||
| 3a001e2f9f | |||
| 09cad3c589 | |||
| ab0180c5ce | |||
| a77f364466 | |||
| a9be8a6abd | |||
| 8de9f36b69 | |||
| 24e2eee547 |
@@ -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: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
|
||||
GIT_BRANCH_NAME: "${{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 }}"
|
||||
|
||||
@@ -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: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
|
||||
GIT_BRANCH_NAME: "${{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 }}"
|
||||
|
||||
@@ -2,6 +2,26 @@
|
||||
|
||||
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.3] (2022-12-13)
|
||||
|
||||
### Changed
|
||||
|
||||
- validator-api: can recover from shutdown during DKG process ([#1872])
|
||||
- clients: deduplicate gateway inititialization, part of work towards a rust-sdk
|
||||
- clients: keep all transmission lanes going at all times by making priority probabilistic
|
||||
|
||||
### Fixed
|
||||
|
||||
- network-requester: fix bug where websocket connection disconnect resulted in success error code
|
||||
- clients: fix a few panics handling the gateway-client
|
||||
- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884])
|
||||
|
||||
[#1872]: https://github.com/nymtech/nym/pull/1872
|
||||
[#1884]: https://github.com/nymtech/nym/pull/1884
|
||||
|
||||
|
||||
## [v1.1.2]
|
||||
|
||||
### Changed
|
||||
|
||||
Generated
+19
-36
@@ -586,7 +586,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"client-connections",
|
||||
"config",
|
||||
@@ -603,12 +603,14 @@ dependencies = [
|
||||
"pemstore",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sled",
|
||||
"tap",
|
||||
"task",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
@@ -2667,7 +2669,7 @@ checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"no-std-compat",
|
||||
"snafu 0.7.1",
|
||||
"snafu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3114,7 +3116,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -3168,7 +3170,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-connections",
|
||||
@@ -3208,7 +3210,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3255,7 +3257,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3297,7 +3299,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap 3.2.8",
|
||||
@@ -3329,7 +3331,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.0.2"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -3345,7 +3347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-connections",
|
||||
@@ -3371,7 +3373,7 @@ dependencies = [
|
||||
"proxy-helpers",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"snafu 0.6.10",
|
||||
"serde_json",
|
||||
"socks5-requests",
|
||||
"tap",
|
||||
"task",
|
||||
@@ -3412,7 +3414,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5004,9 +5006,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.83"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
||||
dependencies = [
|
||||
"itoa 1.0.1",
|
||||
"ryu",
|
||||
@@ -5194,16 +5196,6 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive 0.6.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.7.1"
|
||||
@@ -5211,18 +5203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5666,6 +5647,8 @@ dependencies = [
|
||||
"log",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6534,7 +6517,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -13,9 +13,11 @@ humantime-serde = "1.0"
|
||||
log = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.89"
|
||||
sled = { version = "0.34", optional = true }
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["time", "macros"]}
|
||||
url = { version ="2.2", features = ["serde"] }
|
||||
|
||||
# internal
|
||||
@@ -30,8 +32,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 +52,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::{debug, info};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use std::path::PathBuf;
|
||||
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,
|
||||
) -> Result<GatewayClient, ClientCoreError> {
|
||||
let gateway_id = self.gateway_config.gateway_id.clone();
|
||||
if gateway_id.is_empty() {
|
||||
return Err(ClientCoreError::GatewayIdUnknown);
|
||||
}
|
||||
let gateway_owner = self.gateway_config.gateway_owner.clone();
|
||||
if gateway_owner.is_empty() {
|
||||
return Err(ClientCoreError::GatewayOwnerUnknown);
|
||||
}
|
||||
let gateway_address = self.gateway_config.gateway_listener.clone();
|
||||
if gateway_address.is_empty() {
|
||||
return Err(ClientCoreError::GatwayAddressUnknown);
|
||||
}
|
||||
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
// 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(),
|
||||
shutdown,
|
||||
);
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.tap_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}")
|
||||
})?;
|
||||
Ok(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(),
|
||||
);
|
||||
}
|
||||
|
||||
debug!("Core client startup finished!");
|
||||
debug!("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!");
|
||||
|
||||
@@ -181,7 +191,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
// However it's still useful to alert the user that the gateway or the link to
|
||||
// the gateway can't keep up. Either due to insufficient bandwidth on the
|
||||
// client side, or that the gateway is overloaded.
|
||||
log::warn!("Failed to send: gateway appears to not keep up");
|
||||
log::warn!("Failed to send sphinx packet - gateway or connection to gatway can't keep up");
|
||||
}
|
||||
TrySendError::Closed(_) => {
|
||||
log::warn!("Failed to send cover message - channel closed");
|
||||
@@ -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;
|
||||
|
||||
@@ -489,46 +489,67 @@ where
|
||||
fn log_status_infrequent(&self) {
|
||||
if self.sending_delay_controller.current_multiplier() > 1 {
|
||||
log::warn!(
|
||||
"Unable to send packets fast enough - sending delay multiplier set to: {}",
|
||||
"Unable to send packets at the default rate - rate reduced by setting the delay multiplier set to: {}",
|
||||
self.sending_delay_controller.current_multiplier()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[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");
|
||||
|
||||
|
||||
+9
-3
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_connections::TransmissionLane;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
time::Duration,
|
||||
@@ -116,9 +116,15 @@ impl TransmissionBuffer {
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
}
|
||||
|
||||
// 2/3 chance to pick from the old lanes
|
||||
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
|
||||
let lanes = self.get_oldest_set();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
let rand = &mut rand::thread_rng();
|
||||
if rand.gen_ratio(2, 3) {
|
||||
let lanes = self.get_oldest_set();
|
||||
lanes.choose(rand).copied()
|
||||
} else {
|
||||
self.pick_random_lane().copied()
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
|
||||
|
||||
@@ -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;
|
||||
@@ -292,14 +293,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() => {
|
||||
@@ -307,21 +316,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,11 @@ pub fn missing_string_value() -> String {
|
||||
MISSING_VALUE.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub trait ClientCoreConfigTrait {
|
||||
fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config<T> {
|
||||
client: Client<T>,
|
||||
@@ -42,17 +46,29 @@ 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> ClientCoreConfigTrait for Config<T> {
|
||||
fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig {
|
||||
&self.client.gateway_endpoint
|
||||
}
|
||||
}
|
||||
|
||||
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 +133,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;
|
||||
}
|
||||
|
||||
@@ -135,8 +151,15 @@ impl<T: NymConfig> Config<T> {
|
||||
|
||||
pub fn set_high_default_traffic_volume(&mut self) {
|
||||
self.debug.average_packet_delay = Duration::from_millis(10);
|
||||
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2_000_000); // basically don't really send cover messages
|
||||
self.debug.message_sending_average_delay = Duration::from_millis(4); // 250 "real" messages / s
|
||||
// basically don't really send cover messages
|
||||
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2_000_000);
|
||||
// 250 "real" messages / s
|
||||
self.debug.message_sending_average_delay = Duration::from_millis(4);
|
||||
}
|
||||
|
||||
pub fn set_no_cover_traffic(&mut self) {
|
||||
self.debug.disable_loop_cover_traffic_stream = true;
|
||||
self.debug.disable_main_poisson_packet_distribution = true;
|
||||
}
|
||||
|
||||
pub fn set_custom_version(&mut self, version: &str) {
|
||||
@@ -203,7 +226,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -212,6 +235,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 +284,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 +304,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 +316,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 +327,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 +373,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 +443,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 +515,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 +523,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,10 +18,16 @@ 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")]
|
||||
NoGatewaysOnNetwork,
|
||||
#[error("Failed to setup gateway")]
|
||||
FailedToSetupGateway,
|
||||
#[error("List of validator apis is empty")]
|
||||
ListOfValidatorApisIsEmpty,
|
||||
#[error("Could not load existing gateway configuration: {0}")]
|
||||
@@ -27,6 +35,16 @@ pub enum ClientCoreError {
|
||||
#[error("The current network topology seem to be insufficient to route any packets through")]
|
||||
InsufficientNetworkTopology,
|
||||
|
||||
#[error("The gateway id is invalid - {0}")]
|
||||
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
|
||||
|
||||
#[error("The identity of the gateway is unknwown - did you run init?")]
|
||||
GatewayIdUnknown,
|
||||
#[error("The owner of the gateway is unknown - did you run init?")]
|
||||
GatewayOwnerUnknown,
|
||||
#[error("The address of the gateway is unknown - did you run init?")]
|
||||
GatwayAddressUnknown,
|
||||
|
||||
#[error("Unexpected exit")]
|
||||
UnexpectedExit,
|
||||
}
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Collection of initialization steps used by client implementations
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use rand::{rngs::OsRng, seq::SliceRandom, thread_rng};
|
||||
use tap::TapFallible;
|
||||
use url::Url;
|
||||
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use crypto::asymmetric::identity;
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use tap::TapFallible;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
client::key_manager::KeyManager,
|
||||
@@ -24,9 +16,9 @@ use crate::{
|
||||
error::ClientCoreError,
|
||||
};
|
||||
|
||||
pub async fn query_gateway_details(
|
||||
pub(super) async fn query_gateway_details(
|
||||
validator_servers: Vec<Url>,
|
||||
chosen_gateway_id: Option<&str>,
|
||||
chosen_gateway_id: Option<String>,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
let validator_api = validator_servers
|
||||
.choose(&mut thread_rng())
|
||||
@@ -59,7 +51,30 @@ pub async fn query_gateway_details(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register_with_gateway_and_store_keys<T>(
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
) -> Result<Arc<SharedKeys>, ClientCoreError> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
gateway.owner.clone(),
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
gateway_client
|
||||
.establish_connection()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
|
||||
let shared_keys = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
|
||||
Ok(shared_keys)
|
||||
}
|
||||
|
||||
pub(super) async fn register_with_gateway_and_store_keys<T>(
|
||||
gateway_details: gateway::Node,
|
||||
config: &Config<T>,
|
||||
) -> Result<(), ClientCoreError>
|
||||
@@ -78,72 +93,3 @@ where
|
||||
.store_keys(&pathfinder)
|
||||
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
) -> Result<Arc<SharedKeys>, ClientCoreError> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
gateway.owner.clone(),
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
None,
|
||||
);
|
||||
gateway_client
|
||||
.establish_connection()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
|
||||
let shared_keys = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
|
||||
Ok(shared_keys)
|
||||
}
|
||||
|
||||
pub fn show_address<T>(config: &Config<T>) -> Result<(), ClientCoreError>
|
||||
where
|
||||
T: config::NymConfig,
|
||||
{
|
||||
fn load_identity_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<encryption::KeyPair, ClientCoreError> {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||
Ok(sphinx_keypair)
|
||||
}
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder)?;
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
|
||||
|
||||
let client_recipient = Recipient::new(
|
||||
*identity_keypair.public_key(),
|
||||
*sphinx_keypair.public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(config.get_gateway_id())?,
|
||||
);
|
||||
|
||||
println!("\nThe address of this client is: {}", client_recipient);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Collection of initialization steps used by client implementations
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use nymsphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
|
||||
use serde::Serialize;
|
||||
use tap::TapFallible;
|
||||
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
|
||||
use crate::{
|
||||
config::{
|
||||
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
|
||||
GatewayEndpointConfig,
|
||||
},
|
||||
error::ClientCoreError,
|
||||
init::helpers::{query_gateway_details, register_with_gateway_and_store_keys},
|
||||
};
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
version: String,
|
||||
id: String,
|
||||
identity_key: String,
|
||||
encryption_key: String,
|
||||
gateway_id: String,
|
||||
gateway_listener: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
pub fn new<T>(config: &Config<T>, address: &Recipient) -> Self
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
Self {
|
||||
version: config.get_version().to_string(),
|
||||
id: config.get_id(),
|
||||
identity_key: address.identity().to_base58_string(),
|
||||
encryption_key: address.encryption_key().to_base58_string(),
|
||||
gateway_id: config.get_gateway_id(),
|
||||
gateway_listener: config.get_gateway_listener(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Version: {}", self.version)?;
|
||||
writeln!(f, "ID: {}", self.id)?;
|
||||
writeln!(f, "Identity key: {}", self.identity_key)?;
|
||||
writeln!(f, "Encryption: {}", self.encryption_key)?;
|
||||
writeln!(f, "Gateway ID: {}", self.gateway_id)?;
|
||||
write!(f, "Gateway: {}", self.gateway_listener)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function for setting up the gateway for a client. Depending on the arguments given
|
||||
/// it will do the sensible thing.
|
||||
pub async fn setup_gateway<C: NymConfig + ClientCoreConfigTrait, T: NymConfig>(
|
||||
register_gateway: bool,
|
||||
user_chosen_gateway_id: Option<String>,
|
||||
config: &Config<T>,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
let id = config.get_id();
|
||||
if register_gateway {
|
||||
register_with_gateway(user_chosen_gateway_id, config).await
|
||||
} else if let Some(user_chosen_gateway_id) = user_chosen_gateway_id {
|
||||
config_gateway_with_existing_keys(user_chosen_gateway_id, config).await
|
||||
} else {
|
||||
reuse_existing_gateway_config::<C>(&id)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Saves keys to disk, specified by the paths in `config`.
|
||||
pub async fn register_with_gateway<T: NymConfig>(
|
||||
user_chosen_gateway_id: Option<String>,
|
||||
config: &Config<T>,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
println!("Configuring gateway");
|
||||
let gateway =
|
||||
query_gateway_details(config.get_validator_api_endpoints(), user_chosen_gateway_id).await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
register_with_gateway_and_store_keys(gateway.clone(), config).await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
}
|
||||
|
||||
/// Set the gateway using the usual procedue of querying the validator-api, but don't register or
|
||||
/// create any keys.
|
||||
/// This assumes that the user knows what they are doing, and that the existing keys are valid for
|
||||
/// the gateway being used
|
||||
pub async fn config_gateway_with_existing_keys<T: NymConfig>(
|
||||
user_chosen_gateway_id: String,
|
||||
config: &Config<T>,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
println!("Using gateway provided by user, keeping existing keys");
|
||||
let gateway = query_gateway_details(
|
||||
config.get_validator_api_endpoints(),
|
||||
Some(user_chosen_gateway_id),
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
}
|
||||
|
||||
/// Read and reuse the existing gateway configuration from a file that was generate earlier.
|
||||
pub fn reuse_existing_gateway_config<T: NymConfig + ClientCoreConfigTrait>(
|
||||
id: &str,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
T::load_from_file(Some(id))
|
||||
.map(|existing_config| existing_config.get_gateway_endpoint().clone())
|
||||
.map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the client address by loading the keys from stored files.
|
||||
pub fn get_client_address_from_stored_keys<T>(
|
||||
config: &Config<T>,
|
||||
) -> Result<Recipient, ClientCoreError>
|
||||
where
|
||||
T: config::NymConfig,
|
||||
{
|
||||
fn load_identity_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<encryption::KeyPair, ClientCoreError> {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||
Ok(sphinx_keypair)
|
||||
}
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder)?;
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
|
||||
|
||||
let client_recipient = Recipient::new(
|
||||
*identity_keypair.public_key(),
|
||||
*sphinx_keypair.public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(config.get_gateway_id())?,
|
||||
);
|
||||
|
||||
Ok(client_recipient)
|
||||
}
|
||||
|
||||
pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) {
|
||||
match std::fs::File::create(output_file) {
|
||||
Ok(file) => match serde_json::to_writer_pretty(file, init_results) {
|
||||
Ok(_) => println!("Saved: {}", output_file),
|
||||
Err(err) => eprintln!("Could not save {}: {}", output_file, err),
|
||||
},
|
||||
Err(err) => eprintln!("Could not save {}: {}", output_file, err),
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,7 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
|
||||
&coconut_api_clients,
|
||||
)
|
||||
.await?;
|
||||
println!("Signature: {:?}", signature.to_bs58());
|
||||
shared_storage
|
||||
.insert_coconut_credential(
|
||||
state.amount.to_string(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -26,7 +26,9 @@ log = "0.4" # self explanatory
|
||||
pretty_env_logger = "0.4" # for formatting log messages
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
||||
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
|
||||
serde_json = "1.0"
|
||||
sled = "0.34" # for storage of replySURB decryption keys
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
tokio-tungstenite = "0.14" # websocket
|
||||
@@ -51,7 +53,6 @@ topology = { path = "../../common/topology" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
websocket-requests = { path = "websocket-requests" }
|
||||
tap = "1.0.1"
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::template::config_template;
|
||||
use client_core::config::Config as BaseConfig;
|
||||
pub use client_core::config::MISSING_VALUE;
|
||||
use client_core::config::{ClientCoreConfigTrait, Config as BaseConfig};
|
||||
use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
|
||||
use config::NymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -27,6 +27,10 @@ impl SocketType {
|
||||
_ => SocketType::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_websocket(self) -> bool {
|
||||
matches!(self, SocketType::WebSocket)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
@@ -69,6 +73,12 @@ impl NymConfig for Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCoreConfigTrait for Config {
|
||||
fn get_gateway_endpoint(&self) -> &client_core::config::GatewayEndpointConfig {
|
||||
self.base.get_gateway_endpoint()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||
Config {
|
||||
|
||||
+148
-424
@@ -1,219 +1,58 @@
|
||||
// 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();
|
||||
let mut client_config =
|
||||
validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
let nymd_url = self
|
||||
.config
|
||||
let nymd_url = config
|
||||
.get_base()
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nymd validator endpoint provided");
|
||||
let api_url = self
|
||||
.config
|
||||
let api_url = config
|
||||
.get_base()
|
||||
.get_validator_api_endpoints()
|
||||
.pop()
|
||||
@@ -227,163 +66,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!(
|
||||
@@ -403,117 +127,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!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use clap::Args;
|
||||
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
|
||||
use config::NymConfig;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use tap::TapFallible;
|
||||
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::ClientError,
|
||||
};
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -46,11 +51,19 @@ pub(crate) struct Init {
|
||||
#[clap(long, hidden = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
#[clap(long)]
|
||||
output_json: bool,
|
||||
}
|
||||
|
||||
impl From<Init> for OverrideConfig {
|
||||
@@ -61,6 +74,7 @@ impl From<Init> for OverrideConfig {
|
||||
disable_socket: init_config.disable_socket,
|
||||
port: init_config.port,
|
||||
fastmode: init_config.fastmode,
|
||||
no_cover: init_config.no_cover,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
@@ -68,7 +82,30 @@ impl From<Init> for OverrideConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: client_core::init::InitResults,
|
||||
client_listening_port: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient) -> Self {
|
||||
Self {
|
||||
client_core: client_core::init::InitResults::new(config.get_base(), address),
|
||||
client_listening_port: config.get_listening_port().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.client_core)?;
|
||||
write!(f, "Client listening port: {}", self.client_listening_port)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
@@ -92,25 +129,44 @@ pub(crate) async fn execute(args: &Init) {
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = args.gateway.as_deref();
|
||||
let user_chosen_gateway_id = args.gateway.clone();
|
||||
|
||||
let mut config = Config::new(id);
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
// Load and potentially override config
|
||||
let mut config = override_config(Config::new(id), OverrideConfig::from(args.clone()));
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = client_core::init::setup_gateway::<Config, _>(
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to setup gateway\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
config.save_to_file(None).tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{}", init_results);
|
||||
|
||||
// Output summary to a json file, if specified
|
||||
if args.output_json {
|
||||
client_core::init::output_to_json(&init_results, "client_init_results.json");
|
||||
}
|
||||
|
||||
println!("\nThe address of this client is: {}\n", address);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config) {
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Using gateway: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
@@ -119,63 +175,5 @@ pub(crate) async fn execute(args: &Init) {
|
||||
"Gateway listener: {}",
|
||||
config.get_base().get_gateway_listener()
|
||||
);
|
||||
println!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to show address\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_gateway(
|
||||
id: &str,
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Config,
|
||||
) -> Result<GatewayEndpoint, 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.
|
||||
println!("Configuring gateway");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||
.await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
// valid for the gateway being used
|
||||
println!("Using gateway provided by user, keeping existing keys");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})?;
|
||||
|
||||
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||
}
|
||||
println!("Client configuration completed.\n");
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ pub(crate) struct OverrideConfig {
|
||||
disable_socket: bool,
|
||||
port: Option<u16>,
|
||||
fastmode: bool,
|
||||
no_cover: bool,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: bool,
|
||||
@@ -89,7 +90,7 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
|
||||
let bin_name = "nym-native-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
|
||||
@@ -141,6 +142,10 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
if args.no_cover {
|
||||
config.get_base_mut().set_no_cover_traffic();
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -39,6 +39,15 @@ pub(crate) struct Run {
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
@@ -53,7 +62,8 @@ impl From<Run> for OverrideConfig {
|
||||
api_validators: run_config.api_validators,
|
||||
disable_socket: run_config.disable_socket,
|
||||
port: run_config.port,
|
||||
fastmode: false,
|
||||
fastmode: run_config.fastmode,
|
||||
no_cover: run_config.no_cover,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
}
|
||||
@@ -98,5 +108,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,17 @@
|
||||
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,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
@@ -19,7 +19,8 @@ pin-project = "1.0"
|
||||
pretty_env_logger = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
|
||||
snafu = "0.6"
|
||||
serde_json = "1.0.89"
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
|
||||
url = "2.2"
|
||||
@@ -46,7 +47,6 @@ task = { path = "../../common/task" }
|
||||
topology = { path = "../../common/topology" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
tap = "1.0.1"
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::template::config_template;
|
||||
use client_core::config::Config as BaseConfig;
|
||||
pub use client_core::config::MISSING_VALUE;
|
||||
use client_core::config::{ClientCoreConfigTrait, Config as BaseConfig};
|
||||
use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
|
||||
use config::NymConfig;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
@@ -52,6 +52,12 @@ impl NymConfig for Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCoreConfigTrait for Config {
|
||||
fn get_gateway_endpoint(&self) -> &client_core::config::GatewayEndpointConfig {
|
||||
self.base.get_gateway_endpoint()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S, provider_mix_address: S) -> Self {
|
||||
Config {
|
||||
@@ -60,11 +66,13 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.socks5.listening_port = port;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_provider_mix_address(mut self, address: String) -> Self {
|
||||
self.socks5.provider_mix_address = address;
|
||||
self
|
||||
|
||||
@@ -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,146 +50,19 @@ 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();
|
||||
let mut client_config =
|
||||
validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
let nymd_url = self
|
||||
.config
|
||||
let nymd_url = config
|
||||
.get_base()
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nymd validator endpoint provided");
|
||||
let api_url = self
|
||||
.config
|
||||
let api_url = config
|
||||
.get_base()
|
||||
.get_validator_api_endpoints()
|
||||
.pop()
|
||||
@@ -225,133 +76,63 @@ 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,
|
||||
mut shutdown: ShutdownListener,
|
||||
config: &Config,
|
||||
client_input: ClientInput,
|
||||
client_output: ClientOutput,
|
||||
self_address: Recipient,
|
||||
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 {
|
||||
// Ideally we should have a fully fledged task manager to check for errors in all
|
||||
// tasks.
|
||||
// However, pragmatically, we start out by at least reporting errors for some of the
|
||||
// tasks that interact with the outside world and can fail in normal operation, such as
|
||||
// network issues.
|
||||
// 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)
|
||||
.await
|
||||
{
|
||||
shutdown.send_we_stopped(Box::new(res));
|
||||
}
|
||||
});
|
||||
task::spawn_with_report_error(
|
||||
async move {
|
||||
sphinx_socks
|
||||
.serve(
|
||||
input_sender,
|
||||
received_buffer_request_sender,
|
||||
connection_command_sender,
|
||||
)
|
||||
.await
|
||||
},
|
||||
shutdown,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
let mut shutdown = self
|
||||
.start()
|
||||
.await
|
||||
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
|
||||
pub async fn run_forever(self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut shutdown = self.start().await?;
|
||||
|
||||
let res = wait_for_signal_and_error(&mut shutdown).await;
|
||||
|
||||
@@ -367,14 +148,11 @@ 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>> {
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
// Start the main task
|
||||
let mut shutdown = self
|
||||
.start()
|
||||
.await
|
||||
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
|
||||
let mut shutdown = self.start().await?;
|
||||
|
||||
let res = tokio::select! {
|
||||
biased;
|
||||
@@ -410,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use clap::Args;
|
||||
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
|
||||
use config::NymConfig;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use tap::TapFallible;
|
||||
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -46,11 +51,19 @@ pub(crate) struct Init {
|
||||
#[clap(long, hidden = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
#[clap(long)]
|
||||
output_json: bool,
|
||||
}
|
||||
|
||||
impl From<Init> for OverrideConfig {
|
||||
@@ -60,13 +73,37 @@ impl From<Init> for OverrideConfig {
|
||||
api_validators: init_config.api_validators,
|
||||
port: init_config.port,
|
||||
fastmode: init_config.fastmode,
|
||||
no_cover: init_config.no_cover,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: client_core::init::InitResults,
|
||||
socks5_listening_port: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient) -> Self {
|
||||
Self {
|
||||
client_core: client_core::init::InitResults::new(config.get_base(), address),
|
||||
socks5_listening_port: config.get_listening_port().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.client_core)?;
|
||||
write!(f, "SOCKS5 listening port: {}", self.socks5_listening_port)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
@@ -91,25 +128,47 @@ pub(crate) async fn execute(args: &Init) {
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = args.gateway.as_deref();
|
||||
let user_chosen_gateway_id = args.gateway.clone();
|
||||
|
||||
let mut config = Config::new(id, provider_address);
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
// Load and potentially override config
|
||||
let mut config = override_config(
|
||||
Config::new(id, provider_address),
|
||||
OverrideConfig::from(args.clone()),
|
||||
);
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = client_core::init::setup_gateway::<Config, _>(
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to setup gateway\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
config.save_to_file(None).tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{}", init_results);
|
||||
|
||||
// Output summary to a json file, if specified
|
||||
if args.output_json {
|
||||
client_core::init::output_to_json(&init_results, "socks5_client_init_results.json");
|
||||
}
|
||||
|
||||
println!("\nThe address of this client is: {}\n", address);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config) {
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Using gateway: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
@@ -118,62 +177,5 @@ pub(crate) async fn execute(args: &Init) {
|
||||
"Gateway listener: {}",
|
||||
config.get_base().get_gateway_listener()
|
||||
);
|
||||
println!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to show address\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_gateway(
|
||||
id: &str,
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Config,
|
||||
) -> Result<GatewayEndpoint, 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.
|
||||
println!("Configuring gateway");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||
.await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
// valid for the gateway being used
|
||||
println!("Using gateway provided by user, keeping existing keys");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})?;
|
||||
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||
}
|
||||
println!("Client configuration completed.\n");
|
||||
}
|
||||
|
||||
@@ -83,16 +83,17 @@ pub(crate) struct OverrideConfig {
|
||||
api_validators: Option<String>,
|
||||
port: Option<u16>,
|
||||
fastmode: bool,
|
||||
no_cover: bool,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send>> {
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let bin_name = "nym-socks5-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
|
||||
@@ -136,6 +137,10 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
if args.no_cover {
|
||||
config.get_base_mut().set_no_cover_traffic();
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,15 @@ pub(crate) struct Run {
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
@@ -56,8 +65,8 @@ impl From<Run> for OverrideConfig {
|
||||
nymd_validators: run_config.nymd_validators,
|
||||
api_validators: run_config.api_validators,
|
||||
port: run_config.port,
|
||||
fastmode: false,
|
||||
|
||||
fastmode: run_config.fastmode,
|
||||
no_cover: run_config.no_cover,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
}
|
||||
@@ -86,7 +95,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send>> {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -13,7 +13,7 @@ pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error + Send>> {
|
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use super::authentication::{AuthenticationMethods, Authenticator, User};
|
||||
use super::request::{SocksCommand, SocksRequest};
|
||||
use super::types::{ResponseCode, SocksProxyError};
|
||||
use super::{RESERVED, SOCKS_VERSION};
|
||||
use super::types::{ResponseCodeV4, ResponseCodeV5, SocksProxyError};
|
||||
use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION};
|
||||
use client_connections::{LaneQueueLengths, TransmissionLane};
|
||||
use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
|
||||
use futures::channel::mpsc;
|
||||
@@ -129,13 +129,13 @@ impl AsyncWrite for StreamState {
|
||||
/// A client connecting to the Socks proxy server, because
|
||||
/// it wants to make a Nym-protected outbound request. Typically, this is
|
||||
/// something like e.g. a wallet app running on your laptop connecting to
|
||||
/// SphinxSocksServer.
|
||||
/// `SphinxSocksServer`.
|
||||
pub(crate) struct SocksClient {
|
||||
controller_sender: ControllerSender,
|
||||
stream: StreamState,
|
||||
auth_nmethods: u8,
|
||||
authenticator: Authenticator,
|
||||
socks_version: u8,
|
||||
socks_version: Option<SocksVersion>,
|
||||
input_sender: InputMessageSender,
|
||||
connection_id: ConnectionId,
|
||||
service_provider: Recipient,
|
||||
@@ -158,15 +158,14 @@ impl Drop for SocksClient {
|
||||
}
|
||||
|
||||
impl SocksClient {
|
||||
/// Create a new SOCKClient
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
stream: TcpStream,
|
||||
authenticator: Authenticator,
|
||||
input_sender: InputMessageSender,
|
||||
service_provider: Recipient,
|
||||
service_provider: &Recipient,
|
||||
controller_sender: ControllerSender,
|
||||
self_address: Recipient,
|
||||
self_address: &Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
mut shutdown_listener: ShutdownListener,
|
||||
) -> Self {
|
||||
@@ -180,11 +179,11 @@ impl SocksClient {
|
||||
connection_id,
|
||||
stream: StreamState::Available(stream),
|
||||
auth_nmethods: 0,
|
||||
socks_version: 0,
|
||||
socks_version: None,
|
||||
authenticator,
|
||||
input_sender,
|
||||
service_provider,
|
||||
self_address,
|
||||
service_provider: *service_provider,
|
||||
self_address: *self_address,
|
||||
started_proxy: false,
|
||||
lane_queue_lengths,
|
||||
shutdown_listener,
|
||||
@@ -196,13 +195,45 @@ impl SocksClient {
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
pub async fn send_error(&mut self, err: SocksProxyError) -> Result<(), SocksProxyError> {
|
||||
let error_text = format!("{}", err);
|
||||
let Some(ref version) = self.socks_version else {
|
||||
log::error!("Trying to send error without knowing the version");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match version {
|
||||
SocksVersion::V4 => {
|
||||
let response = ResponseCodeV4::RequestRejected;
|
||||
self.send_error_v4(response).await
|
||||
}
|
||||
SocksVersion::V5 => {
|
||||
let response = if error_text.contains("Host") {
|
||||
ResponseCodeV5::HostUnreachable
|
||||
} else if error_text.contains("Network") {
|
||||
ResponseCodeV5::NetworkUnreachable
|
||||
} else if error_text.contains("ttl") {
|
||||
ResponseCodeV5::TtlExpired
|
||||
} else {
|
||||
ResponseCodeV5::Failure
|
||||
};
|
||||
self.send_error_v5(response).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send an error back to the client
|
||||
pub async fn error(&mut self, r: ResponseCode) -> Result<(), SocksProxyError> {
|
||||
self.stream.write_all(&[5, r as u8]).await?;
|
||||
pub async fn send_error_v4(&mut self, r: ResponseCodeV4) -> Result<(), SocksProxyError> {
|
||||
self.stream.write_all(&[SOCKS4_VERSION, r as u8]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shutdown the TcpStream to the client and end the session
|
||||
pub async fn send_error_v5(&mut self, r: ResponseCodeV5) -> Result<(), SocksProxyError> {
|
||||
self.stream.write_all(&[SOCKS5_VERSION, r as u8]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shutdown the `TcpStream` to the client and end the session
|
||||
pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> {
|
||||
info!("client is shutting down its TCP stream");
|
||||
self.stream.shutdown().await?;
|
||||
@@ -214,25 +245,27 @@ impl SocksClient {
|
||||
/// is in use and that the client is authenticated, then runs the request.
|
||||
pub async fn run(&mut self) -> Result<(), SocksProxyError> {
|
||||
debug!("New connection from: {}", self.stream.peer_addr()?.ip());
|
||||
let mut header = [0u8; 2];
|
||||
|
||||
// Read a byte from the stream and determine the version being requested
|
||||
let mut header = [0u8];
|
||||
self.stream.read_exact(&mut header).await?;
|
||||
|
||||
self.socks_version = header[0];
|
||||
self.auth_nmethods = header[1];
|
||||
self.socks_version = match SocksVersion::try_from(header[0]) {
|
||||
Ok(version) => Some(version),
|
||||
Err(_err) => {
|
||||
warn!("Init: Unsupported version: SOCKS{}", header[0]);
|
||||
return self.shutdown().await;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle SOCKS4 requests
|
||||
if header[0] != SOCKS_VERSION {
|
||||
warn!("Init: Unsupported version: SOCKS{}", self.socks_version);
|
||||
self.shutdown().await
|
||||
}
|
||||
// Valid SOCKS5
|
||||
else {
|
||||
// Authenticate w/ client
|
||||
self.authenticate().await?;
|
||||
// Handle requests
|
||||
self.handle_request().await
|
||||
if self.socks_version == Some(SocksVersion::V5) {
|
||||
let mut auth = [0u8];
|
||||
self.stream.read_exact(&mut auth).await?;
|
||||
self.auth_nmethods = auth[0];
|
||||
self.authenticate_socks5().await?;
|
||||
}
|
||||
|
||||
self.handle_request().await
|
||||
}
|
||||
|
||||
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
|
||||
@@ -295,8 +328,17 @@ impl SocksClient {
|
||||
async fn handle_request(&mut self) -> Result<(), SocksProxyError> {
|
||||
debug!("Handling CONNECT Command");
|
||||
|
||||
let request = SocksRequest::from_stream(&mut self.stream).await?;
|
||||
let remote_address = request.to_string();
|
||||
let version = self
|
||||
.socks_version
|
||||
.as_ref()
|
||||
.expect("Must read version before parsing request");
|
||||
|
||||
let request = match version {
|
||||
SocksVersion::V4 => SocksRequest::from_stream_socks4(&mut self.stream).await?,
|
||||
SocksVersion::V5 => SocksRequest::from_stream_socks5(&mut self.stream).await?,
|
||||
};
|
||||
|
||||
let remote_address = request.address_string();
|
||||
|
||||
// setup for receiving from the mixnet
|
||||
let (mix_sender, mix_receiver) = mpsc::unbounded();
|
||||
@@ -305,7 +347,10 @@ impl SocksClient {
|
||||
// Use the Proxy to connect to the specified addr/port
|
||||
SocksCommand::Connect => {
|
||||
trace!("Connecting to: {:?}", remote_address.clone());
|
||||
self.acknowledge_socks5().await;
|
||||
match version {
|
||||
SocksVersion::V4 => self.acknowledge_socks4().await,
|
||||
SocksVersion::V5 => self.acknowledge_socks5().await,
|
||||
}
|
||||
|
||||
self.started_proxy = true;
|
||||
self.controller_sender
|
||||
@@ -336,8 +381,8 @@ impl SocksClient {
|
||||
async fn acknowledge_socks5(&mut self) {
|
||||
self.stream
|
||||
.write_all(&[
|
||||
SOCKS_VERSION,
|
||||
ResponseCode::Success as u8,
|
||||
SOCKS5_VERSION,
|
||||
ResponseCodeV5::Success as u8,
|
||||
RESERVED,
|
||||
1,
|
||||
127,
|
||||
@@ -351,13 +396,30 @@ impl SocksClient {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Writes a Socks4 header back to the requesting client's TCP stream,
|
||||
async fn acknowledge_socks4(&mut self) {
|
||||
self.stream
|
||||
.write_all(&[
|
||||
0, //SOCKS4_VERSION,
|
||||
ResponseCodeV4::Granted as u8,
|
||||
0,
|
||||
0,
|
||||
127,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Authenticate the incoming request. Each request is checked for its
|
||||
/// authentication method. A user/password request will extract the
|
||||
/// username and password from the stream, then check with the Authenticator
|
||||
/// to see if the resulting user is allowed.
|
||||
///
|
||||
/// A lot of this could probably be put into the `SocksRequest::from_stream()`
|
||||
/// constructor, and/or cleaned up with tokio::codec. It's mostly just
|
||||
/// constructor, and/or cleaned up with `tokio::codec`. It's mostly just
|
||||
/// read-a-byte-or-two. The bytes being extracted look like this:
|
||||
///
|
||||
/// +----+------+----------+------+------------+
|
||||
@@ -369,7 +431,7 @@ impl SocksClient {
|
||||
/// Pulling out the stream code into its own home, and moving the if/else logic
|
||||
/// into the Authenticator (where it'll be more easily testable)
|
||||
/// would be a good next step.
|
||||
async fn authenticate(&mut self) -> Result<(), SocksProxyError> {
|
||||
async fn authenticate_socks5(&mut self) -> Result<(), SocksProxyError> {
|
||||
debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip());
|
||||
// Get valid auth methods
|
||||
let methods = self.get_available_methods().await?;
|
||||
@@ -378,7 +440,7 @@ impl SocksClient {
|
||||
let mut response = [0u8; 2];
|
||||
|
||||
// Set the version in the response
|
||||
response[0] = SOCKS_VERSION;
|
||||
response[0] = SOCKS5_VERSION;
|
||||
if methods.contains(&(AuthenticationMethods::UserPass as u8)) {
|
||||
// Set the default auth method (NO AUTH)
|
||||
response[1] = AuthenticationMethods::UserPass as u8;
|
||||
@@ -414,11 +476,11 @@ impl SocksClient {
|
||||
// Authenticate passwords
|
||||
if self.authenticator.is_allowed(&user) {
|
||||
debug!("Access Granted. User: {}", user.username);
|
||||
let response = [1, ResponseCode::Success as u8];
|
||||
let response = [1, ResponseCodeV5::Success as u8];
|
||||
self.stream.write_all(&response).await?;
|
||||
} else {
|
||||
debug!("Access Denied. User: {}", user.username);
|
||||
let response = [1, ResponseCode::Failure as u8];
|
||||
let response = [1, ResponseCodeV5::Failure as u8];
|
||||
self.stream.write_all(&response).await?;
|
||||
|
||||
// Shutdown
|
||||
@@ -437,7 +499,7 @@ impl SocksClient {
|
||||
response[1] = AuthenticationMethods::NoMethods as u8;
|
||||
self.stream.write_all(&response).await?;
|
||||
self.shutdown().await?;
|
||||
Err(ResponseCode::Failure.into())
|
||||
Err(ResponseCodeV5::Failure.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ impl MixnetResponseListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use self::types::SocksProxyError;
|
||||
|
||||
pub mod authentication;
|
||||
mod client;
|
||||
pub(crate) mod mixnet_responses;
|
||||
@@ -9,6 +13,27 @@ pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
/// Version of socks
|
||||
const SOCKS_VERSION: u8 = 0x05;
|
||||
const SOCKS4_VERSION: u8 = 0x04;
|
||||
const SOCKS5_VERSION: u8 = 0x05;
|
||||
|
||||
const RESERVED: u8 = 0x00;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum SocksVersion {
|
||||
V4 = 0x04,
|
||||
V5 = 0x05,
|
||||
}
|
||||
|
||||
pub struct InvalidSocksVersion;
|
||||
|
||||
impl TryFrom<u8> for SocksVersion {
|
||||
type Error = SocksProxyError;
|
||||
|
||||
fn try_from(version: u8) -> Result<Self, Self::Error> {
|
||||
match version {
|
||||
SOCKS4_VERSION => Ok(Self::V4),
|
||||
SOCKS5_VERSION => Ok(Self::V5),
|
||||
_ => Err(SocksProxyError::UnsupportedProxyVersion(version)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::types::{AddrType, ResponseCode, SocksProxyError};
|
||||
use super::{utils as socks_utils, SOCKS_VERSION};
|
||||
use crate::socks::SOCKS4_VERSION;
|
||||
|
||||
use super::types::{AddrType, ResponseCodeV5, SocksProxyError};
|
||||
use super::{utils as socks_utils, SOCKS5_VERSION};
|
||||
use log::*;
|
||||
use std::fmt::{self, Display};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
@@ -15,80 +17,114 @@ pub(crate) struct SocksRequest {
|
||||
}
|
||||
|
||||
impl SocksRequest {
|
||||
/// Parse a SOCKS5 request from a TcpStream
|
||||
pub async fn from_stream<R>(stream: &mut R) -> Result<Self, SocksProxyError>
|
||||
/// Parse a SOCKS4 request from a `TcpStream`
|
||||
/// From documents at:
|
||||
/// - SOCKS4: https://www.openssh.com/txt/socks4.protocol
|
||||
/// - SOCKS4a: https://www.openssh.com/txt/socks4a.protocol
|
||||
pub async fn from_stream_socks4<R>(stream: &mut R) -> Result<Self, SocksProxyError>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
log::trace!("read from stream socks4");
|
||||
|
||||
let mut packet = [0u8; 3];
|
||||
stream.read_exact(&mut packet).await?;
|
||||
|
||||
// CD (command)
|
||||
let Some(command) = SocksCommand::from(packet[0] as usize) else {
|
||||
log::warn!("Invalid Command");
|
||||
return Err(ResponseCodeV5::CommandNotSupported.into());
|
||||
};
|
||||
|
||||
// DSTPORT
|
||||
let mut port = [0u8; 2];
|
||||
port.copy_from_slice(&packet[1..]);
|
||||
let port = merge_u8_into_u16(port[0], port[1]);
|
||||
|
||||
// DSTIP
|
||||
let mut ip = [0u8; 4];
|
||||
stream.read_exact(&mut ip).await?;
|
||||
|
||||
// USERID
|
||||
let _userid = read_until_zero(stream).await;
|
||||
|
||||
// SOCKS4a extension
|
||||
// https://www.openssh.com/txt/socks4a.protocol
|
||||
// If the IP is 0.0.0.x with x nonzero, read the domain name
|
||||
let (addr, addr_type) = if ip[..3] == [0, 0, 0] && ip[3] != 0 {
|
||||
(read_until_zero(stream).await?, AddrType::Domain)
|
||||
} else {
|
||||
(ip.to_vec(), AddrType::V4)
|
||||
};
|
||||
|
||||
// Return parsed request
|
||||
Ok(SocksRequest {
|
||||
version: SOCKS4_VERSION,
|
||||
command,
|
||||
addr_type,
|
||||
addr,
|
||||
port,
|
||||
})
|
||||
}
|
||||
/// Parse a SOCKS5 request from a `TcpStream`
|
||||
/// From: https://www.rfc-editor.org/rfc/rfc1928
|
||||
pub async fn from_stream_socks5<R>(stream: &mut R) -> Result<Self, SocksProxyError>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
log::info!("read from stream socks5");
|
||||
|
||||
let mut packet = [0u8; 4];
|
||||
// Read a byte from the stream and determine the version being requested
|
||||
stream.read_exact(&mut packet).await?;
|
||||
|
||||
if packet[0] != SOCKS_VERSION {
|
||||
warn!("from_stream Unsupported version: SOCKS{}", packet[0]);
|
||||
// VER
|
||||
if packet[0] != SOCKS5_VERSION {
|
||||
warn!("Unsupported version: SOCKS{}", packet[0]);
|
||||
return Err(SocksProxyError::UnsupportedProxyVersion(packet[0]));
|
||||
}
|
||||
|
||||
// Get command
|
||||
let mut command: SocksCommand = SocksCommand::Connect;
|
||||
match SocksCommand::from(packet[1] as usize) {
|
||||
Some(com) => {
|
||||
command = com;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
warn!("Invalid Command");
|
||||
Err(ResponseCode::CommandNotSupported)
|
||||
}
|
||||
}?;
|
||||
// CMD
|
||||
let Some(command) = SocksCommand::from(packet[1] as usize) else {
|
||||
warn!("Invalid Command");
|
||||
return Err(ResponseCodeV5::CommandNotSupported.into());
|
||||
};
|
||||
|
||||
// DST.address
|
||||
// RSV
|
||||
// packet[2] is reserved
|
||||
|
||||
let mut addr_type: AddrType = AddrType::V6;
|
||||
match AddrType::from(packet[3] as usize) {
|
||||
Some(addr) => {
|
||||
addr_type = addr;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
error!("No Addr");
|
||||
Err(ResponseCode::AddrTypeNotSupported)
|
||||
}
|
||||
}?;
|
||||
// ATYP
|
||||
let Some(addr_type) = AddrType::from(packet[3] as usize) else {
|
||||
error!("No Addr");
|
||||
return Err(ResponseCodeV5::AddrTypeNotSupported.into())
|
||||
};
|
||||
|
||||
trace!("Getting Addr");
|
||||
// Get Addr from addr_type and stream
|
||||
let addr: Result<Vec<u8>, SocksProxyError> = match addr_type {
|
||||
// DST.ADDR
|
||||
let addr = match addr_type {
|
||||
AddrType::Domain => {
|
||||
let mut domain_length = [0u8; 1];
|
||||
let mut domain_length = [0u8];
|
||||
stream.read_exact(&mut domain_length).await?;
|
||||
|
||||
let mut domain = vec![0u8; domain_length[0] as usize];
|
||||
stream.read_exact(&mut domain).await?;
|
||||
|
||||
Ok(domain)
|
||||
domain
|
||||
}
|
||||
AddrType::V4 => {
|
||||
let mut addr = [0u8; 4];
|
||||
stream.read_exact(&mut addr).await?;
|
||||
Ok(addr.to_vec())
|
||||
addr.to_vec()
|
||||
}
|
||||
AddrType::V6 => {
|
||||
let mut addr = [0u8; 16];
|
||||
stream.read_exact(&mut addr).await?;
|
||||
Ok(addr.to_vec())
|
||||
addr.to_vec()
|
||||
}
|
||||
};
|
||||
|
||||
let addr = addr?;
|
||||
|
||||
// read DST.port
|
||||
// DST.PORT
|
||||
let mut port = [0u8; 2];
|
||||
stream.read_exact(&mut port).await?;
|
||||
// Merge two u8s into u16
|
||||
let port = (u16::from(port[0]) << 8) | u16::from(port[1]);
|
||||
let port = merge_u8_into_u16(port[0], port[1]);
|
||||
|
||||
// Return parsed request
|
||||
Ok(SocksRequest {
|
||||
version: packet[0],
|
||||
command,
|
||||
@@ -97,14 +133,18 @@ impl SocksRequest {
|
||||
port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Print out the address and port to a String.
|
||||
/// This might return domain:port, ipv6:port, or ipv4:port.
|
||||
pub fn address_string(&self) -> String {
|
||||
let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr);
|
||||
format!("{}:{}", address, self.port)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SocksRequest {
|
||||
/// Print out the address and port to a String.
|
||||
/// This might return domain:port, ipv6:port, or ipv4:port.
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr);
|
||||
write!(f, "{}:{}", address, self.port)
|
||||
write!(f, "{}", self.address_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,3 +167,23 @@ impl SocksCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_u8_into_u16(a: u8, b: u8) -> u16 {
|
||||
(u16::from(a) << 8) | u16::from(b)
|
||||
}
|
||||
|
||||
async fn read_until_zero<R>(stream: &mut R) -> Result<Vec<u8>, SocksProxyError>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
let mut result = Vec::new();
|
||||
let mut char = [0u8];
|
||||
loop {
|
||||
stream.read_exact(&mut char).await?;
|
||||
if char[0] == 0 {
|
||||
break;
|
||||
}
|
||||
result.push(char[0]);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::error::Socks5ClientError;
|
||||
|
||||
use super::authentication::Authenticator;
|
||||
use super::client::SocksClient;
|
||||
use super::{mixnet_responses::MixnetResponseListener, types::ResponseCode};
|
||||
use super::{
|
||||
authentication::Authenticator, client::SocksClient, mixnet_responses::MixnetResponseListener,
|
||||
};
|
||||
use client_connections::{ConnectionCommandSender, LaneQueueLengths};
|
||||
use client_core::client::{
|
||||
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
|
||||
@@ -85,47 +85,26 @@ impl SphinxSocksServer {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Ok((stream, _remote)) = listener.accept() => {
|
||||
// TODO Optimize this
|
||||
let mut client = SocksClient::new(
|
||||
stream,
|
||||
self.authenticator.clone(),
|
||||
input_sender.clone(),
|
||||
self.service_provider,
|
||||
&self.service_provider,
|
||||
controller_sender.clone(),
|
||||
self.self_address,
|
||||
&self.self_address,
|
||||
self.lane_queue_lengths.clone(),
|
||||
self.shutdown.clone(),
|
||||
);
|
||||
|
||||
tokio::spawn(async move {
|
||||
{
|
||||
match client.run().await {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
error!("Error! {}", error);
|
||||
let error_text = format!("{}", error);
|
||||
|
||||
let response: ResponseCode;
|
||||
|
||||
if error_text.contains("Host") {
|
||||
response = ResponseCode::HostUnreachable;
|
||||
} else if error_text.contains("Network") {
|
||||
response = ResponseCode::NetworkUnreachable;
|
||||
} else if error_text.contains("ttl") {
|
||||
response = ResponseCode::TtlExpired
|
||||
} else {
|
||||
response = ResponseCode::Failure
|
||||
}
|
||||
|
||||
if client.error(response).await.is_err() {
|
||||
warn!("Failed to send error code");
|
||||
};
|
||||
if client.shutdown().await.is_err() {
|
||||
warn!("Failed to shutdown TcpStream");
|
||||
};
|
||||
}
|
||||
if let Err(err) = client.run().await {
|
||||
error!("Error! {}", err);
|
||||
if client.send_error(err).await.is_err() {
|
||||
warn!("Failed to send error code");
|
||||
};
|
||||
if client.shutdown().await.is_err() {
|
||||
warn!("Failed to shutdown TcpStream");
|
||||
};
|
||||
// client gets dropped here
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
use snafu::Snafu;
|
||||
#[derive(Debug, Snafu)]
|
||||
/// SOCKS4 Response codes
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum ResponseCodeV4 {
|
||||
Granted = 0x5a,
|
||||
RequestRejected = 0x5b,
|
||||
CannotConnectToIdent = 0x5c,
|
||||
DifferentUserId = 0x5d,
|
||||
}
|
||||
|
||||
/// Possible SOCKS5 Response Codes
|
||||
pub(crate) enum ResponseCode {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum ResponseCodeV5 {
|
||||
#[error("SOCKS5 Server Success")]
|
||||
Success = 0x00,
|
||||
#[snafu(display("SOCKS5 Server Failure"))]
|
||||
#[error("SOCKS5 Server Failure")]
|
||||
Failure = 0x01,
|
||||
#[snafu(display("SOCKS5 Rule failure"))]
|
||||
#[error("SOCKS5 Rule failure")]
|
||||
RuleFailure = 0x02,
|
||||
#[snafu(display("network unreachable"))]
|
||||
#[error("network unreachable")]
|
||||
NetworkUnreachable = 0x03,
|
||||
#[snafu(display("host unreachable"))]
|
||||
#[error("host unreachable")]
|
||||
HostUnreachable = 0x04,
|
||||
#[snafu(display("connection refused"))]
|
||||
#[error("connection refused")]
|
||||
ConnectionRefused = 0x05,
|
||||
#[snafu(display("TTL expired"))]
|
||||
#[error("TTL expired")]
|
||||
TtlExpired = 0x06,
|
||||
#[snafu(display("Command not supported"))]
|
||||
#[error("Command not supported")]
|
||||
CommandNotSupported = 0x07,
|
||||
#[snafu(display("Addr Type not supported"))]
|
||||
#[error("Addr Type not supported")]
|
||||
AddrTypeNotSupported = 0x08,
|
||||
}
|
||||
|
||||
@@ -48,7 +58,7 @@ where
|
||||
}
|
||||
|
||||
/// DST.addr variant types
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum AddrType {
|
||||
V4 = 0x01,
|
||||
Domain = 0x03,
|
||||
|
||||
@@ -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")]
|
||||
@@ -54,7 +61,7 @@ where
|
||||
#[cfg(feature = "coconut")]
|
||||
pub async fn prepare_coconut_credential(
|
||||
&self,
|
||||
) -> Result<coconut_interface::Credential, GatewayClientError> {
|
||||
) -> Result<(coconut_interface::Credential, i64), GatewayClientError> {
|
||||
let verification_key = obtain_aggregate_verification_key(&self.coconut_api_clients).await?;
|
||||
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
|
||||
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
|
||||
@@ -68,13 +75,21 @@ where
|
||||
coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
|
||||
|
||||
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
|
||||
Ok(prepare_for_spending(
|
||||
voucher_value,
|
||||
voucher_info,
|
||||
serial_number,
|
||||
binding_number,
|
||||
&signature,
|
||||
&verification_key,
|
||||
)?)
|
||||
Ok((
|
||||
prepare_for_spending(
|
||||
voucher_value,
|
||||
voucher_info,
|
||||
serial_number,
|
||||
binding_number,
|
||||
&signature,
|
||||
&verification_key,
|
||||
)?,
|
||||
bandwidth_credential.id,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub async fn consume_credential(&self, id: i64) -> Result<(), GatewayClientError> {
|
||||
Ok(self.storage.consume_coconut_credential(id).await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub use crate::packet_router::{
|
||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
||||
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
use gateway_requests::iv::IV;
|
||||
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
|
||||
@@ -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,9 +66,8 @@ pub struct GatewayClient {
|
||||
/// Delay between each subsequent reconnection attempt.
|
||||
reconnection_backoff: Duration,
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// Listen to shutdown messages.
|
||||
shutdown: Option<ShutdownListener>,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
@@ -85,7 +83,7 @@ impl GatewayClient {
|
||||
ack_sender: AcknowledgementSender,
|
||||
response_timeout_duration: Duration,
|
||||
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
@@ -97,18 +95,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 +128,6 @@ impl GatewayClient {
|
||||
gateway_owner: String,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
response_timeout_duration: Duration,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||
) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
|
||||
@@ -144,12 +135,8 @@ 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 shutdown = ShutdownListener::dummy();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
|
||||
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
@@ -167,7 +154,6 @@ impl GatewayClient {
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
@@ -295,44 +281,19 @@ impl GatewayClient {
|
||||
// technically the `wasm_timer` also works outside wasm, but unless required,
|
||||
// I really prefer to just stick to tokio
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let timeout = wasm_timer::Delay::new(self.response_timeout_duration);
|
||||
|
||||
let mut fused_timeout = timeout.fuse();
|
||||
let mut fused_stream = conn.fuse();
|
||||
|
||||
// Bit of an ugly workaround for selecting on an `Option` without having access to
|
||||
// `tokio::select`
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let shutdown = {
|
||||
let m_shutdown = self.shutdown.clone();
|
||||
async {
|
||||
if let Some(mut s) = m_shutdown {
|
||||
// TODO: fix this by marking as success _after_ the select
|
||||
s.mark_as_success();
|
||||
s.recv().await
|
||||
} else {
|
||||
std::future::pending::<()>().await
|
||||
}
|
||||
}
|
||||
.fuse()
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::pin!(shutdown);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut shutdown = std::future::pending::<()>().fuse();
|
||||
let mut timeout = wasm_timer::Delay::new(self.response_timeout_duration);
|
||||
|
||||
loop {
|
||||
futures::select! {
|
||||
_ = shutdown => {
|
||||
tokio::select! {
|
||||
_ = self.shutdown.recv() => {
|
||||
log::trace!("GatewayClient control response: Received shutdown");
|
||||
log::debug!("GatewayClient control response: Exiting");
|
||||
break Err(GatewayClientError::ConnectionClosedGatewayShutdown);
|
||||
}
|
||||
_ = &mut fused_timeout => {
|
||||
_ = &mut timeout => {
|
||||
break Err(GatewayClientError::Timeout);
|
||||
}
|
||||
msg = fused_stream.next() => {
|
||||
msg = conn.next() => {
|
||||
let ws_msg = match cleanup_socket_message(msg) {
|
||||
Err(err) => break Err(err),
|
||||
Ok(msg) => msg
|
||||
@@ -403,13 +364,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 +387,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 {
|
||||
@@ -632,7 +587,7 @@ impl GatewayClient {
|
||||
let _gateway_owner = self.gateway_owner.clone();
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let credential = self
|
||||
let (credential, credential_id) = self
|
||||
.bandwidth_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
@@ -642,7 +597,15 @@ impl GatewayClient {
|
||||
return self.try_claim_testnet_bandwidth().await;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
return self.claim_coconut_bandwidth(credential).await;
|
||||
{
|
||||
self.claim_coconut_bandwidth(credential).await?;
|
||||
self.bandwidth_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.consume_credential(credential_id)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
|
||||
@@ -788,7 +751,6 @@ impl GatewayClient {
|
||||
.as_ref()
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.shutdown.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,20 +21,18 @@ 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>,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl PacketRouter {
|
||||
pub fn new(
|
||||
ack_sender: AcknowledgementSender,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
PacketRouter {
|
||||
ack_sender,
|
||||
mixnet_message_sender,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
@@ -86,13 +82,10 @@ 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
|
||||
log::warn!("Failed to send mixnet message due to receiver task shutdown");
|
||||
return Err(GatewayClientError::MixnetMsgSenderFailedToSend);
|
||||
}
|
||||
if self.shutdown.is_shutdown_poll() || self.shutdown.is_dummy() {
|
||||
// This should ideally not happen, but it's ok
|
||||
log::warn!("Failed to send mixnet message due to receiver task shutdown");
|
||||
return Err(GatewayClientError::MixnetMsgSenderFailedToSend);
|
||||
}
|
||||
// This should never happen during ordinary operation the way it's currently used.
|
||||
// Abort to be on the safe side
|
||||
|
||||
@@ -10,7 +10,6 @@ use futures::{SinkExt, StreamExt};
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use log::*;
|
||||
use std::sync::Arc;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use task::ShutdownListener;
|
||||
use tungstenite::Message;
|
||||
|
||||
@@ -85,7 +84,7 @@ impl PartiallyDelegated {
|
||||
conn: WsConn,
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedKeys>,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||
mut shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
// when called for, it NEEDS TO yield back the stream so that we could merge it and
|
||||
// read control request responses.
|
||||
@@ -99,27 +98,9 @@ impl PartiallyDelegated {
|
||||
let mut chunk_stream = (&mut stream).ready_chunks(8);
|
||||
let mut packet_router = packet_router;
|
||||
|
||||
// Bit of an ugly workaround for selecting on an `Option` without having access to
|
||||
// `tokio::select`
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let shutdown = {
|
||||
async {
|
||||
if let Some(mut s) = shutdown {
|
||||
s.recv().await
|
||||
} else {
|
||||
std::future::pending::<()>().await
|
||||
}
|
||||
}
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::pin!(shutdown);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut shutdown = std::future::pending::<()>();
|
||||
|
||||
let ret_err = loop {
|
||||
tokio::select! {
|
||||
_ = &mut shutdown => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("GatewayClient listener: Received shutdown");
|
||||
log::debug!("GatewayClient listener: Exiting");
|
||||
return;
|
||||
@@ -142,7 +123,10 @@ impl PartiallyDelegated {
|
||||
|
||||
if match ret_err {
|
||||
Err(err) => stream_sender.send(Err(err)),
|
||||
Ok(_) => stream_sender.send(Ok(stream)),
|
||||
Ok(_) => {
|
||||
shutdown.mark_as_success();
|
||||
stream_sender.send(Ok(stream))
|
||||
}
|
||||
}
|
||||
.is_err()
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ pub trait Storage: Send + Sync {
|
||||
|
||||
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError>;
|
||||
|
||||
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -59,7 +59,7 @@ impl Storage for PersistentStorage {
|
||||
Err(StorageError::WasmNotSupported)
|
||||
}
|
||||
|
||||
async fn remove_coconut_credential(&self, _id: i64) -> Result<(), StorageError> {
|
||||
async fn consume_coconut_credential(&self, _id: i64) -> Result<(), StorageError> {
|
||||
Err(StorageError::WasmNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,12 @@ impl Client {
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::DETAILED],
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::DETAILED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -161,6 +166,7 @@ impl Client {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::ACTIVE,
|
||||
routes::DETAILED,
|
||||
@@ -252,6 +258,7 @@ impl Client {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::REWARDED,
|
||||
routes::DETAILED,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
DROP TABLE coconut_credentials;
|
||||
CREATE TABLE coconut_credentials
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
voucher_value TEXT NOT NULL,
|
||||
voucher_info TEXT NOT NULL,
|
||||
serial_number TEXT NOT NULL,
|
||||
binding_number TEXT NOT NULL,
|
||||
signature TEXT NOT NULL UNIQUE,
|
||||
consumed BOOLEAN NOT NULL
|
||||
);
|
||||
@@ -36,8 +36,8 @@ impl CoconutCredentialManager {
|
||||
signature: String,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature) VALUES (?, ?, ?, ?, ?)",
|
||||
voucher_value, voucher_info, serial_number, binding_number, signature
|
||||
"INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature, consumed) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
voucher_value, voucher_info, serial_number, binding_number, signature, false
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
@@ -48,20 +48,26 @@ impl CoconutCredentialManager {
|
||||
pub(crate) async fn get_next_coconut_credential(
|
||||
&self,
|
||||
) -> Result<CoconutCredential, sqlx::Error> {
|
||||
sqlx::query_as!(CoconutCredential, "SELECT * FROM coconut_credentials")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
sqlx::query_as!(
|
||||
CoconutCredential,
|
||||
"SELECT * FROM coconut_credentials WHERE NOT consumed"
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Removes from the database the specified credential.
|
||||
/// Consumes in the database the specified credential.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id`: Database id.
|
||||
pub(crate) async fn remove_coconut_credential(&self, id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM coconut_credentials WHERE id = ?", id)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
pub(crate) async fn consume_coconut_credential(&self, id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE coconut_credentials SET consumed = TRUE WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,9 +93,9 @@ impl Storage for PersistentStorage {
|
||||
Ok(credential)
|
||||
}
|
||||
|
||||
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||
self.coconut_credential_manager
|
||||
.remove_coconut_credential(id)
|
||||
.consume_coconut_credential(id)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -9,4 +9,5 @@ pub struct CoconutCredential {
|
||||
pub serial_number: String,
|
||||
pub binding_number: String,
|
||||
pub signature: String,
|
||||
pub consumed: bool,
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ pub trait Storage: Send + Sync {
|
||||
/// Tries to retrieve one of the stored, unused credentials.
|
||||
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError>;
|
||||
|
||||
/// Removes from the database the specified credential.
|
||||
/// Marks as consumed in the database the specified credential.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `signature`: Coconut credential in the form of a signature.
|
||||
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
|
||||
/// * `id`: Id of the credential to be consumed.
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PublicKeyWithProof {
|
||||
pub(crate) key: PublicKey,
|
||||
|
||||
@@ -13,7 +13,7 @@ use zeroize::Zeroize;
|
||||
const DISCRETE_LOG_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct ProofOfDiscreteLog {
|
||||
pub(crate) rand_commitment: G1Projective,
|
||||
|
||||
@@ -9,19 +9,79 @@ use crate::interpolation::polynomial::{Polynomial, PublicCoefficients};
|
||||
use crate::interpolation::{
|
||||
perform_lagrangian_interpolation_at_origin, perform_lagrangian_interpolation_at_x,
|
||||
};
|
||||
use crate::utils::deserialize_g2;
|
||||
use crate::{NodeIndex, Share, Threshold};
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use group::GroupEncoding;
|
||||
use rand_core::RngCore;
|
||||
use std::collections::BTreeMap;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct RecoveredVerificationKeys {
|
||||
pub recovered_master: G2Projective,
|
||||
pub recovered_partials: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl RecoveredVerificationKeys {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let partials = self.recovered_partials.len();
|
||||
let mut bytes = Vec::with_capacity(96 + 4 + 96 * partials);
|
||||
bytes.extend_from_slice(self.recovered_master.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(&((partials as u32).to_be_bytes()));
|
||||
for partial in &self.recovered_partials {
|
||||
bytes.extend_from_slice(partial.to_bytes().as_ref());
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
|
||||
if b.len() < 96 + 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"RecoveredVerificationKeys",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let recovered_master = deserialize_g2(&b[..96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"RecoveredVerificationKeys.recovered_master",
|
||||
"invalid curve point",
|
||||
)
|
||||
})?;
|
||||
|
||||
let partials = u32::from_be_bytes([b[96], b[97], b[98], b[99]]) as usize;
|
||||
let mut recovered_partials = Vec::with_capacity(partials);
|
||||
|
||||
if b.len() != 96 + 4 + 96 * partials {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"RecoveredVerificationKeys",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let mut i = 96 + 4;
|
||||
for _ in 0..partials {
|
||||
let partial = deserialize_g2(&b[i..i + 96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"RecoveredVerificationKeys.recovered_partials",
|
||||
"invalid curve point",
|
||||
)
|
||||
})?;
|
||||
|
||||
recovered_partials.push(partial);
|
||||
i += 96;
|
||||
}
|
||||
|
||||
Ok(RecoveredVerificationKeys {
|
||||
recovered_master,
|
||||
recovered_partials,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Dealing {
|
||||
@@ -355,6 +415,17 @@ mod tests {
|
||||
use crate::combine_shares;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn recovered_verification_keys_serde() {
|
||||
let keys = RecoveredVerificationKeys {
|
||||
recovered_master: Default::default(),
|
||||
recovered_partials: vec![Default::default(), Default::default()],
|
||||
};
|
||||
let bytes = keys.to_bytes();
|
||||
let recovered_keys = RecoveredVerificationKeys::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(keys, recovered_keys);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // expensive test
|
||||
fn recovering_partial_verification_keys() {
|
||||
|
||||
@@ -9,7 +9,7 @@ pub(crate) const BECH32_PREFIX: &str = "n";
|
||||
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
|
||||
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
|
||||
|
||||
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
pub const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
|
||||
@@ -25,7 +25,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("0000000000000000000000000000000000000000");
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
|
||||
|
||||
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
pub const NYMD_VALIDATOR: &str = "https://rpc.nymtech.net";
|
||||
pub const API_VALIDATOR: &str = "https://validator.nymtech.net/api/";
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
|
||||
@@ -45,6 +45,7 @@ fn keygen() -> Result<(), CoconutError> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // expensive test
|
||||
fn dkg() -> Result<(), CoconutError> {
|
||||
let params = setup(5)?;
|
||||
let node_indices = vec![15u64, 248, 33521];
|
||||
|
||||
@@ -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");
|
||||
|
||||
+11
-1
@@ -9,7 +9,17 @@ 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"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2.83"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] }
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod shutdown;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod signal;
|
||||
pub mod spawn;
|
||||
|
||||
pub use shutdown::{ShutdownListener, ShutdownNotifier};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use signal::{wait_for_signal, wait_for_signal_and_error};
|
||||
|
||||
pub use spawn::spawn_with_report_error;
|
||||
|
||||
+98
-12
@@ -3,7 +3,7 @@
|
||||
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
use futures::FutureExt;
|
||||
use futures::{future::pending, FutureExt};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
@@ -14,7 +14,7 @@ use tokio::{
|
||||
|
||||
const DEFAULT_SHUTDOWN_TIMER_SECS: u64 = 5;
|
||||
|
||||
pub(crate) type SentError = Box<dyn Error + Send>;
|
||||
pub(crate) type SentError = Box<dyn Error + Send + Sync>;
|
||||
type ErrorSender = mpsc::UnboundedSender<SentError>;
|
||||
type ErrorReceiver = mpsc::UnboundedReceiver<SentError>;
|
||||
|
||||
@@ -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");
|
||||
@@ -143,12 +149,14 @@ pub struct ShutdownListener {
|
||||
// Also notify if we dropped without shutdown being registered
|
||||
drop_error: ErrorSender,
|
||||
|
||||
// Sometimes it's necessary to clone and drop the shutdown listener during normal operation,
|
||||
// for those situations we need to explicitly not drop (and trigger shutdown).
|
||||
set_not_drop: bool,
|
||||
// The current operating mode
|
||||
mode: ShutdownListenerMode,
|
||||
}
|
||||
|
||||
impl ShutdownListener {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
fn new(
|
||||
notify: watch::Receiver<()>,
|
||||
return_error: ErrorSender,
|
||||
@@ -159,15 +167,40 @@ impl ShutdownListener {
|
||||
notify,
|
||||
return_error,
|
||||
drop_error,
|
||||
set_not_drop: false,
|
||||
mode: ShutdownListenerMode::Listening,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a dummy that will never report that we should shutdown.
|
||||
pub fn dummy() -> ShutdownListener {
|
||||
let (_notify_tx, notify_rx) = watch::channel(());
|
||||
let (task_halt_tx, _task_halt_rx) = mpsc::unbounded_channel();
|
||||
let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel();
|
||||
ShutdownListener {
|
||||
shutdown: false,
|
||||
notify: notify_rx,
|
||||
return_error: task_halt_tx,
|
||||
drop_error: task_drop_tx,
|
||||
mode: ShutdownListenerMode::Dummy,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dummy(&self) -> bool {
|
||||
self.mode.is_dummy()
|
||||
}
|
||||
|
||||
pub fn is_shutdown(&self) -> bool {
|
||||
self.shutdown
|
||||
if self.mode.is_dummy() {
|
||||
false
|
||||
} else {
|
||||
self.shutdown
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn recv(&mut self) {
|
||||
if self.mode.is_dummy() {
|
||||
return pending().await;
|
||||
}
|
||||
if self.shutdown {
|
||||
return;
|
||||
}
|
||||
@@ -175,7 +208,20 @@ impl ShutdownListener {
|
||||
self.shutdown = true;
|
||||
}
|
||||
|
||||
pub async fn recv_timeout(&mut self) {
|
||||
if self.mode.is_dummy() {
|
||||
return pending().await;
|
||||
}
|
||||
#[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.mode.is_dummy() {
|
||||
return false;
|
||||
}
|
||||
if self.shutdown {
|
||||
return true;
|
||||
}
|
||||
@@ -194,21 +240,30 @@ impl ShutdownListener {
|
||||
}
|
||||
}
|
||||
|
||||
// This listener should to *not* notify the ShutdownNotifier to shutdown when dropped. For
|
||||
// example when we clone the listener for a task handling connections, we often want to drop
|
||||
// without signal failure.
|
||||
pub fn mark_as_success(&mut self) {
|
||||
self.mode.set_should_not_signal_on_drop();
|
||||
}
|
||||
|
||||
pub fn send_we_stopped(&mut self, err: SentError) {
|
||||
if self.mode.is_dummy() {
|
||||
return;
|
||||
}
|
||||
log::trace!("Notifying we stopped: {:?}", err);
|
||||
if self.return_error.send(err).is_err() {
|
||||
log::error!("Failed to send back error message");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_as_success(&mut self) {
|
||||
self.set_not_drop = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ShutdownListener {
|
||||
fn drop(&mut self) {
|
||||
if !self.set_not_drop && !self.is_shutdown_poll() {
|
||||
if !self.mode.should_signal_on_drop() {
|
||||
return;
|
||||
}
|
||||
if !self.is_shutdown_poll() {
|
||||
log::trace!("Notifying stop on unexpected drop");
|
||||
// If we can't send, well then there is not much to do
|
||||
self.drop_error
|
||||
@@ -218,6 +273,37 @@ impl Drop for ShutdownListener {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
enum ShutdownListenerMode {
|
||||
// Normal operations
|
||||
Listening,
|
||||
// Normal operations, but we don't report back if the we stop by getting dropped.
|
||||
ListeningButDontReportHalt,
|
||||
// Dummy mode, for when we don't do anything at all.
|
||||
Dummy,
|
||||
}
|
||||
|
||||
impl ShutdownListenerMode {
|
||||
fn is_dummy(&self) -> bool {
|
||||
self == &ShutdownListenerMode::Dummy
|
||||
}
|
||||
|
||||
fn should_signal_on_drop(&self) -> bool {
|
||||
match self {
|
||||
ShutdownListenerMode::Listening => true,
|
||||
ShutdownListenerMode::ListeningButDontReportHalt | ShutdownListenerMode::Dummy => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_should_not_signal_on_drop(&mut self) {
|
||||
use ShutdownListenerMode::{Dummy, Listening, ListeningButDontReportHalt};
|
||||
*self = match &self {
|
||||
ListeningButDontReportHalt | Listening => ListeningButDontReportHalt,
|
||||
Dummy => Dummy,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
use crate::ShutdownListener;
|
||||
use std::future::Future;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(crate) fn spawn<F>(future: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn spawn<F>(future: F)
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
pub fn spawn_with_report_error<F, T, E>(future: F, mut shutdown: ShutdownListener)
|
||||
where
|
||||
F: Future<Output = Result<T, E>> + Send + 'static,
|
||||
T: 'static,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let future_that_sends = async move {
|
||||
if let Err(err) = future.await {
|
||||
shutdown.send_we_stopped(Box::new(err));
|
||||
}
|
||||
};
|
||||
spawn(future_that_sends);
|
||||
}
|
||||
+2
-2
@@ -9,7 +9,7 @@ MIX_DENOM_DISPLAY=nym
|
||||
STAKE_DENOM=unyx
|
||||
STAKE_DENOM_DISPLAY=nyx
|
||||
DENOMS_EXPONENT=6
|
||||
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
|
||||
MIXNET_CONTRACT_ADDRESS=n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr
|
||||
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
@@ -17,5 +17,5 @@ MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYMD_VALIDATOR="https://rpc.nyx.nodes.guru/"
|
||||
NYMD_VALIDATOR="https://rpc.nymtech.net";
|
||||
API_VALIDATOR="https://validator.nymtech.net/api/"
|
||||
|
||||
+3
-2
@@ -12,8 +12,9 @@ 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
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n12ckdkm3q7eytefs7rwu4ue3t9hxgvl9v08jddmtwgct2ve0pv50q0t8dlt
|
||||
MULTISIG_CONTRACT_ADDRESS=n14krxe8ukzagwhvec0rmteexu62w8k9kp9sra9ww6em2hnmzcukqsa0utc8
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n1rl5n6cxuz2hdy3f7d9hsnw8zn0zwwwr0r4dxfz7tktgpgkcnz9zshmvksc
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYMD_VALIDATOR="https://qwerty-validator.qa.nymte.ch/"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
EXPLORER_API_URL=https://explorer.nymtech.net/api/v1
|
||||
VALIDATOR_API_URL=https://validator.nymtech.net
|
||||
VALIDATOR_URL=https://rpc.nyx.nodes.guru
|
||||
VALIDATOR_URL=https://rpc.nymtech.net
|
||||
BIG_DIPPER_URL=https://blocks.nymtech.net
|
||||
CURRENCY_DENOM=unym
|
||||
CURRENCY_STAKING_DENOM=unyx
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::config::template::config_template;
|
||||
use config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
use config::NymConfig;
|
||||
use log::error;
|
||||
use network_defaults::mainnet::{API_VALIDATOR, NYMD_VALIDATOR, STATISTICS_SERVICE_DOMAIN_ADDRESS};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
@@ -416,9 +417,9 @@ impl Default for Gateway {
|
||||
private_sphinx_key_file: Default::default(),
|
||||
public_sphinx_key_file: Default::default(),
|
||||
enabled_statistics: false,
|
||||
statistics_service_url: Url::from_str("http://127.0.0.1").unwrap(),
|
||||
validator_api_urls: vec![],
|
||||
validator_nymd_urls: vec![],
|
||||
statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS).expect("Invalid default statistics service URL"),
|
||||
validator_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")],
|
||||
validator_nymd_urls: vec![Url::from_str(NYMD_VALIDATOR).expect("Invalid default nymd URL")],
|
||||
cosmos_mnemonic: bip39::Mnemonic::from_str("exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day").unwrap(),
|
||||
nym_root_directory: Config::default_root_directory(),
|
||||
persistent_storage: Default::default(),
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::template::config_template;
|
||||
use config::defaults::mainnet::API_VALIDATOR;
|
||||
use config::defaults::{
|
||||
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||
};
|
||||
@@ -411,7 +412,7 @@ impl Default for MixNode {
|
||||
public_identity_key_file: Default::default(),
|
||||
private_sphinx_key_file: Default::default(),
|
||||
public_sphinx_key_file: Default::default(),
|
||||
validator_api_urls: vec![],
|
||||
validator_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")],
|
||||
nym_root_directory: Config::default_root_directory(),
|
||||
wallet_address: "nymXXXXXXXX".to_string(),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-connect-v1.1.3](https://github.com/nymtech/nym/tree/nym-connect-v1.1.3) (2022-12-13)
|
||||
|
||||
- socks5-client: added support for socks4a.
|
||||
|
||||
## [nym-connect-v1.1.2](https://github.com/nymtech/nym/tree/nym-connect-v1.1.2) (2022-11-29)
|
||||
|
||||
- socks5-client: fix error with client failing and disconnecting unnecessarily.
|
||||
|
||||
Generated
+11
-34
@@ -630,7 +630,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"client-connections",
|
||||
"config",
|
||||
@@ -647,11 +647,13 @@ dependencies = [
|
||||
"pemstore",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sled",
|
||||
"tap",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
@@ -1414,12 +1416,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
@@ -3318,7 +3314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-connect"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"client-core",
|
||||
@@ -3351,7 +3347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"client-connections",
|
||||
@@ -3375,7 +3371,7 @@ dependencies = [
|
||||
"proxy-helpers",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"snafu",
|
||||
"serde_json",
|
||||
"socks5-requests",
|
||||
"tap",
|
||||
"task",
|
||||
@@ -4817,9 +4813,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.85"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
||||
dependencies = [
|
||||
"itoa 1.0.3",
|
||||
"ryu",
|
||||
@@ -5037,27 +5033,6 @@ version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
@@ -5463,6 +5438,8 @@ dependencies = [
|
||||
"log",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6297,7 +6274,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
@@ -32,7 +32,7 @@ reqwest = { version = "0.11", features = ["json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tap = "1.0.1"
|
||||
tauri = { version = "^1.1.1", features = ["clipboard-write-text", "macos-private-api", "shell-open", "system-tray", "updater", "window-close", "window-start-dragging"] }
|
||||
tauri = { version = "^1.1.1", features = ["clipboard-write-text", "macos-private-api", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.21.2", features = ["sync", "time"] }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -116,6 +115,9 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
// Future proofing. This flag exists for the other clients
|
||||
let user_wants_force_register = false;
|
||||
|
||||
// If the client was already initialized, don't generate new keys and don't re-register with
|
||||
// the gateway (because this would create a new shared key).
|
||||
// Unless the user really wants to.
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
log::trace!("Creating config for id: {}", id);
|
||||
@@ -127,28 +129,38 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
.set_custom_validator_apis(config_common::parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
let gateway = setup_gateway(
|
||||
&id,
|
||||
// Setup gateway by either registering a new one, or reusing exiting keys
|
||||
let gateway = client_core::init::setup_gateway::<Socks5Config, _>(
|
||||
register_gateway,
|
||||
Some(&chosen_gateway_id),
|
||||
config.get_socks5(),
|
||||
Some(chosen_gateway_id),
|
||||
config.get_base(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_socks5().get_config_file_save_location();
|
||||
config.get_socks5().save_to_file(None).tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
log::info!("Saved configuration file to {:?}", config_save_location);
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
log::info!("The address of this client is: {}", address);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config) {
|
||||
log::info!(
|
||||
"Saved configuration file to {:?}",
|
||||
config.get_socks5().get_config_file_save_location()
|
||||
);
|
||||
log::info!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
log::info!("Gateway owner: {}", config.get_base().get_gateway_owner());
|
||||
log::info!(
|
||||
"Gateway listener: {}",
|
||||
config.get_base().get_gateway_listener()
|
||||
);
|
||||
|
||||
log::info!(
|
||||
"Service provider address: {}",
|
||||
config.get_socks5().get_provider_mix_address()
|
||||
@@ -158,60 +170,4 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
config.get_socks5().get_listening_port()
|
||||
);
|
||||
log::info!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: deduplicate with same functions in other client
|
||||
async fn setup_gateway(
|
||||
id: &str,
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Socks5Config,
|
||||
) -> Result<GatewayEndpoint> {
|
||||
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.
|
||||
println!("Configuring gateway");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||
.await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
// valid for the gateway being used
|
||||
println!("Using gateway provided by user, keeping existing keys");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
let existing_config = Socks5Config::load_from_file(Some(id)).map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
BackendError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})?;
|
||||
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use client_core::config::{ClientCoreConfigTrait, GatewayEndpointConfig};
|
||||
use futures::channel::mpsc;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
@@ -19,19 +19,23 @@ pub enum Socks5StatusMessage {
|
||||
/// The SOCKS5 task successfully stopped
|
||||
Stopped,
|
||||
/// The SOCKS5 task failed to start
|
||||
FailedToStart,
|
||||
Failed(Box<dyn std::error::Error + Send>),
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -52,7 +56,7 @@ pub fn start_nym_socks5_client(
|
||||
if let Err(err) = result {
|
||||
log::error!("SOCKS5 proxy failed: {err}");
|
||||
socks5_status_tx
|
||||
.send(Socks5StatusMessage::FailedToStart)
|
||||
.send(Socks5StatusMessage::Failed(err))
|
||||
.expect("Failed to send status message back to main task");
|
||||
return;
|
||||
}
|
||||
@@ -94,14 +98,14 @@ pub fn start_disconnect_listener(
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Ok(Socks5StatusMessage::FailedToStart) => {
|
||||
log::info!("SOCKS5 task reported it failed to start");
|
||||
Ok(Socks5StatusMessage::Failed(err)) => {
|
||||
log::info!("SOCKS5 task reported error: {}", err);
|
||||
window
|
||||
.emit(
|
||||
"socks5-event",
|
||||
Payload {
|
||||
title: "SOCKS5 error".into(),
|
||||
message: "SOCKS5 failed to start".into(),
|
||||
message: format!("SOCKS5 failed: {}", err),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.2"
|
||||
"version": "1.1.3"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -57,7 +57,8 @@
|
||||
},
|
||||
"window": {
|
||||
"startDragging": true,
|
||||
"close": true
|
||||
"close": true,
|
||||
"minimize": true
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ArrowBack, Close, HelpOutline } from '@mui/icons-material';
|
||||
import { ArrowBack, Close, HelpOutline, Minimize } from '@mui/icons-material';
|
||||
import { Box, IconButton } from '@mui/material';
|
||||
import { NymWordmark } from '@nymproject/react/logo/NymWordmark';
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
@@ -19,7 +19,7 @@ const customTitleBarStyles = {
|
||||
};
|
||||
|
||||
const CustomButton = ({ Icon, onClick }: { Icon: React.JSXElementConstructor<any>; onClick: () => void }) => (
|
||||
<IconButton size="small" style={{ padding: 0 }} onClick={onClick}>
|
||||
<IconButton size="small" onClick={onClick} sx={{ padding: 0 }}>
|
||||
<Icon style={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
);
|
||||
@@ -28,14 +28,22 @@ export const CustomTitleBar = () => {
|
||||
const { showHelp, handleShowHelp } = useClientContext();
|
||||
return (
|
||||
<Box data-tauri-drag-region style={customTitleBarStyles.titlebar}>
|
||||
<CustomButton
|
||||
Icon={!showHelp ? HelpOutline : ArrowBack}
|
||||
onClick={() => {
|
||||
handleShowHelp();
|
||||
}}
|
||||
/>
|
||||
{/* set width to keep logo centered */}
|
||||
<Box sx={{ width: '40px' }}>
|
||||
<CustomButton
|
||||
Icon={!showHelp ? HelpOutline : ArrowBack}
|
||||
onClick={() => {
|
||||
handleShowHelp();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<NymWordmark width={36} />
|
||||
<CustomButton Icon={Close} onClick={() => appWindow.close()} />
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CustomButton Icon={Minimize} onClick={() => appWindow.minimize()} />
|
||||
<CustomButton Icon={Close} onClick={() => appWindow.close()} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## [nym-wallet-v1.1.3](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.3) (2022-12-13)
|
||||
|
||||
- wallet: improved unbond screen.
|
||||
|
||||
## [nym-wallet-v1.1.2](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.2) (2022-11-09)
|
||||
|
||||
- wallet: Bity buy functionality
|
||||
|
||||
Generated
+2
-2
@@ -2937,7 +2937,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2 0.3.4",
|
||||
@@ -5497,7 +5497,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-wallet-app",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "1.1.2"
|
||||
"version": "1.1.3"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -15,7 +15,7 @@ export const AppBar = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none' }}>
|
||||
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none', pt: 3 }}>
|
||||
<Toolbar disableGutters>
|
||||
<Grid container justifyContent="space-between" alignItems="center" flexWrap="nowrap">
|
||||
<Grid item container alignItems="center" spacing={1}>
|
||||
|
||||
@@ -114,7 +114,6 @@ export const BondedMixnode = ({
|
||||
<BondedMixnodeActions
|
||||
onActionSelect={onActionSelect}
|
||||
disabledRedeemAndCompound={(operatorRewards && Number(operatorRewards.amount) === 0) || false}
|
||||
disabledBondMore // TODO for now disable bond more feature until backend is ready
|
||||
/>
|
||||
),
|
||||
id: 'actions-cell',
|
||||
@@ -136,7 +135,7 @@ export const BondedMixnode = ({
|
||||
</Box>
|
||||
{name && (
|
||||
<Tooltip title={host} arrow>
|
||||
<Typography fontWeight="regular" variant="h6">
|
||||
<Typography fontWeight="regular" variant="h6" width="fit-content">
|
||||
{name}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
|
||||
@@ -35,7 +35,7 @@ export const BondedMixnodeActions = ({
|
||||
)}
|
||||
<ActionsMenuItem
|
||||
title="Redeem rewards"
|
||||
Icon={<Typography sx={{ pl: 1, fontWeight: 700 }}>R</Typography>}
|
||||
Icon={<Typography sx={{ pl: 0.5, fontWeight: 700 }}>R</Typography>}
|
||||
onClick={() => handleActionClick('redeem')}
|
||||
disabled={disabledRedeemAndCompound}
|
||||
/>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user