Compare commits

..

2 Commits

Author SHA1 Message Date
Gala 469b13dfd9 adding a comment 2022-11-11 08:07:05 +01:00
Gala 53da6512eb second banner version 2022-11-11 07:50:34 +01:00
87 changed files with 2759 additions and 1403 deletions
+1 -4
View File
@@ -16,10 +16,7 @@ jobs:
- name: Install cargo deny
run: cargo install --locked cargo-deny
- name: Run cargo deny
run: |
find . -name Cargo.toml -exec cargo deny --manifest-path {} check \
advisories -A advisory-not-detected --hide-inclusion-graph \; &> \
>(uniq &> .github/workflows/support-files/notifications/deny.message )
run: cargo deny check advisories --hide-inclusion-graph &> .github/workflows/support-files/notifications/deny.message
- uses: actions/upload-artifact@v3
with:
name: report
+72
View File
@@ -0,0 +1,72 @@
name: Continuous integration on dispatch
on: workflow_dispatch
jobs:
build:
runs-on: [ self-hosted, custom-linux ]
# Enable sccache via environment variable
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace -- -D warnings
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features=coconut -- -D warnings
@@ -1,56 +0,0 @@
name: CI for Network Explorer API
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check the release tag starts with `nym-explorer-api-`
if: startsWith(github.ref, 'refs/tags/nym-explorer-api-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-explorer-api-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all explorer-api
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path explorer-api/Cargo.toml --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/explorer-api
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/explorer-api
@@ -1,4 +1,4 @@
name: Nightly builds on latest release
name: Nightly builds on dispatch
on: workflow_dispatch
jobs:
@@ -7,40 +7,26 @@ jobs:
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_release_matrix.json
- uses: actions/checkout@v3
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_release_matrix.json'
inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-latest
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.lastest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1 | sed 's/origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
@@ -56,12 +42,6 @@ jobs:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
@@ -119,12 +99,6 @@ jobs:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -177,7 +151,7 @@ jobs:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
@@ -186,14 +160,14 @@ jobs:
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
NYM_PROJECT_NAME: "Nym nightly build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly-release"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "${{ secrets.KEYBASE_CHANNEL_DEV_CORE_ID }}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
-9
View File
@@ -4,16 +4,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## Unreleased
### Added
- binaries: add `-c` shortform for `--config-env-file`
- websocket-requests: add server response signalling current packet queue length in the client
### Changed
- clients: add concept of transmission lanes to better handle multiple data streams ([#1720])
[#1720]: https://github.com/nymtech/nym/pull/1720
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
Generated
+2 -14
View File
@@ -576,18 +576,10 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "client-connections"
version = "0.1.0"
dependencies = [
"futures",
]
[[package]]
name = "client-core"
version = "1.1.0"
dependencies = [
"client-connections",
"config",
"crypto",
"dirs",
@@ -858,9 +850,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.5"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [
"libc",
]
@@ -3133,7 +3125,6 @@ name = "nym-client"
version = "1.1.0"
dependencies = [
"clap 3.2.8",
"client-connections",
"client-core",
"coconut-interface",
"completions",
@@ -3262,7 +3253,6 @@ version = "1.1.0"
dependencies = [
"async-trait",
"clap 3.2.8",
"client-connections",
"completions",
"dirs",
"futures",
@@ -3309,7 +3299,6 @@ name = "nym-socks5-client"
version = "1.1.0"
dependencies = [
"clap 3.2.8",
"client-connections",
"client-core",
"coconut-interface",
"completions",
@@ -4134,7 +4123,6 @@ name = "proxy-helpers"
version = "0.1.0"
dependencies = [
"bytes",
"client-connections",
"futures",
"log",
"ordered-buffer",
-1
View File
@@ -26,7 +26,6 @@ members = [
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
"common/client-connections",
"common/coconut-interface",
"common/commands",
"common/config",
-3
View File
@@ -92,9 +92,6 @@ build-wallet:
build-connect:
cargo build --manifest-path nym-connect/Cargo.toml --workspace
build-explorer-api:
cargo build --manifest-path explorer-api/Cargo.toml --workspace
build-wasm-client:
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
+2 -4
View File
@@ -14,13 +14,11 @@ log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
sled = { version = "0.34", optional = true }
tap = "1.0.1"
thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] }
# internal
config = { path = "../../common/config" }
client-connections = { path = "../../common/client-connections" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
@@ -30,6 +28,7 @@ nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
tap = "1.0.1"
tokio = { version = "1.21.2", features = ["time", "macros"]}
@@ -57,5 +56,4 @@ tempfile = "3.1.0"
default = ["reply-surb"]
wasm = ["gateway-client/wasm"]
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
reply-surb = ["sled"]
reply-surb = ["sled"]
@@ -178,10 +178,6 @@ impl LoopCoverTrafficStream<OsRng> {
// This isn't a problem, if the channel is full means we're already sending the
// max amount of messages downstream can handle.
log::debug!("Failed to send cover message - channel full");
// 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");
}
TrySendError::Closed(_) => {
log::warn!("Failed to send cover message - channel closed");
@@ -1,4 +1,3 @@
use client_connections::TransmissionLane;
use futures::channel::mpsc;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySurb;
@@ -12,7 +11,6 @@ pub enum InputMessage {
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
lane: TransmissionLane,
},
Reply {
reply_surb: ReplySurb,
@@ -21,17 +19,11 @@ pub enum InputMessage {
}
impl InputMessage {
pub fn new_fresh(
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
lane: TransmissionLane,
) -> Self {
pub fn new_fresh(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Self {
InputMessage::Fresh {
recipient,
data,
with_reply_surb,
lane,
}
}
@@ -33,7 +33,7 @@ impl AcknowledgementListener {
}
async fn on_ack(&mut self, ack_content: Vec<u8>) {
trace!("Received an ack");
debug!("Received an ack");
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
.map(FragmentIdentifier::try_from_bytes)
{
@@ -8,7 +8,6 @@ use crate::client::{
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use client_connections::TransmissionLane;
use futures::StreamExt;
use log::*;
use nymsphinx::anonymous_replies::ReplySurb;
@@ -105,7 +104,6 @@ where
content: Vec<u8>,
with_reply_surb: bool,
) -> Option<Vec<RealMessage>> {
log::trace!("handling msg size: {}", content.len());
let topology_permit = self.topology_access.get_read_permit().await;
let topology = match topology_permit
.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient))
@@ -166,30 +164,26 @@ where
}
async fn on_input_message(&mut self, msg: InputMessage) {
let (real_messages, lane) = match msg {
let real_messages = match msg {
InputMessage::Fresh {
recipient,
data,
with_reply_surb,
lane,
} => (
} => {
self.handle_fresh_message(recipient, data, with_reply_surb)
.await,
lane,
),
InputMessage::Reply { reply_surb, data } => (
self.handle_reply(reply_surb, data)
.await
.map(|message| vec![message]),
TransmissionLane::Reply,
),
}
InputMessage::Reply { reply_surb, data } => self
.handle_reply(reply_surb, data)
.await
.map(|message| vec![message]),
};
// there's no point in trying to send nothing
if let Some(real_messages) = real_messages {
// tells real message sender (with the poisson timer) to send this to the mix network
self.real_message_sender
.unbounded_send((real_messages, lane))
.unbounded_send(real_messages)
.unwrap();
}
}
@@ -1,21 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
action_controller::{Action, ActionSender},
PendingAcknowledgement, RetransmissionRequestReceiver,
};
use super::action_controller::{Action, ActionSender};
use super::PendingAcknowledgement;
use super::RetransmissionRequestReceiver;
use crate::client::{
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use client_connections::TransmissionLane;
use futures::StreamExt;
use log::*;
use nymsphinx::{
acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer,
};
use nymsphinx::preparer::MessagePreparer;
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
@@ -117,10 +113,10 @@ where
// send to `OutQueueControl` to eventually send to the mix network
self.real_message_sender
.unbounded_send((
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
TransmissionLane::Retransmission,
))
.unbounded_send(vec![RealMessage::new(
prepared_fragment.mix_packet,
frag_id,
)])
.unwrap();
}
@@ -14,7 +14,6 @@ use crate::client::{
topology_control::TopologyAccessor,
};
use crate::spawn_future;
use client_connections::ClosedConnectionReceiver;
use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver;
use log::*;
@@ -111,7 +110,6 @@ impl RealMessagesController<OsRng> {
mix_sender: BatchMixMessageSender,
topology_access: TopologyAccessor,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
closed_connection_rx: ClosedConnectionReceiver,
) -> Self {
let rng = OsRng;
@@ -161,7 +159,6 @@ impl RealMessagesController<OsRng> {
rng,
config.self_recipient,
topology_access,
closed_connection_rx,
);
RealMessagesController {
@@ -4,7 +4,6 @@
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor;
use client_connections::{ClosedConnectionReceiver, ConnectionId, TransmissionLane};
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
@@ -17,6 +16,7 @@ use nymsphinx::forwarding::packet::MixPacket;
use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration;
use rand::{CryptoRng, Rng};
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
@@ -27,22 +27,22 @@ use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use self::{
sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
};
mod sending_delay_controller;
mod transmission_buffer;
#[cfg(not(target_arch = "wasm32"))]
fn get_time_now() -> time::Instant {
time::Instant::now()
}
#[cfg(target_arch = "wasm32")]
fn get_time_now() -> wasm_timer::Instant {
wasm_timer::Instant::now()
}
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
// the available buffer space we want to take somewhat swift action, but we still need to give a
// short time to give the channel a chance reduce pressure.
const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
// The minimum time between decreasing the average delay between packets. We don't want to change
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
// wait a little to see the effect before we decrease further.
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
// If we enough time passes without any sign of backpressure in the channel, we can consider
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
// bandwidth at all times.
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
// The maximum multiplier we apply to the base average Poisson delay.
const MAX_DELAY_MULTIPLIER: u32 = 6;
// The minium multiplier we apply to the base average Poisson delay.
const MIN_DELAY_MULTIPLIER: u32 = 1;
/// Configurable parameters of the `OutQueueControl`
pub(crate) struct Config {
@@ -85,6 +85,101 @@ impl Config {
}
}
struct SendingDelayController {
/// Multiply the average sending delay.
/// This is normally set to unity, but if we detect backpressure we increase this
/// multiplier. We use discrete steps.
current_multiplier: u32,
/// Maximum delay multiplier
upper_bound: u32,
/// Minimum delay multiplier
lower_bound: u32,
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
#[cfg(not(target_arch = "wasm32"))]
time_when_changed: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_changed: wasm_timer::Instant,
/// If we have a long enough time without any backpressure detected we try reducing the sending
/// delay multiplier
#[cfg(not(target_arch = "wasm32"))]
time_when_backpressure_detected: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_backpressure_detected: wasm_timer::Instant,
}
#[cfg(not(target_arch = "wasm32"))]
fn get_time_now() -> time::Instant {
time::Instant::now()
}
#[cfg(target_arch = "wasm32")]
fn get_time_now() -> wasm_timer::Instant {
wasm_timer::Instant::now()
}
impl SendingDelayController {
fn new(lower_bound: u32, upper_bound: u32) -> Self {
assert!(lower_bound <= upper_bound);
let now = get_time_now();
SendingDelayController {
current_multiplier: MIN_DELAY_MULTIPLIER,
upper_bound,
lower_bound,
time_when_changed: now,
time_when_backpressure_detected: now,
}
}
fn current_multiplier(&self) -> u32 {
self.current_multiplier
}
fn increase_delay_multiplier(&mut self) {
self.current_multiplier =
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Increasing sending delay multiplier to: {}",
self.current_multiplier
);
}
fn decrease_delay_multiplier(&mut self) {
self.current_multiplier =
(self.current_multiplier - 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Decreasing sending delay multiplier to: {}",
self.current_multiplier
);
}
fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
fn not_increased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
fn is_sending_reliable(&self) -> bool {
let now = get_time_now();
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
let acceptable_time_without_backpressure =
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
&& now > self.time_when_changed + delay_change_interval
}
}
pub(crate) struct OutQueueControl<R>
where
R: CryptoRng + Rng,
@@ -108,7 +203,7 @@ where
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
// messages.
sending_delay_controller: SendingDelayController,
sending_rate_controller: SendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
@@ -127,13 +222,8 @@ where
/// Accessor to the common instance of network topology.
topology_access: TopologyAccessor,
/// Buffer containing all incoming real messages keyed by transmission lane, that we will send
/// out to the mixnet.
transmission_buffer: TransmissionBuffer,
/// Incoming channel for being notified of closed connections, so that we can close lanes
/// corresponding to connections. To avoid sending traffic unnecessary
closed_connection_rx: ClosedConnectionReceiver,
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
received_buffer: VecDeque<RealMessage>,
}
pub(crate) struct RealMessage {
@@ -152,9 +242,8 @@ impl RealMessage {
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
// after sufficient delay
pub(crate) type BatchRealMessageSender =
mpsc::UnboundedSender<(Vec<RealMessage>, TransmissionLane)>;
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<(Vec<RealMessage>, TransmissionLane)>;
pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>;
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>;
pub(crate) enum StreamMessage {
Cover,
@@ -177,21 +266,22 @@ where
rng: R,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
closed_connection_rx: ClosedConnectionReceiver,
) -> Self {
OutQueueControl {
config,
ack_key,
sent_notifier,
next_delay: None,
sending_delay_controller: Default::default(),
sending_rate_controller: SendingDelayController::new(
MIN_DELAY_MULTIPLIER,
MAX_DELAY_MULTIPLIER,
),
mix_tx,
real_receiver,
our_full_destination,
rng,
topology_access,
transmission_buffer: Default::default(),
closed_connection_rx,
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
}
}
@@ -256,10 +346,6 @@ where
self.sent_notify(fragment_id);
}
// In addition to closing connections on receiving messages throught closed_connection_rx,
// also close connections when sufficiently stale.
self.transmission_buffer.prune_stale_connections();
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
@@ -271,41 +357,35 @@ where
tokio::task::yield_now().await;
}
fn on_close_connection(&mut self, connection_id: ConnectionId) {
log::debug!("Removing lane for connection: {connection_id}");
self.transmission_buffer
.remove(&TransmissionLane::ConnectionId(connection_id));
}
fn current_average_message_sending_delay(&self) -> Duration {
self.config.average_message_sending_delay
* self.sending_delay_controller.current_multiplier()
* self.sending_rate_controller.current_multiplier()
}
fn adjust_current_average_message_sending_delay(&mut self) {
let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity();
log::trace!(
"used_slots: {used_slots}, current_multiplier: {}",
self.sending_delay_controller.current_multiplier()
self.sending_rate_controller.current_multiplier()
);
// Even just a single used slot is enough to signal backpressure
if used_slots > 0 {
log::trace!("Backpressure detected");
self.sending_delay_controller.record_backpressure_detected();
self.sending_rate_controller.record_backpressure_detected();
}
// If the buffer is running out, slow down the sending rate
if self.mix_tx.capacity() == 0
&& self.sending_delay_controller.not_increased_delay_recently()
&& self.sending_rate_controller.not_increased_delay_recently()
{
self.sending_delay_controller.increase_delay_multiplier();
self.sending_rate_controller.increase_delay_multiplier();
}
// Very carefully step up the sending rate in case it seems like we can solidly handle the
// current rate.
if self.sending_delay_controller.is_sending_reliable() {
self.sending_delay_controller.decrease_delay_multiplier();
if self.sending_rate_controller.is_sending_reliable() {
self.sending_rate_controller.decrease_delay_multiplier();
}
}
@@ -315,13 +395,6 @@ where
self.adjust_current_average_message_sending_delay();
let avg_delay = self.current_average_message_sending_delay();
// Start by checking if we have any incoming messages about closed connections
// NOTE: this feels a bit iffy, the `OutQueueControl` is getting ripe for a rewrite to
// something simpler.
if let Poll::Ready(Some(id)) = Pin::new(&mut self.closed_connection_rx).poll_next(cx) {
self.on_close_connection(id);
}
if let Some(ref mut next_delay) = &mut self.next_delay {
// it is not yet time to return a message
if next_delay.as_mut().poll(cx).is_pending() {
@@ -346,35 +419,28 @@ where
next_delay.as_mut().reset(next_poisson_delay);
}
// On every iteration we get new messages from upstream. Given that these come bunched
// in `Vec`, this ensures that on average we will fetch messages faster than we can
// send, which is a condition for being able to multiplex sphinx packets from multiple
// data streams.
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
// decide what kind of message to send
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some((real_messages, conn_id))) => {
log::trace!("handling real_messages: size: {}", real_messages.len());
self.transmission_buffer.store(&conn_id, real_messages);
let real_next = self
.transmission_buffer
.pop_next_message_at_random()
.expect("we just added one");
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
Poll::Pending => {
if let Some(real_next) = self.transmission_buffer.pop_next_message_at_random() {
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} else {
// otherwise construct a dummy one
Poll::Ready(Some(StreamMessage::Cover))
}
}
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
}
} else {
// we never set an initial delay - let's do it now
@@ -396,9 +462,14 @@ where
}
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// Start by checking if we have any incoming messages about closed connections
if let Poll::Ready(Some(id)) = Pin::new(&mut self.closed_connection_rx).poll_next(cx) {
self.on_close_connection(id);
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
// if there are more messages immediately available, notify the runtime
// because we should be polled again
if !self.received_buffer.is_empty() {
cx.waker().wake_by_ref()
}
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
match Pin::new(&mut self.real_receiver).poll_next(cx) {
@@ -406,26 +477,17 @@ where
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some((real_messages, conn_id))) => {
log::trace!("handling real_messages: size: {}", real_messages.len());
// First store what we got for the given connection id
self.transmission_buffer.store(&conn_id, real_messages);
let real_next = self
.transmission_buffer
.pop_next_message_at_random()
.expect("we just added one");
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
Poll::Pending => {
if let Some(real_next) = self.transmission_buffer.pop_next_message_at_random() {
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} else {
Poll::Pending
}
}
// if there's nothing, then there's nothing
Poll::Pending => Poll::Pending,
}
}
@@ -440,47 +502,24 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
fn log_status(&self) {
let packets = self.transmission_buffer.total_size();
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
let lanes = self.transmission_buffer.num_lanes();
let mult = self.sending_delay_controller.current_multiplier();
let delay = self.current_average_message_sending_delay().as_millis();
if self.config.disable_poisson_packet_distribution {
log::info!(
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), no delay",
backlog
);
} else {
log::info!(
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), avg delay: {}ms ({mult})",
backlog,
delay
);
}
}
#[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(1));
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
_ = status_timer.tick() => {
self.log_status();
}
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;
next_message = self.next() => match next_message {
Some(next_message) => {
self.on_message(next_message).await;
},
None => {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
}
}
@@ -1,124 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::get_time_now;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
// the available buffer space we want to take somewhat swift action, but we still need to give a
// short time to give the channel a chance reduce pressure.
const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
// The minimum time between decreasing the average delay between packets. We don't want to change
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
// wait a little to see the effect before we decrease further.
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
// If we enough time passes without any sign of backpressure in the channel, we can consider
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
// bandwidth at all times.
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
// The maximum multiplier we apply to the base average Poisson delay.
const MAX_DELAY_MULTIPLIER: u32 = 6;
// The minium multiplier we apply to the base average Poisson delay.
const MIN_DELAY_MULTIPLIER: u32 = 1;
pub(crate) struct SendingDelayController {
/// Multiply the average sending delay.
/// This is normally set to unity, but if we detect backpressure we increase this
/// multiplier. We use discrete steps.
current_multiplier: u32,
/// Maximum delay multiplier
upper_bound: u32,
/// Minimum delay multiplier
lower_bound: u32,
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
#[cfg(not(target_arch = "wasm32"))]
time_when_changed: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_changed: wasm_timer::Instant,
/// If we have a long enough time without any backpressure detected we try reducing the sending
/// delay multiplier
#[cfg(not(target_arch = "wasm32"))]
time_when_backpressure_detected: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_backpressure_detected: wasm_timer::Instant,
}
impl Default for SendingDelayController {
fn default() -> Self {
SendingDelayController::new(MIN_DELAY_MULTIPLIER, MAX_DELAY_MULTIPLIER)
}
}
impl SendingDelayController {
pub(crate) fn new(lower_bound: u32, upper_bound: u32) -> Self {
assert!(lower_bound <= upper_bound);
let now = get_time_now();
SendingDelayController {
current_multiplier: MIN_DELAY_MULTIPLIER,
upper_bound,
lower_bound,
time_when_changed: now,
time_when_backpressure_detected: now,
}
}
pub(crate) fn current_multiplier(&self) -> u32 {
self.current_multiplier
}
pub(crate) fn increase_delay_multiplier(&mut self) {
if self.current_multiplier < self.upper_bound {
self.current_multiplier =
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Increasing sending delay multiplier to: {}",
self.current_multiplier
);
} else {
log::warn!("Trying to increase delay multipler higher than allowed");
}
}
pub(crate) fn decrease_delay_multiplier(&mut self) {
if self.current_multiplier > self.lower_bound {
self.current_multiplier =
(self.current_multiplier - 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Decreasing sending delay multiplier to: {}",
self.current_multiplier
);
}
}
pub(crate) fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
pub(crate) fn not_increased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
pub(crate) fn is_sending_reliable(&self) -> bool {
let now = get_time_now();
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
let acceptable_time_without_backpressure =
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
&& now > self.time_when_changed + delay_change_interval
}
}
@@ -1,207 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::TransmissionLane;
use rand::seq::SliceRandom;
use std::{
collections::{HashMap, HashSet, VecDeque},
time::Duration,
};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use super::{get_time_now, RealMessage};
// The number of lanes included in the oldest set. Used when we need to prioritize traffic.
const OLDEST_LANE_SET_SIZE: usize = 5;
// As a way of prune connections we also check for timeouts.
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
#[derive(Default)]
pub(crate) struct TransmissionBuffer {
buffer: HashMap<TransmissionLane, LaneBufferEntry>,
}
impl TransmissionBuffer {
#[allow(unused)]
pub(crate) fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry> {
self.buffer.remove(lane)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn num_lanes(&self) -> usize {
self.buffer.keys().count()
}
#[allow(unused)]
pub(crate) fn connections(&self) -> HashSet<u64> {
self.buffer
.keys()
.filter_map(|lane| match lane {
TransmissionLane::ConnectionId(id) => Some(id),
_ => None,
})
.copied()
.collect()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size(&self) -> usize {
self.buffer.values().map(LaneBufferEntry::len).sum()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size_in_bytes(&self) -> usize {
self.buffer
.values()
.map(|lane_buffer_entry| {
lane_buffer_entry
.real_messages
.iter()
.map(|real_message| real_message.mix_packet.sphinx_packet().len())
.sum::<usize>()
})
.sum()
}
fn get_oldest_set(&self) -> Vec<TransmissionLane> {
let mut buffer: Vec<_> = self
.buffer
.iter()
.map(|(k, v)| (k, v.messages_transmitted))
.collect();
buffer.sort_by_key(|v| v.1);
buffer
.iter()
.rev()
.map(|(k, _)| *k)
.take(OLDEST_LANE_SET_SIZE)
.copied()
.collect()
}
pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec<RealMessage>) {
if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) {
lane_buffer_entry.append(real_messages);
} else {
self.buffer
.insert(*lane, LaneBufferEntry::new(real_messages));
}
}
fn pick_random_lane(&self) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pick_random_small_lane(&self) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self
.buffer
.iter()
.filter(|(_, v)| v.is_small())
.map(|(k, _)| k)
.collect();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
let lanes = self.get_oldest_set();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
let real_msgs_queued = self.buffer.get_mut(lane)?;
let real_next = real_msgs_queued.pop_front()?;
real_msgs_queued.messages_transmitted += 1;
if real_msgs_queued.is_empty() {
self.buffer.remove(lane);
}
Some(real_next)
}
pub(crate) fn pop_next_message_at_random(&mut self) -> Option<RealMessage> {
if self.buffer.is_empty() {
return None;
}
// Very basic heuristic where we prioritize according to small lanes first, the older lanes
// to try to finish lanes when possible, then the rest.
let lane = if let Some(small_lane) = self.pick_random_small_lane() {
*small_lane
} else if let Some(old_lane) = self.pick_random_old_lane() {
old_lane
} else {
*self.pick_random_lane()?
};
log::trace!("picking to send from lane: {:?}", lane);
self.pop_front_from_lane(&lane)
}
pub(crate) fn prune_stale_connections(&mut self) {
let stale_entries: Vec<_> = self
.buffer
.iter()
.filter_map(|(lane, entry)| if entry.is_stale() { Some(lane) } else { None })
.copied()
.collect();
for lane in stale_entries {
self.remove(&lane);
}
}
}
pub(crate) struct LaneBufferEntry {
pub real_messages: VecDeque<RealMessage>,
pub messages_transmitted: usize,
#[cfg(not(target_arch = "wasm32"))]
pub time_for_last_activity: time::Instant,
#[cfg(target_arch = "wasm32")]
pub time_for_last_activity: wasm_timer::Instant,
}
impl LaneBufferEntry {
fn new(real_messages: Vec<RealMessage>) -> Self {
LaneBufferEntry {
real_messages: real_messages.into(),
messages_transmitted: 0,
time_for_last_activity: get_time_now(),
}
}
fn append(&mut self, real_messages: Vec<RealMessage>) {
self.real_messages.append(&mut real_messages.into());
self.time_for_last_activity = get_time_now();
}
fn pop_front(&mut self) -> Option<RealMessage> {
self.real_messages.pop_front()
}
fn is_small(&self) -> bool {
self.real_messages.len() < 100
}
fn is_stale(&self) -> bool {
get_time_now() - self.time_for_last_activity
> Duration::from_secs(MSG_CONSIDERED_STALE_AFTER_SECS)
}
#[cfg(not(target_arch = "wasm32"))]
fn len(&self) -> usize {
self.real_messages.len()
}
fn is_empty(&self) -> bool {
self.real_messages.is_empty()
}
}
@@ -208,7 +208,7 @@ impl ReceivedMessagesBuffer {
}
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
trace!(
debug!(
"Processing {:?} new message that might get added to the buffer!",
msgs.len()
);
-1
View File
@@ -33,7 +33,6 @@ tokio-tungstenite = "0.14" # websocket
## internal
client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
@@ -43,7 +43,6 @@ async fn send_file_with_reply() {
recipient,
message: read_data,
with_reply_surb: true,
connection_id: 0,
};
println!("sending content of 'dummy_file' over the mix network...");
@@ -92,7 +91,6 @@ async fn send_file_without_reply() {
recipient,
message: read_data,
with_reply_surb: false,
connection_id: 0,
};
println!("sending content of 'dummy_file' over the mix network...");
+6 -23
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::{ClosedConnectionReceiver, ClosedConnectionSender, TransmissionLane};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -111,7 +110,6 @@ impl NymClient {
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
@@ -119,7 +117,6 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
closed_connection_rx: ClosedConnectionReceiver,
shutdown: ShutdownListener,
) {
let mut controller_config = real_messages_control::Config::new(
@@ -149,7 +146,6 @@ impl NymClient {
mix_sender,
topology_accessor,
reply_key_storage,
closed_connection_rx,
)
.start_with_shutdown(shutdown);
}
@@ -283,16 +279,11 @@ impl NymClient {
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
closed_connection_tx: ClosedConnectionSender,
) {
info!("Starting websocket listener...");
let websocket_handler = websocket::Handler::new(
msg_input,
closed_connection_tx,
buffer_requester,
&self.as_mix_recipient(),
);
let websocket_handler =
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
}
@@ -301,8 +292,7 @@ impl NymClient {
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
pub 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);
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
self.input_tx
.as_ref()
@@ -413,17 +403,12 @@ impl NymClient {
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 (closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
closed_connection_rx,
shutdown.subscribe(),
);
@@ -440,11 +425,9 @@ impl NymClient {
}
match self.config.get_socket_type() {
SocketType::WebSocket => self.start_websocket_listener(
received_buffer_request_sender,
input_sender,
closed_connection_tx,
),
SocketType::WebSocket => {
self.start_websocket_listener(received_buffer_request_sender, input_sender)
}
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
+37 -49
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::{ClosedConnectionSender, TransmissionLane};
use client_core::client::{
inbound_messages::{InputMessage, InputMessageSender},
received_buffer::{
@@ -35,7 +34,6 @@ impl Default for ReceivedResponseType {
pub(crate) struct Handler {
msg_input: InputMessageSender,
closed_connection_tx: ClosedConnectionSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: Recipient,
socket: Option<WebSocketStream<TcpStream>>,
@@ -47,7 +45,6 @@ impl Clone for Handler {
fn clone(&self) -> Self {
Handler {
msg_input: self.msg_input.clone(),
closed_connection_tx: self.closed_connection_tx.clone(),
buffer_requester: self.buffer_requester.clone(),
self_full_address: self.self_full_address,
socket: None,
@@ -67,15 +64,13 @@ impl Drop for Handler {
impl Handler {
pub(crate) fn new(
msg_input: InputMessageSender,
closed_connection_tx: ClosedConnectionSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: &Recipient,
self_full_address: Recipient,
) -> Self {
Handler {
msg_input,
closed_connection_tx,
buffer_requester,
self_full_address: *self_full_address,
self_full_address,
socket: None,
received_response_type: Default::default(),
}
@@ -83,14 +78,12 @@ impl Handler {
fn handle_send(
&mut self,
recipient: &Recipient,
recipient: Recipient,
message: Vec<u8>,
with_reply_surb: bool,
connection_id: u64,
) -> Option<ServerResponse> {
// the ack control is now responsible for chunking, etc.
let lane = TransmissionLane::ConnectionId(connection_id);
let input_msg = InputMessage::new_fresh(*recipient, message, with_reply_surb, lane);
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
self.msg_input.unbounded_send(input_msg).unwrap();
None
@@ -111,27 +104,18 @@ impl Handler {
ServerResponse::SelfAddress(self.self_full_address)
}
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
self.closed_connection_tx
.unbounded_send(connection_id)
.unwrap();
None
}
fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
match request {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
connection_id,
} => self.handle_send(&recipient, message, with_reply_surb, connection_id),
} => self.handle_send(recipient, message, with_reply_surb),
ClientRequest::Reply {
message,
reply_surb,
} => self.handle_reply(reply_surb, message),
ClientRequest::SelfAddress => Some(self.handle_self_address()),
ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id),
}
}
@@ -150,7 +134,7 @@ impl Handler {
response.map(|resp| WsMessage::text(resp.into_text()))
}
fn handle_binary_message(&mut self, msg: &[u8]) -> Option<WsMessage> {
fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<WsMessage> {
debug!("Handling binary message request");
self.received_response_type = ReceivedResponseType::Binary;
@@ -170,11 +154,37 @@ impl Handler {
// old version of this file.
match raw_request {
WsMessage::Text(text_message) => self.handle_text_message(text_message),
WsMessage::Binary(binary_message) => self.handle_binary_message(&binary_message),
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message),
_ => None,
}
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_binary(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
.collect()
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_text(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
.collect()
}
async fn push_websocket_received_plaintexts(
&mut self,
reconstructed_messages: Vec<ReconstructedMessage>,
@@ -183,8 +193,10 @@ impl Handler {
// if it's text or binary, but for time being we use the naive assumption that if
// client is sending Message::Text it expects text back. Same for Message::Binary
let response_messages = match self.received_response_type {
ReceivedResponseType::Binary => prepare_reconstructed_binary(reconstructed_messages),
ReceivedResponseType::Text => prepare_reconstructed_text(reconstructed_messages),
ReceivedResponseType::Binary => {
self.prepare_reconstructed_binary(reconstructed_messages)
}
ReceivedResponseType::Text => self.prepare_reconstructed_text(reconstructed_messages),
};
let mut send_stream = futures::stream::iter(response_messages);
@@ -279,27 +291,3 @@ impl Handler {
self.listen_for_requests(reconstructed_receiver).await;
}
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_binary(
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
.collect()
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_text(
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
.collect()
}
@@ -20,9 +20,6 @@ pub const REPLY_REQUEST_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02;
/// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`]
pub const CLOSED_CONNECTION_REQUEST_TAG: u8 = 0x03;
#[allow(non_snake_case)]
#[derive(Debug)]
pub enum ClientRequest {
@@ -31,41 +28,32 @@ pub enum ClientRequest {
message: Vec<u8>,
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
with_reply_surb: bool,
connection_id: u64,
},
Reply {
message: Vec<u8>,
reply_surb: ReplySurb,
},
SelfAddress,
ClosedConnection(u64),
}
// we could have been parsing it directly TryFrom<WsMessage>, but we want to retain
// information about whether it came from binary or text to send appropriate response back
impl ClientRequest {
// SEND_REQUEST_TAG || with_surb || recipient || conn_id || data_len || data
fn serialize_send(
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
connection_id: u64,
) -> Vec<u8> {
// SEND_REQUEST_TAG || with_surb || recipient || data_len || data
fn serialize_send(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Vec<u8> {
let data_len_bytes = (data.len() as u64).to_be_bytes();
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(SEND_REQUEST_TAG)
.chain(std::iter::once(with_reply_surb as u8))
.chain(recipient.to_bytes().iter().cloned()) // will not be length prefixed because the length is constant
.chain(conn_id_bytes.iter().cloned())
.chain(data_len_bytes.iter().cloned())
.chain(data.into_iter())
.collect()
}
// SEND_REQUEST_TAG || with_reply || recipient || conn_id || data_len || data
// SEND_REQUEST_TAG || with_reply || recipient || data_len || data
fn deserialize_send(b: &[u8]) -> Result<Self, error::Error> {
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + 2*sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + 2 * size_of::<u64>() {
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::TooShortRequest,
"not enough data provided to recover 'send'".to_string(),
@@ -98,15 +86,9 @@ impl ClientRequest {
}
};
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes
.copy_from_slice(&b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
let data_len_bytes =
&b[2 + Recipient::LEN + size_of::<u64>()..2 + Recipient::LEN + 2 * size_of::<u64>()];
let data_len_bytes = &b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()];
let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap());
let data = &b[2 + Recipient::LEN + 2 * size_of::<u64>()..];
let data = &b[2 + Recipient::LEN + size_of::<u64>()..];
if data.len() as u64 != data_len {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
@@ -122,12 +104,11 @@ impl ClientRequest {
with_reply_surb,
recipient,
message: data.to_vec(),
connection_id,
})
}
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
fn serialize_reply(message: Vec<u8>, reply_surb: &ReplySurb) -> Vec<u8> {
fn serialize_reply(message: Vec<u8>, reply_surb: ReplySurb) -> Vec<u8> {
let reply_surb_bytes = reply_surb.to_bytes();
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
let message_len_bytes = (message.len() as u64).to_be_bytes();
@@ -221,43 +202,20 @@ impl ClientRequest {
ClientRequest::SelfAddress
}
// CLOSED_CONNECTION_REQUEST_TAG
fn serialize_closed_connection(connection_id: u64) -> Vec<u8> {
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(CLOSED_CONNECTION_REQUEST_TAG)
.chain(conn_id_bytes.iter().copied())
.collect()
}
// CLOSED_CONNECTION_REQUEST_TAG
fn deserialize_closed_connection(b: &[u8]) -> Self {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], CLOSED_CONNECTION_REQUEST_TAG);
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
ClientRequest::ClosedConnection(connection_id)
}
pub fn serialize(self) -> Vec<u8> {
match self {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
connection_id,
} => Self::serialize_send(recipient, message, with_reply_surb, connection_id),
} => Self::serialize_send(recipient, message, with_reply_surb),
ClientRequest::Reply {
message,
reply_surb,
} => Self::serialize_reply(message, &reply_surb),
} => Self::serialize_reply(message, reply_surb),
ClientRequest::SelfAddress => Self::serialize_self_address(),
ClientRequest::ClosedConnection(id) => Self::serialize_closed_connection(id),
}
}
@@ -287,16 +245,15 @@ impl ClientRequest {
SEND_REQUEST_TAG => Self::deserialize_send(b),
REPLY_REQUEST_TAG => Self::deserialize_reply(b),
SELF_ADDRESS_REQUEST_TAG => Ok(Self::deserialize_self_address(b)),
CLOSED_CONNECTION_REQUEST_TAG => Ok(Self::deserialize_closed_connection(b)),
n => Err(error::Error::new(
ErrorKind::UnknownRequest,
format!("type {n}"),
format!("type {}", n),
)),
}
}
pub fn try_from_binary(raw_req: &[u8]) -> Result<Self, error::Error> {
Self::deserialize(raw_req)
pub fn try_from_binary(raw_req: Vec<u8>) -> Result<Self, error::Error> {
Self::deserialize(&raw_req)
}
pub fn try_from_text(raw_req: String) -> Result<Self, error::Error> {
@@ -323,7 +280,6 @@ mod tests {
recipient,
message: b"foomp".to_vec(),
with_reply_surb: false,
connection_id: 42,
};
let bytes = send_request_no_surb.serialize();
@@ -333,12 +289,10 @@ mod tests {
recipient,
message,
with_reply_surb,
connection_id,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(!with_reply_surb);
assert_eq!(connection_id, 42)
assert!(!with_reply_surb)
}
_ => unreachable!(),
}
@@ -347,7 +301,6 @@ mod tests {
recipient,
message: b"foomp".to_vec(),
with_reply_surb: true,
connection_id: 213,
};
let bytes = send_request_surb.serialize();
@@ -357,12 +310,10 @@ mod tests {
recipient,
message,
with_reply_surb,
connection_id,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(with_reply_surb);
assert_eq!(connection_id, 213)
assert!(with_reply_surb)
}
_ => unreachable!(),
}
@@ -401,15 +352,4 @@ mod tests {
_ => unreachable!(),
}
}
#[test]
fn close_connection_request_serialization_works() {
let close_connection_request = ClientRequest::ClosedConnection(42);
let bytes = close_connection_request.serialize();
let recovered = ClientRequest::deserialize(&bytes).unwrap();
match recovered {
ClientRequest::ClosedConnection(id) => assert_eq!(id, 42),
_ => unreachable!(),
}
}
}
@@ -23,14 +23,10 @@ pub const RECEIVED_RESPONSE_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02;
/// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`]
pub const LANE_QUEUE_LENGTH_RESPONSE_TAG: u8 = 0x03;
#[derive(Debug)]
pub enum ServerResponse {
Received(ReconstructedMessage),
SelfAddress(Recipient),
LaneQueueLength(u64, usize),
Error(error::Error),
}
@@ -197,31 +193,6 @@ impl ServerResponse {
Ok(ServerResponse::SelfAddress(recipient))
}
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
fn serialize_lane_queue_length(lane: u64, queue_length: usize) -> Vec<u8> {
std::iter::once(LANE_QUEUE_LENGTH_RESPONSE_TAG)
.chain(lane.to_be_bytes().iter().cloned())
.chain(queue_length.to_be_bytes().iter().cloned())
.collect()
}
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
fn deserialize_lane_queue_length(b: &[u8]) -> Result<Self, error::Error> {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], LANE_QUEUE_LENGTH_RESPONSE_TAG);
let mut lane_bytes = [0u8; size_of::<u64>()];
lane_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let lane = u64::from_be_bytes(lane_bytes);
let mut queue_length_bytes = [0u8; size_of::<usize>()];
queue_length_bytes
.copy_from_slice(&b[1 + size_of::<u64>()..1 + size_of::<u64>() + size_of::<usize>()]);
let queue_length = usize::from_be_bytes(queue_length_bytes);
Ok(ServerResponse::LaneQueueLength(lane, queue_length))
}
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
fn serialize_error(error: error::Error) -> Vec<u8> {
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
@@ -301,9 +272,6 @@ impl ServerResponse {
Self::serialize_received(reconstructed_message)
}
ServerResponse::SelfAddress(address) => Self::serialize_self_address(address),
ServerResponse::LaneQueueLength(lane, queue_length) => {
Self::serialize_lane_queue_length(lane, queue_length)
}
ServerResponse::Error(err) => Self::serialize_error(err),
}
}
@@ -334,7 +302,6 @@ impl ServerResponse {
match response_tag {
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b),
LANE_QUEUE_LENGTH_RESPONSE_TAG => Self::deserialize_lane_queue_length(b),
ERROR_RESPONSE_TAG => Self::deserialize_error(b),
n => Err(error::Error::new(
ErrorKind::UnknownResponse,
@@ -411,20 +378,6 @@ mod tests {
}
}
#[test]
fn lane_queue_length_response_serialization_works() {
let lane_queue_length_response = ServerResponse::LaneQueueLength(13, 42);
let bytes = lane_queue_length_response.serialize();
let recovered = ServerResponse::deserialize(&bytes).unwrap();
match recovered {
ServerResponse::LaneQueueLength(lane, queue_length) => {
assert_eq!(lane, 13);
assert_eq!(queue_length, 42)
}
_ => unreachable!(),
}
}
#[test]
fn error_response_serialization_works() {
let dummy_error = error::Error::new(ErrorKind::UnknownRequest, "foomp message".to_string());
@@ -20,7 +20,6 @@ pub(super) enum ClientRequestText {
message: String,
recipient: String,
with_reply_surb: bool,
connection_id: u64,
},
SelfAddress,
#[serde(rename_all = "camelCase")]
@@ -47,7 +46,6 @@ impl TryInto<ClientRequest> for ClientRequestText {
message,
recipient,
with_reply_surb,
connection_id,
} => {
let message_bytes = message.into_bytes();
let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| {
@@ -58,7 +56,6 @@ impl TryInto<ClientRequest> for ClientRequestText {
message: message_bytes,
recipient,
with_reply_surb,
connection_id,
})
}
ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress),
@@ -94,10 +91,6 @@ pub(super) enum ServerResponseText {
SelfAddress {
address: String,
},
LaneQueueLength {
lane: u64,
queue_length: usize,
},
Error {
message: String,
},
@@ -139,9 +132,6 @@ impl From<ServerResponse> for ServerResponseText {
ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress {
address: recipient.to_string(),
},
ServerResponse::LaneQueueLength(lane, queue_length) => {
ServerResponseText::LaneQueueLength { lane, queue_length }
}
ServerResponse::Error(err) => ServerResponseText::Error {
message: err.to_string(),
},
-1
View File
@@ -26,7 +26,6 @@ url = "2.2"
# internal
client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
+1 -16
View File
@@ -9,7 +9,6 @@ use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_connections::{ClosedConnectionReceiver, ClosedConnectionSender};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -111,7 +110,6 @@ impl NymClient {
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
@@ -119,7 +117,6 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
closed_connection_rx: ClosedConnectionReceiver,
shutdown: ShutdownListener,
) {
let mut controller_config = client_core::client::real_messages_control::Config::new(
@@ -149,7 +146,6 @@ impl NymClient {
mix_sender,
topology_accessor,
reply_key_storage,
closed_connection_rx,
)
.start_with_shutdown(shutdown);
}
@@ -283,7 +279,6 @@ impl NymClient {
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
closed_connection_tx: ClosedConnectionSender,
shutdown: ShutdownListener,
) {
info!("Starting socks5 listener...");
@@ -298,11 +293,7 @@ impl NymClient {
self.as_mix_recipient(),
shutdown,
);
tokio::spawn(async move {
sphinx_socks
.serve(msg_input, buffer_requester, closed_connection_tx)
.await
});
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
@@ -405,17 +396,12 @@ impl NymClient {
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 (closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
closed_connection_rx,
shutdown.subscribe(),
);
@@ -434,7 +420,6 @@ impl NymClient {
self.start_socks5_listener(
received_buffer_request_sender,
input_sender,
closed_connection_tx,
shutdown.subscribe(),
);
+7 -12
View File
@@ -4,8 +4,8 @@ use super::authentication::{AuthenticationMethods, Authenticator, User};
use super::request::{SocksCommand, SocksRequest};
use super::types::{ResponseCode, SocksProxyError};
use super::{RESERVED, SOCKS_VERSION};
use client_connections::TransmissionLane;
use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use client_core::client::inbound_messages::InputMessage;
use client_core::client::inbound_messages::InputMessageSender;
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use log::*;
@@ -226,21 +226,17 @@ impl SocksClient {
}
}
fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
let msg = Message::Request(req);
let input_message = InputMessage::new_fresh(
self.service_provider,
msg.into_bytes(),
false,
TransmissionLane::ConnectionId(self.connection_id),
);
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false);
self.input_sender.unbounded_send(input_message).unwrap();
}
async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) {
self.send_connect_to_mixnet(remote_proxy_target.clone());
self.send_connect_to_mixnet(remote_proxy_target.clone())
.await;
let stream = self.stream.run_proxy();
let local_stream_remote = stream
@@ -263,8 +259,7 @@ impl SocksClient {
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
let provider_message = Message::Request(provider_request);
let lane = TransmissionLane::ConnectionId(conn_id);
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false, lane)
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false)
})
.await
.into_inner();
+1 -3
View File
@@ -4,7 +4,6 @@ use super::{
mixnet_responses::MixnetResponseListener,
types::{ResponseCode, SocksProxyError},
};
use client_connections::ClosedConnectionSender;
use client_core::client::{
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
};
@@ -52,14 +51,13 @@ impl SphinxSocksServer {
&mut self,
input_sender: InputMessageSender,
buffer_requester: ReceivedBufferRequestSender,
closed_connection_tx: ClosedConnectionSender,
) -> Result<(), SocksProxyError> {
let listener = TcpListener::bind(self.listening_address).await.unwrap();
info!("Serving Connections...");
// controller for managing all active connections
let (mut active_streams_controller, controller_sender) =
Controller::new(closed_connection_tx, self.shutdown.clone());
Controller::new(self.shutdown.clone());
tokio::spawn(async move {
active_streams_controller.run().await;
});
+2 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.1.0"
version = "1.0.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -29,7 +29,6 @@ url = "2.2"
# internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
@@ -60,4 +59,4 @@ wasm-opt = true
[profile.release]
lto = true
opt-level = 'z'
opt-level = 'z'
+1 -10
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use self::config::Config;
use client_connections::{ClosedConnectionReceiver, TransmissionLane};
use client_core::client::{
cover_traffic_stream::LoopCoverTrafficStream,
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
@@ -128,7 +127,6 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
closed_connection_rx: ClosedConnectionReceiver,
) {
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
@@ -153,7 +151,6 @@ impl NymClient {
input_receiver,
mix_sender,
topology_accessor,
closed_connection_rx,
)
.start();
}
@@ -330,10 +327,6 @@ impl NymClient {
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 (_closed_connection_tx, closed_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())
@@ -358,7 +351,6 @@ impl NymClient {
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
closed_connection_rx,
);
if !self.config.debug.disable_loop_cover_traffic_stream {
@@ -384,9 +376,8 @@ impl NymClient {
console_log!("Sending {} bytes to {}", message.len(), recipient);
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_fresh(recipient, message, false, lane);
let input_msg = InputMessage::new_fresh(recipient, message, false);
self.input_tx
.as_ref()
-9
View File
@@ -1,9 +0,0 @@
[package]
name = "client-connections"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3"
-21
View File
@@ -1,21 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
pub type ConnectionId = u64;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum TransmissionLane {
General,
Reply,
Retransmission,
Control,
ConnectionId(ConnectionId),
}
/// Announce connections that are closed, for whoever is interested.
/// One usecase is that the network-requester and socks5-client wants to know about this, so that
/// they can forward this to the `OutQueueControl` (via `ClientRequest` for the network-requester)
pub type ClosedConnectionSender = mpsc::UnboundedSender<ConnectionId>;
pub type ClosedConnectionReceiver = mpsc::UnboundedReceiver<ConnectionId>;
@@ -109,12 +109,6 @@ pub trait VestingSigningClient {
cap: Option<PledgeCap>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn transfer_ownership(
&self,
to_address: &str,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
@@ -411,25 +405,4 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn transfer_ownership(
&self,
to_address: &str,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TransferOwnership {
to_address: to_address.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TransferOwnership",
vec![],
)
.await
}
}
@@ -13,7 +13,7 @@ use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
pub struct SignatureOutputJson {
pub account_id: String,
pub public_key: PublicKey,
pub signature_as_hex: String,
pub signature: String,
}
#[derive(Debug, Parser)]
@@ -46,7 +46,7 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
let output = SignatureOutputJson {
account_id: account.address().to_string(),
public_key: account.public_key(),
signature_as_hex: signature.to_string(),
signature: signature.to_string(),
};
println!("{}", json!(output));
}
@@ -413,6 +413,4 @@ pub enum QueryMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub vesting_contract_address: Option<String>,
}
pub struct MigrateMsg {}
+1 -1
View File
@@ -10,7 +10,7 @@ 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 =
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
+1 -4
View File
@@ -14,11 +14,8 @@ tokio-util = { version = "0.7.3", features = [ "io" ] } # reason for getting thi
# In the long run, the dependency should probably get removed in favour of pure-tokio implementation, but for time being it's fine.
futures = "0.3"
log = "0.4"
# internal
client-connections = { path = "../../client-connections" }
ordered-buffer = { path = "../ordered-buffer" }
socks5-requests = { path = "../requests" }
ordered-buffer = { path = "../ordered-buffer" }
task = { path = "../../task" }
[dev-dependencies]
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::ClosedConnectionSender;
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
@@ -74,9 +73,6 @@ pub struct Controller {
// to avoid memory issues
recently_closed: HashSet<ConnectionId>,
// Broadcast closed connections
closed_connection_tx: ClosedConnectionSender,
// TODO: this can potentially be abused to ddos and kill provider. Not sure at this point
// how to handle it more gracefully
@@ -88,17 +84,13 @@ pub struct Controller {
}
impl Controller {
pub fn new(
closed_connection_tx: ClosedConnectionSender,
shutdown: ShutdownListener,
) -> (Self, ControllerSender) {
pub fn new(shutdown: ShutdownListener) -> (Self, ControllerSender) {
let (sender, receiver) = mpsc::unbounded();
(
Controller {
active_connections: HashMap::new(),
receiver,
recently_closed: HashSet::new(),
closed_connection_tx,
pending_messages: HashMap::new(),
shutdown,
},
@@ -135,9 +127,6 @@ impl Controller {
)
}
self.recently_closed.insert(conn_id);
// Announce closed connections, currently used by the `OutQueueControl`.
self.closed_connection_tx.unbounded_send(conn_id).unwrap();
}
fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec<u8>, is_closed: bool) {
@@ -183,15 +172,11 @@ impl Controller {
pending.push((payload, is_closed));
} else if !is_closed {
error!(
"Tried to write to closed connection {} ({} bytes were 'lost)",
conn_id,
"Tried to write to closed connection ({} bytes were 'lost)",
payload.len()
);
} else {
debug!(
"Tried to write to closed connection {}, but remote is already closed",
conn_id
)
debug!("Tried to write to closed connection, but remote is already closed")
}
}
@@ -63,10 +63,6 @@ where
// if we're sending through the mixnet increase the sequence number...
let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes();
log::trace!(
"pushing data down the input sender: size: {}",
ordered_msg.len()
);
mix_sender
.unbounded_send(adapter_fn(connection_id, ordered_msg, is_finished))
.unwrap();
-9
View File
@@ -1,12 +1,3 @@
## Unreleased
## Added
- Added migration code to the mixnet contract to allow updating stored vesting contract address to make it easier to deploy any future environments ([#1759],[#1769])
[#1759]: https://github.com/nymtech/nym/pull/1759
[#1769]: https://github.com/nymtech/nym/pull/1769
## [nym-contracts-v1.1.0](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.0) (2022-11-09)
### Changed
+2 -2
View File
@@ -287,9 +287,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.5"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
+2 -12
View File
@@ -467,20 +467,10 @@ pub fn query(
#[entry_point]
pub fn migrate(
deps: DepsMut<'_>,
_deps: DepsMut<'_>,
_env: Env,
msg: MigrateMsg,
_msg: MigrateMsg,
) -> Result<Response, MixnetContractError> {
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
// environment, one of the contracts will HAVE TO go through a migration
if let Some(vesting_contract_address) = msg.vesting_contract_address {
let mut current_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
current_state.vesting_contract_address =
deps.api.addr_validate(&vesting_contract_address)?;
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &current_state)?;
}
Ok(Default::default())
}
+2 -2
View File
@@ -420,9 +420,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.5"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813"
dependencies = [
"libc",
]
@@ -95,12 +95,12 @@ impl GeoLocateTask {
.await;
}
},
Err(_e) => {
// warn!(
// "❌ Oh no! Location for {} failed. Error: {:#?}",
// cache_item.mix_node().host,
// e
// );
Err(e) => {
warn!(
"❌ Oh no! Location for {} failed. Error: {:#?}",
cache_item.mix_node().host,
e
);
}
};
}
+3 -4
View File
@@ -57,7 +57,6 @@ export const MobileNav: React.FC<{ children: React.ReactNode }> = ({ children }:
borderRadius: 0,
}}
>
<MaintenanceBanner open={openMaintenance} onClick={() => setOpenMaintenance(false)} />
<Toolbar
disableGutters
sx={{
@@ -157,9 +156,9 @@ export const MobileNav: React.FC<{ children: React.ReactNode }> = ({ children }:
</List>
</Box>
</Drawer>
<Box sx={{ width: '100%', p: 4, mt: 7 }}>
{children}
<Box>
<MaintenanceBanner open={openMaintenance} onClick={() => setOpenMaintenance(false)} sx={{ mt: 7 }} />
<Box sx={{ width: '100%', p: 4 }}>{children}</Box>
<Footer />
</Box>
</Box>
+3 -5
View File
@@ -25,7 +25,6 @@ import { DarkLightSwitchDesktop } from './Switch';
import { NavOptionType } from '../context/nav';
const drawerWidth = 255;
const bannerHeight = 80;
const openedMixin = (theme: Theme): CSSObject => ({
width: drawerWidth,
@@ -272,7 +271,6 @@ export const Nav: React.FC = ({ children }) => {
borderRadius: 0,
}}
>
<MaintenanceBanner open={openMaintenance} onClick={() => setOpenMaintenance(false)} height={bannerHeight} />
<Toolbar
disableGutters
sx={{
@@ -337,7 +335,6 @@ export const Nav: React.FC = ({ children }) => {
style: {
background: theme.palette.nym.networkExplorer.nav.background,
borderRadius: 0,
top: openMaintenance ? bannerHeight : 0,
},
}}
>
@@ -376,8 +373,9 @@ export const Nav: React.FC = ({ children }) => {
))}
</List>
</Drawer>
<Box sx={{ width: '100%', py: 5, px: 6, mt: 7 }}>
{children}
<Box sx={{ width: '100%' }}>
<MaintenanceBanner open={openMaintenance} onClick={() => setOpenMaintenance(false)} sx={{ mt: 8 }} />
<Box sx={{ width: '100%', py: 5, px: 6 }}>{children}</Box>
<Footer />
</Box>
</Box>
+12 -12
View File
@@ -36,16 +36,16 @@ export const useMixnodeContext = (): React.ContextType<typeof MixnodeContext> =>
React.useContext<MixnodeState>(MixnodeContext);
interface MixnodeContextProviderProps {
mixId: string;
mixNodeIdentityKey: string;
}
/**
* Provides a state context for a mixnode by identity
* @param mixId The mixID of the mixnode
* @param mixNodeIdentityKey The identity key of the mixnode
*/
export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({ mixId, children }) => {
export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({ mixNodeIdentityKey, children }) => {
const [mixNode, fetchMixnodeById, clearMixnodeById] = useApiState<MixNodeResponseItem | undefined>(
mixId,
mixNodeIdentityKey,
Api.fetchMixnodeByID,
'Failed to fetch mixnode by id',
);
@@ -53,44 +53,44 @@ export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({
const [mixNodeRow, setMixnodeRow] = React.useState<MixnodeRowType | undefined>();
const [delegations, fetchDelegations, clearDelegations] = useApiState<DelegationsResponse>(
mixId,
mixNodeIdentityKey,
Api.fetchDelegationsById,
'Failed to fetch delegations for mixnode',
);
const [uniqDelegations, fetchUniqDelegations, clearUniqDelegations] = useApiState<UniqDelegationsResponse>(
mixId,
mixNodeIdentityKey,
Api.fetchUniqDelegationsById,
'Failed to fetch delegations for mixnode',
);
const [status, fetchStatus, clearStatus] = useApiState<StatusResponse>(
mixId,
mixNodeIdentityKey,
Api.fetchStatusById,
'Failed to fetch mixnode status',
);
const [stats, fetchStats, clearStats] = useApiState<StatsResponse>(
mixId,
mixNodeIdentityKey,
Api.fetchStatsById,
'Failed to fetch mixnode stats',
);
const [description, fetchDescription, clearDescription] = useApiState<MixNodeDescriptionResponse>(
mixId,
mixNodeIdentityKey,
Api.fetchMixnodeDescriptionById,
'Failed to fetch mixnode description',
);
const [economicDynamicsStats, fetchEconomicDynamicsStats, clearEconomicDynamicsStats] =
useApiState<MixNodeEconomicDynamicsStatsResponse>(
mixId,
mixNodeIdentityKey,
Api.fetchMixnodeEconomicDynamicsStatsById,
'Failed to fetch mixnode dynamics stats by id',
);
const [uptimeStory, fetchUptimeHistory, clearUptimeHistory] = useApiState<UptimeStoryResponse>(
mixId,
mixNodeIdentityKey,
Api.fetchUptimeStoryById,
'Failed to fetch mixnode uptime history',
);
@@ -123,7 +123,7 @@ export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({
fetchUptimeHistory(),
]);
});
}, [mixId]);
}, [mixNodeIdentityKey]);
const state = React.useMemo<MixnodeState>(
() => ({
+1 -1
View File
@@ -223,7 +223,7 @@ export const PageMixnodeDetail: React.FC = () => {
}
return (
<MixnodeContextProvider mixId={id}>
<MixnodeContextProvider mixNodeIdentityKey={id}>
<PageMixnodeDetailGuard />
</MixnodeContextProvider>
);
+1983
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "chitchat-test"
version = "0.3.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chitchat = "0.4"
poem = "1"
poem-openapi = {version="2", features = ["swagger-ui"] }
structopt = "0.3"
tokio = { version = "1.14.0", features = ["net", "sync", "rt-multi-thread", "macros", "time"] }
serde = { version="1", features=["derive"] }
serde_json = "1"
anyhow = "1"
once_cell = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
cool-id-generator = "1"
env_logger = "0.9"
[dev-dependencies]
assert_cmd = "2"
predicates = "2"
reqwest = { version = "0.11", default-features=false, features = ["blocking", "json"] }
[workspace]
+15
View File
@@ -0,0 +1,15 @@
# Chitchat test
Runs simple chitchat servers, mostly copied over from https://github.com/quickwit-oss/chitchat
## Example
```bash
# Starts 5 servers and joins them into a cluster on localhost ports 10000-10004
# All servers print cluster state on `/` ie 127.0.0.1:10000
# `/docs` endpoint has an open api with a key value setter, set it on one node and observe how the state propagates to the other nodes
# NodeState is a regular BTreeMap
./run-servers.sh
# run killall chitchat-test after you're done, as the servers will continue to run forever in the background
```
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
killall chitchat-test
cargo build --release
for i in $(seq 10000 10004)
do
listen_addr="127.0.0.1:$i";
echo ${listen_addr};
cargo run --release -- --listen_addr ${listen_addr} --seed 127.0.0.1:10000 --node_id node_$i &
done;
read
kill 0
+123
View File
@@ -0,0 +1,123 @@
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use chitchat::transport::UdpTransport;
use chitchat::{spawn_chitchat, Chitchat, ChitchatConfig, FailureDetectorConfig, NodeId};
use cool_id_generator::Size;
use poem::listener::TcpListener;
use poem::{Route, Server};
use poem_openapi::param::Query;
use poem_openapi::payload::Json;
use poem_openapi::{OpenApi, OpenApiService};
use structopt::StructOpt;
use tokio::sync::Mutex;
use chitchat::ClusterStateSnapshot;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct ApiResponse {
pub cluster_id: String,
pub cluster_state: ClusterStateSnapshot,
pub live_nodes: Vec<NodeId>,
pub dead_nodes: Vec<NodeId>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SetKeyValueResponse {
pub status: bool,
}
struct Api {
chitchat: Arc<Mutex<Chitchat>>,
}
#[OpenApi]
impl Api {
/// Chitchat state
#[oai(path = "/", method = "get")]
async fn index(&self) -> Json<serde_json::Value> {
let chitchat_guard = self.chitchat.lock().await;
let response = ApiResponse {
cluster_id: chitchat_guard.cluster_id().to_string(),
cluster_state: chitchat_guard.state_snapshot(),
live_nodes: chitchat_guard.live_nodes().cloned().collect::<Vec<_>>(),
dead_nodes: chitchat_guard.dead_nodes().cloned().collect::<Vec<_>>(),
};
Json(serde_json::to_value(&response).unwrap())
}
/// Set a key & value on this node (with no validation).
#[oai(path = "/set_kv/", method = "get")]
async fn set_kv(&self, key: Query<String>, value: Query<String>) -> Json<serde_json::Value> {
let mut chitchat_guard = self.chitchat.lock().await;
let cc_state = chitchat_guard.self_node_state();
cc_state.set(key.as_str(), value.as_str());
Json(serde_json::to_value(&SetKeyValueResponse { status: true }).unwrap())
}
}
#[derive(Debug, StructOpt)]
#[structopt(name = "chitchat", about = "Chitchat test server.")]
struct Opt {
/// Defines the socket addr on which we should listen to.
#[structopt(long = "listen_addr", default_value = "127.0.0.1:10000")]
listen_addr: SocketAddr,
/// Defines the socket_address (host:port) other servers should use to
/// reach this server.
///
/// It defaults to the listen address, but this is only valid
/// when all server are running on the same server.
#[structopt(long = "public_addr")]
public_addr: Option<SocketAddr>,
/// Node id. Has to be unique. If None, the node_id will be generated from
/// the public_addr and a random suffix.
#[structopt(long = "node_id")]
node_id: Option<String>,
#[structopt(long = "seed")]
seeds: Vec<String>,
#[structopt(long = "interval_ms", default_value = "500")]
interval: u64,
}
fn generate_server_id(public_addr: SocketAddr) -> String {
let cool_id = cool_id_generator::get_id(Size::Medium);
format!("server:{}-{}", public_addr, cool_id)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let opt = Opt::from_args();
println!("{:?}", opt);
let public_addr = opt.public_addr.unwrap_or(opt.listen_addr);
let node_id_str = opt
.node_id
.unwrap_or_else(|| generate_server_id(public_addr));
let node_id = NodeId::new(node_id_str, public_addr);
let config = ChitchatConfig {
node_id,
cluster_id: "testing".to_string(),
gossip_interval: Duration::from_millis(opt.interval),
listen_addr: opt.listen_addr,
seed_nodes: opt.seeds.clone(),
failure_detector_config: FailureDetectorConfig::default(),
};
let chitchat_handler = spawn_chitchat(config, Vec::new(), &UdpTransport).await?;
let chitchat = chitchat_handler.chitchat();
let api = Api { chitchat };
let api_service = OpenApiService::new(api, "Hello World", "1.0")
.server(&format!("http://{}/", opt.listen_addr));
let docs = api_service.swagger_ui();
let app = Route::new().nest("/", api_service).nest("/docs", docs);
Server::new(TcpListener::bind(&opt.listen_addr))
.run(app)
.await?;
Ok(())
}
+3 -13
View File
@@ -594,18 +594,10 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "client-connections"
version = "0.1.0"
dependencies = [
"futures",
]
[[package]]
name = "client-core"
version = "1.1.0"
version = "1.0.1"
dependencies = [
"client-connections",
"config",
"crypto",
"dirs",
@@ -3258,7 +3250,6 @@ name = "nym-socks5-client"
version = "1.1.0"
dependencies = [
"clap",
"client-connections",
"client-core",
"completions",
"config",
@@ -4043,7 +4034,6 @@ name = "proxy-helpers"
version = "0.1.0"
dependencies = [
"bytes",
"client-connections",
"futures",
"log",
"ordered-buffer",
@@ -5658,9 +5648,9 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.15.2"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]]
name = "thin-slice"
@@ -5,7 +5,7 @@ import { NymWordmark } from '@nymproject/react/logo/NymWordmark';
export const AppWindowFrame: React.FC = ({ children }) => (
<Box
sx={{
background: (t) => t.palette.background.default,
background: '#121726',
borderRadius: '12px',
padding: '12px 16px',
display: 'grid',
@@ -2,10 +2,10 @@ import React from 'react';
import { ConnectionStatusKind } from '../types';
const getBusyFillColor = (color: string): string => {
if (color === '#F4B02D') {
if (color === '#60D6EF') {
return '#21D072';
}
return '#F4B02D';
return '#60D6EF';
};
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
@@ -21,10 +21,10 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro
if (hover) {
return '#21D072';
}
return '#F4B02D';
return '#60D6EF';
case ConnectionStatusKind.connecting:
case ConnectionStatusKind.disconnecting:
return '#F4B02D';
return '#60D6EF';
default:
// connected
if (hover) {
@@ -2,10 +2,8 @@ import React, { useEffect, useMemo } from 'react';
import IconButton from '@mui/material/IconButton';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';
import KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';
import { Box, CircularProgress, Stack, Tooltip, Typography, ListItemIcon } from '@mui/material';
import Check from '@mui/icons-material/Check';
import ArrowDropDownCircleIcon from '@mui/icons-material/ArrowDropDownCircle';
import { Box, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
import { ServiceProvider, Service, Services } from '../types/directory';
type ServiceWithRandomSp = {
@@ -64,7 +62,7 @@ export const ServiceProviderSelector: React.FC<{
Loading services...
</Typography>
<IconButton id="service-provider-button" disabled>
{open ? <KeyboardArrowUpRoundedIcon /> : <KeyboardArrowDownRoundedIcon />}
<ArrowDropDownCircleIcon />
</IconButton>
</Box>
);
@@ -82,14 +80,14 @@ export const ServiceProviderSelector: React.FC<{
return (
<>
<Box
display="flex"
alignItems="center"
justifyContent="space-between"
sx={{ mt: 3, borderBottom: (theme) => `1px solid ${theme.palette.info.main}` }}
>
<Typography ref={textEl} fontSize={14} fontWeight={700} color={(theme) => theme.palette.info.main}>
{!service ? 'Select a service' : service.description}
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
<Typography
ref={textEl}
fontSize={14}
fontWeight={700}
color={(theme) => (serviceProvider ? undefined : theme.palette.primary.main)}
>
{service ? service.description : 'Select a service'}
</Typography>
<IconButton
id="service-provider-button"
@@ -97,11 +95,8 @@ export const ServiceProviderSelector: React.FC<{
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
color="info"
size="small"
sx={{ padding: 0 }}
>
{open ? <KeyboardArrowUpRoundedIcon /> : <KeyboardArrowDownRoundedIcon />}
<ArrowDropDownCircleIcon />
</IconButton>
</Box>
<Menu
@@ -117,11 +112,6 @@ export const ServiceProviderSelector: React.FC<{
vertical: 'top',
horizontal: 'left',
}}
PaperProps={{
sx: {
border: '1px solid rgba(96, 214, 239, 0.4)',
},
}}
MenuListProps={{
'aria-labelledby': 'service-provider-button',
sx: {
@@ -130,18 +120,7 @@ export const ServiceProviderSelector: React.FC<{
}}
>
{servicesWithRandomSp.map(({ id, description, sp }) => (
<MenuItem
dense
autoFocus={id === service?.id}
key={id}
sx={{
fontSize: 'small',
fontWeight: 'bold',
minWidth: '208px',
'&.Mui-focusVisible': { bgcolor: 'transparent' },
}}
onClick={() => handleClose(sp)}
>
<MenuItem dense key={id} sx={{ fontSize: 'small', fontWeight: 'bold' }} onClick={() => handleClose(sp)}>
<Tooltip
title={
<Stack direction="column">
@@ -164,16 +143,6 @@ export const ServiceProviderSelector: React.FC<{
>
<Typography>{description}</Typography>
</Tooltip>
{id === service?.id && (
<ListItemIcon
sx={{
position: 'absolute',
right: '0',
}}
>
<Check sx={{ padding: 0 }} />
</ListItemIcon>
)}
</MenuItem>
))}
</Menu>
+4 -4
View File
@@ -24,17 +24,17 @@ const nymPalette: NymPalette = {
success: '#21D073',
info: '#60D7EF',
fee: '#967FF0',
background: { light: '#F4F6F8', dark: '#1D2125' },
background: { light: '#F4F6F8', dark: '#121726' },
text: {
light: '#F2F2F2',
dark: '#1D2125',
dark: '#121726',
},
};
const darkMode: NymPaletteVariant = {
mode: 'dark',
background: {
main: '#1D2125',
main: '#121726',
paper: '#242C3D',
},
text: {
@@ -52,7 +52,7 @@ const lightMode: NymPaletteVariant = {
paper: '#FFFFFF',
},
text: {
main: '#1D2125',
main: '#121726',
},
topNav: {
background: '#111826',
+2 -2
View File
@@ -8,8 +8,8 @@
- wallet: Group delegations on unbonded nodes at the top of the list
- wallet: Simplified delegation screen when user has no delegations
- wallet: Update "create password" howto guide
- wallet: Allow setting of operating cost when node is bonded
- wallet: Allow update of operating cost on bonded node
- wallet: Allow setting of operatoring cost when node is bonded
- wallet: Allow update of operatoring cost on bonded node
- wallet: New bond settings area
- wallet: Display pending unbonds
- wallet: Display routing score and average score for gateways
+5 -5
View File
@@ -793,9 +793,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.5"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
@@ -811,9 +811,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -2877,7 +2877,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.1.1"
version = "1.1.0"
dependencies = [
"aes-gcm",
"argon2 0.3.4",
+4 -4
View File
@@ -160,9 +160,9 @@ mod qa {
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
"n1frq2hzkjtatsupc6jtyaz67ytydk9nya437q92qg76ny3y8fcnjsw806vg";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
@@ -177,8 +177,8 @@ mod qa {
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qwerty-validator.qa.nymte.ch/",
Some("https://qwerty-validator-api.qa.nymte.ch/api"),
"https://adv-epoch-qa-validator.qa.nymte.ch/",
Some("https://adv-epoch-qa-val-api.qa.nymte.ch/api"),
)]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-wallet-app",
"version": "1.1.1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym_wallet"
version = "1.1.1"
version = "1.1.0"
description = "Nym Native Wallet"
authors = ["Nym Technologies SA"]
license = ""
@@ -72,7 +72,7 @@ mod base64 {
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let s = <String>::deserialize(deserializer)?;
base64::decode(s).map_err(serde::de::Error::custom)
base64::decode(&s).map_err(serde::de::Error::custom)
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "1.1.1"
"version": "1.1.0"
},
"build": {
"distDir": "../dist",
@@ -91,7 +91,7 @@ export const BondedGateway = ({
{network && (
<Typography sx={{ mt: 2, fontSize: 'small' }}>
Check more stats of your gateway on the{' '}
<Link href={`${urls(network).networkExplorer}/network-components/gateway/${identityKey}`} target="_blank">
<Link href={`${urls(network).networkExplorer}/network-components/gateways`} target="_blank">
explorer
</Link>
</Typography>
@@ -42,7 +42,7 @@ const headers: Header[] = [
header: 'Operator rewards',
id: 'operator-rewards',
tooltipText:
'This is your (operator) rewards including the PM and cost. Rewards are automatically compounded every epoch. You can redeem your rewards at any time.',
'This is your (operator) new rewards including the PM and cost. You can compound your rewards manually every epoch or unbond your node to redeem them.',
},
{
header: 'No. delegators',
@@ -66,7 +66,6 @@ export const BondedMixnode = ({
const navigate = useNavigate();
const {
name,
mixId,
stake,
bond,
stakeSaturation,
@@ -166,7 +165,7 @@ export const BondedMixnode = ({
{network && (
<Typography sx={{ mt: 2, fontSize: 'small' }}>
Check more stats of your node on the{' '}
<Link href={`${urls(network).networkExplorer}/network-components/mixnode/${mixId}`} target="_blank">
<Link href={`${urls(network).networkExplorer}/network-components/mixnodes`} target="_blank">
explorer
</Link>
</Typography>
@@ -45,7 +45,7 @@ export const DelegationItem = ({
) : (
<Link
target="_blank"
href={`${explorerUrl}/network-components/mixnode/${item.mix_id}`}
href={`${explorerUrl}/network-components/mixnode/${item.node_identity}`}
text={`${item.node_identity.slice(0, 6)}...${item.node_identity.slice(-6)}`}
color="text.primary"
noIcon
@@ -8,7 +8,7 @@ export const PendingDelegationItem = ({ item, explorerUrl }: { item: WrappedDele
<TableCell>
<Link
target="_blank"
href={`${explorerUrl}/network-components/mixnode/${item.event.mix_id}`}
href={`${explorerUrl}/network-components/mixnode/${item.node_identity}`}
text={`${item.node_identity.slice(0, 6)}...${item.node_identity.slice(-6)}`}
color="text.primary"
noIcon
-2
View File
@@ -48,7 +48,6 @@ import {
export type TBondedMixnode = {
name?: string;
mixId: number;
identityKey: string;
stake: DecCoin;
bond: DecCoin;
@@ -267,7 +266,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
const routingScore = await getAvgUptime();
setBondedNode({
name: nodeDescription?.name,
mixId: mix_id,
identityKey: bond_information.mix_node.identity_key,
stake: {
amount: calculateStake(rewarding_details.operator, rewarding_details.delegates),
+9 -16
View File
@@ -1,20 +1,14 @@
import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { getDelegationSummary, undelegateAllFromMixnode, undelegateFromMixnode } from 'src/requests/delegation';
import { getDelegationSummary, undelegateAllFromMixnode } from 'src/requests/delegation';
import {
DelegationWithEverything,
FeeDetails,
DecCoin,
TransactionExecuteResult,
WrappedDelegationEvent,
Fee,
} from '@nymproject/types';
import type { Network } from 'src/types';
import {
delegateToMixnode,
getAllPendingDelegations,
vestingDelegateToMixnode,
vestingUndelegateFromMixnode,
} from 'src/requests';
import { delegateToMixnode, getAllPendingDelegations, vestingDelegateToMixnode } from 'src/requests';
import { TPoolOption } from 'src/components';
import { decCoinToDisplay } from 'src/utils';
@@ -31,8 +25,11 @@ export type TDelegationContext = {
tokenPool: TPoolOption,
fee?: FeeDetails,
) => Promise<TransactionExecuteResult>;
undelegate: (mix_id: number, fee?: Fee) => Promise<TransactionExecuteResult>;
undelegateVesting: (mix_id: number) => Promise<TransactionExecuteResult>;
undelegate: (
mix_id: number,
usesVestingContractTokens: boolean,
fee?: FeeDetails,
) => Promise<TransactionExecuteResult[]>;
};
export type TDelegationTransaction = {
@@ -54,10 +51,7 @@ export const DelegationContext = createContext<TDelegationContext>({
addDelegation: async () => {
throw new Error('Not implemented');
},
undelegate: () => {
throw new Error('Not implemented');
},
undelegateVesting: () => {
undelegate: async () => {
throw new Error('Not implemented');
},
});
@@ -141,8 +135,7 @@ export const DelegationContextProvider: FC<{
totalRewards,
refresh,
addDelegation,
undelegate: undelegateFromMixnode,
undelegateVesting: vestingUndelegateFromMixnode,
undelegate: undelegateAllFromMixnode,
}),
[isLoading, error, delegations, pendingDelegations, totalDelegations],
);
-1
View File
@@ -8,7 +8,6 @@ const SLEEP_MS = 1000;
const bondedMixnodeMock: TBondedMixnode = {
name: 'Monster node',
mixId: 1,
identityKey: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
stake: { denom: 'nym', amount: '1234' },
bond: { denom: 'nym', amount: '1234' },
+17 -32
View File
@@ -1,12 +1,5 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import {
DelegationWithEverything,
DecCoin,
TransactionExecuteResult,
FeeDetails,
Fee,
CurrencyDenom,
} from '@nymproject/types';
import { DelegationWithEverything, DecCoin, TransactionExecuteResult, FeeDetails } from '@nymproject/types';
import { DelegationContext, TDelegationTransaction } from '../delegations';
import { mockSleep } from './utils';
@@ -161,7 +154,11 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => {
};
};
const undelegate = async (mix_id: number, _fee?: Fee): Promise<TransactionExecuteResult> => {
const undelegate = async (
mix_id: number,
_usesVestingContractTokens: boolean,
_fee?: FeeDetails,
): Promise<TransactionExecuteResult[]> => {
await mockSleep(SLEEP_MS);
mockDelegations = mockDelegations.map((d) => {
if (d.mix_id === mix_id) {
@@ -178,29 +175,18 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => {
triggerStateUpdate();
}, 3000);
return {
logs_json: '',
data_json: '',
transaction_hash: '',
gas_info: {
gas_wanted: { gas_units: BigInt(1) },
gas_used: { gas_units: BigInt(1) },
return [
{
logs_json: '',
data_json: '',
transaction_hash: '',
gas_info: {
gas_wanted: { gas_units: BigInt(1) },
gas_used: { gas_units: BigInt(1) },
},
fee: { amount: '1', denom: 'nym' },
},
fee: { amount: '1', denom: 'nym' as CurrencyDenom },
};
};
const undelegateVesting = async (mix_id: number, _fee?: FeeDetails) => {
return {
logs_json: '',
data_json: '',
transaction_hash: '',
gas_info: {
gas_wanted: { gas_units: BigInt(1) },
gas_used: { gas_units: BigInt(1) },
},
fee: { amount: '1', denom: 'nym' as CurrencyDenom },
};
];
};
const resetState = () => {
@@ -240,7 +226,6 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => {
addDelegation,
updateDelegation,
undelegate,
undelegateVesting,
}),
[isLoading, error, delegations, totalDelegations, trigger],
);
+9 -2
View File
@@ -130,10 +130,16 @@ const TokenTransfer = () => {
</Grid>
<Grid item>
<Typography variant="subtitle2" sx={{ color: (t) => t.palette.nym.text.muted, mt: 2 }}>
Unlocked transferable tokens
Transferable tokens
</Typography>
<Typography data-testid="refresh-success" sx={{ color: 'text.primary' }} variant="h5" textTransform="uppercase">
<Typography
data-testid="refresh-success"
sx={{ color: 'text.primary' }}
variant="h5"
fontWeight="700"
textTransform="uppercase"
>
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()}
</Typography>
</Grid>
@@ -161,6 +167,7 @@ export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> })
<NymCard
title="Vesting Schedule"
data-testid="check-unvested-tokens"
Icon={<InfoOutlined />}
Action={
<IconButton
onClick={async () => {
@@ -33,17 +33,10 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => {
</Grid>
<Grid item container direction={'column'} spacing={2} width={0.5} padding={3}>
<Grid item>
<Box sx={{ mb: 1 }}>
<Error
message={`Remember you should only unbond if you want to remove your ${
isMixnode(bondedNode) ? 'node' : 'gateway'
} from the network for good.`}
/>
</Box>
<Error
message={`Unbonding is irreversible and it wont be possible to restore the current state of your ${
isMixnode(bondedNode) ? 'node' : 'gateway'
} again.`}
} again`}
/>
</Grid>
<Grid item>
@@ -4,20 +4,15 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { Button, Divider, Typography, TextField, Grid, CircularProgress, Box } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { isMixnode } from 'src/types';
import { simulateUpdateMixnodeConfig, simulateVestingUpdateMixnodeConfig, updateMixnodeConfig } from 'src/requests';
import { updateMixnodeConfig } from 'src/requests';
import { TBondedMixnode, TBondedGateway } from 'src/context/bonding';
import { SimpleModal } from 'src/components/Modals/SimpleModal';
import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
import { Console } from 'src/utils/console';
import { Alert } from 'src/components/Alert';
import { vestingUpdateMixnodeConfig } from 'src/requests/vesting';
import { ConfirmTx } from 'src/components/ConfirmTX';
import { useGetFee } from 'src/hooks/useGetFee';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
const { getFee, fee, resetFeeState } = useGetFee();
const theme = useTheme();
@@ -38,7 +33,6 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
verlocPort?: number;
httpApiPort?: number;
}) => {
resetFeeState();
const { host, version, mixPort, verlocPort, httpApiPort } = data;
if (host && version && mixPort && verlocPort && httpApiPort) {
const MixNodeConfigParams = {
@@ -49,11 +43,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
version,
};
try {
if (bondedNode.proxy) {
await vestingUpdateMixnodeConfig(MixNodeConfigParams);
} else {
await updateMixnodeConfig(MixNodeConfigParams);
}
await updateMixnodeConfig(MixNodeConfigParams);
setOpenConfirmationModal(true);
} catch (error) {
Console.error(error);
@@ -63,22 +53,10 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
return (
<Grid container xs item>
{fee && (
<ConfirmTx
open
header="Update node settings"
fee={fee}
onConfirm={handleSubmit((d) => onSubmit(d))}
onPrev={resetFeeState}
onClose={resetFeeState}
/>
)}
{isSubmitting && <LoadingModal />}
<Alert
title={
<Box sx={{ fontWeight: 600 }}>
Changing these values will ONLY change the data about your node on the blockchain. Remember to change your
nodes config file with the same values too
Your changes will be ONLY saved on the display. Remember to change the values on your nodes config file too
</Box>
}
dismissable
@@ -89,6 +67,16 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Port
</Typography>
<Typography
variant="body1"
sx={{
fontSize: 14,
mb: 2,
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
}}
>
Change profit margin of your node
</Typography>
</Grid>
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
<Grid item width={1}>
@@ -132,6 +120,16 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Host
</Typography>
<Typography
variant="body1"
sx={{
fontSize: 14,
mb: 2,
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
}}
>
Lock wallet after certain time
</Typography>
</Grid>
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
<Grid item width={1}>
@@ -153,6 +151,16 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Version
</Typography>
<Typography
variant="body1"
sx={{
fontSize: 14,
mb: 2,
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
}}
>
Lock wallet after certain time
</Typography>
</Grid>
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
<Grid item width={1}>
@@ -174,24 +182,18 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) =>
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeConfig : simulateUpdateMixnodeConfig, {
host: data.host,
mix_port: data.mixPort,
verloc_port: data.verlocPort,
http_api_port: data.httpApiPort,
version: data.version,
}),
)}
sx={{ m: 3 }}
onClick={handleSubmit((d) => onSubmit(d))}
type="submit"
sx={{ m: 3, width: '320px' }}
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Submit changes to the blockchain
Save all display changes
</Button>
</Grid>
</Grid>
<SimpleModal
open={openConfirmationModal}
header="Your changes are submitted to the blockchain"
header="Your changes were ONLY saved on the display"
subHeader="Remember to change the values
on your nodes config file too."
okLabel="close"
@@ -214,6 +216,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
textAlign: 'center',
color: theme.palette.nym.nymWallet.text.blue,
fontSize: 16,
textTransform: 'capitalize',
}}
subHeaderStyles={{
width: '100%',
@@ -221,6 +224,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
textAlign: 'center',
color: 'main',
fontSize: 14,
textTransform: 'capitalize',
}}
/>
</Grid>
@@ -14,27 +14,17 @@ import {
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
import { add, format, fromUnixTime } from 'date-fns';
import { isMixnode } from 'src/types';
import {
getCurrentInterval,
getPendingIntervalEvents,
simulateUpdateMixnodeCostParams,
simulateVestingUpdateMixnodeCostParams,
updateMixnodeCostParams,
vestingUpdateMixnodeCostParams,
} from 'src/requests';
import { TBondedMixnode } from 'src/context/bonding';
import { getCurrentInterval, getPendingIntervalEvents, updateMixnodeCostParams } from 'src/requests';
import { TBondedMixnode, TBondedGateway } from 'src/context/bonding';
import { SimpleModal } from 'src/components/Modals/SimpleModal';
import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
import { Console } from 'src/utils/console';
import { Alert } from 'src/components/Alert';
import { ChangeMixCostParams } from 'src/pages/bonding/types';
import { AppContext } from 'src/context';
import { useGetFee } from 'src/hooks/useGetFee';
import { ConfirmTx } from 'src/components/ConfirmTX';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
@@ -44,8 +34,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
const { clientDetails } = useContext(AppContext);
const theme = useTheme();
const { fee, getFee, resetFeeState } = useGetFee();
const defaultValues = {
operatorCost: bondedNode.operatorCost,
profitMargin: bondedNode.profitMargin,
@@ -108,7 +96,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
}, []);
const onSubmit = async (data: { operatorCost: { amount: string; denom: CurrencyDenom }; profitMargin: string }) => {
resetFeeState();
if (data.operatorCost && data.profitMargin) {
const MixNodeCostParams = {
profit_margin_percent: (+data.profitMargin / 100).toString(),
@@ -118,11 +105,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
},
};
try {
if (bondedNode.proxy) {
await vestingUpdateMixnodeCostParams(MixNodeCostParams);
} else {
await updateMixnodeCostParams(MixNodeCostParams);
}
await updateMixnodeCostParams(MixNodeCostParams);
await getPendingEvents();
reset();
setOpenConfirmationModal(true);
@@ -134,17 +117,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
return (
<Grid container xs item>
{fee && (
<ConfirmTx
open
header="Update cost parameters"
fee={fee}
onConfirm={handleSubmit((d) => onSubmit(d))}
onPrev={resetFeeState}
onClose={resetFeeState}
/>
)}
{isSubmitting && <LoadingModal />}
<Alert
title={
<>
@@ -157,7 +129,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
/>
<Grid container direction="column">
<Grid item container alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
<Grid item xl={6}>
<Grid item>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Profit Margin
</Typography>
@@ -251,16 +223,12 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) => {
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeCostParams : simulateUpdateMixnodeCostParams, {
profit_margin_percent: (+data.profitMargin / 100).toString(),
interval_operating_cost: data.operatorCost,
});
})}
onClick={handleSubmit(onSubmit)}
type="submit"
sx={{ m: 3 }}
sx={{ m: 3, width: '320px' }}
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Submit changes to the blockchain
Save all display changes
</Button>
</Grid>
</Grid>
@@ -268,7 +236,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
open={openConfirmationModal}
header="Your changes will take place
in the next interval"
okLabel="Close"
okLabel="close"
hideCloseIcon
displayInfoIcon
onOk={async () => {
@@ -288,6 +256,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
textAlign: 'center',
color: theme.palette.nym.nymWallet.text.blue,
fontSize: 16,
textTransform: 'capitalize',
}}
subHeaderStyles={{
m: 0,
+7 -17
View File
@@ -52,7 +52,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
isLoading,
addDelegation,
undelegate,
undelegateVesting,
refresh: refreshDelegations,
} = useDelegationContext();
@@ -207,8 +206,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
const handleUndelegate = async (
mixId: number,
// identityKey is no longer used
_: string,
identityKey: string,
usesVestingContractTokens: boolean,
fee?: FeeDetails,
) => {
@@ -218,27 +216,19 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
});
setShowUndelegateModal(false);
setCurrentDelegationListActionItem(undefined);
let tx;
try {
if (usesVestingContractTokens) {
tx = await undelegateVesting(mixId);
} else {
tx = await undelegate(mixId, fee?.fee);
}
// const txs = await undelegate(mixId, usesVestingContractTokens, fee);
try {
const txs = await undelegate(mixId, usesVestingContractTokens, fee);
const balances = await getAllBalances();
setConfirmationModalProps({
status: 'success',
action: 'undelegate',
...balances,
transactions: [
{
url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`,
hash: tx.transaction_hash,
},
],
transactions: txs.map((tx) => ({
url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`,
hash: tx.transaction_hash,
})),
});
} catch (e) {
Console.error('Failed to undelegate', e);
+25 -3
View File
@@ -1,8 +1,30 @@
# Nym SDK (Typescript)
## Packages
The Nym SDK for Typescript will get you creating apps that can use the Nym Mixnet and Coconut credentials quickly.
- [SDK](packages/sdk) - the Nym SDK package
## TL;DR
Include the SDK in your project:
```
npm install @nymproject/sdk
```
Open a connection to a Gateway on the Nym Mixnet:
```ts
import { client } from '@nymproject/sdk';
const session = await client.connect('<<GATEWAY>>');
```
This will start the WASM client on a worker thread, so that your code can stay nice and snappy.
Send a message to another user (you will need to know their address at a Gateway):
```ts
const result = await client.send('<<USER ADDRESS>>', 'Hello Timmy!');
```
## Examples
@@ -13,4 +35,4 @@ Coming soon:
- [Node tester](examples/node-tester) - a React app that sends test packets to a mixnode and measure the network speed
- [Mixnet topology viewer](examples/topology) - a Svelte app that shows the mixnodes current in the active set
- [Get a bandwidth voucher](examples/coconut-bandwidth-voucher) - get a bandwidth voucher to use the mixnet
- [Get a bandwidth voucher](examples/coconut-bandwidth-voucher) - get a bandwidth voucher to use the mixnet
-58
View File
@@ -1,58 +0,0 @@
# Nym SDK (Typescript)
The Nym SDK for Typescript will get you creating apps that can use the Nym Mixnet and Coconut credentials quickly.
## TL;DR
Include the SDK in your project:
```
npm install @nymproject/sdk
```
Open a connection to a Gateway on the Nym Mixnet:
```ts
import { createNymMixnetClient } from '@nymproject/sdk';
const main = async () => {
const nym = await createNymMixnetClient();
const validatorApiUrl = 'https://validator.nymtech.net/api';
// show message payload content when received
nym.events.subscribeToTextMessageReceivedEvent((e) => {
console.log('Got a message: ', e.args.payload);
});
// start the client and connect to a gateway
await nym.client.start({
clientId: 'My awesome client',
validatorApiUrl,
});
// send a message to yourself
const payload = 'Hello mixnet';
const recipient = nym.client.selfAddress();
nym.client.sendMessage({ payload, recipient });
};
```
This will start the WASM client on a worker thread, so that your code can stay nice and snappy.
Send a message to another user (you will need to know their address at a Gateway):
```ts
const payload = 'Hello mixnet';
const recipient = '<< RECIPIENT ADDRESS GOES HERE >>';
await nym.client.sendMessage({ payload, recipient });
```
### Packaging
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:
1. bump version numbers as necessary for SemVer
2. `yarn build` builds the release directory
3. `npm publish --access=public` will publish your changed package to NPM
+4 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/sdk",
"version": "1.1.4",
"version": "1.0.0",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
"main": "dist/index.js",
@@ -10,8 +10,7 @@
"dist/nym_client_wasm.d.ts",
"dist/nym_client_wasm.js",
"dist/nym_client_wasm_bg.wasm",
"dist/nym_client_wasm_bg.wasm.d.ts",
"dist/**/*"
"dist/nym_client_wasm_bg.wasm.d.ts"
],
"exports": {
".": "./dist/index.js",
@@ -28,10 +27,9 @@
"build:dependencies:nym-client-wasm": "../nym-client-wasm/scripts/build.sh",
"prebuild": "yarn build:dependencies",
"build": "tsc",
"postbuild": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm && yarn copy:readme",
"postbuild": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm",
"build:only-this": "tsc",
"postbuild:only-this": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm",
"copy:readme": "cp README.md dist"
"postbuild:only-this": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm"
},
"dependencies": {
"comlink": "^4.3.1"
@@ -28,10 +28,9 @@ tokio-tungstenite = "0.17.2"
# internal
client-connections = { path = "../../common/client-connections" }
completions = { path = "../../common/completions" }
network-defaults = { path = "../../common/network-defaults" }
nymsphinx = { path = "../../common/nymsphinx" }
completions = { path = "../../common/completions" }
logging = { path = "../../common/logging"}
ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
+25 -53
View File
@@ -7,7 +7,6 @@ use crate::error::NetworkRequesterError;
use crate::statistics::ServiceStatisticsCollector;
use crate::websocket;
use crate::websocket::TSWebsocketStream;
use client_connections::ClosedConnectionReceiver;
use futures::channel::mpsc;
use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt};
@@ -69,50 +68,32 @@ impl ServiceProvider {
mut websocket_writer: SplitSink<TSWebsocketStream, Message>,
mut mix_reader: mpsc::UnboundedReceiver<(Socks5Message, Recipient)>,
stats_collector: Option<ServiceStatisticsCollector>,
mut closed_connection_rx: ClosedConnectionReceiver,
) {
loop {
tokio::select! {
// TODO: wire SURBs in here once they're available
socks5_msg = mix_reader.next() => {
if let Some((msg, return_address)) = socks5_msg {
if let Some(stats_collector) = stats_collector.as_ref() {
if let Some(remote_addr) = stats_collector
.connected_services
.read()
.await
.get(&msg.conn_id())
{
stats_collector
.response_stats_data
.write()
.await
.processed(remote_addr, msg.size() as u32);
}
}
let conn_id = msg.conn_id();
// make 'request' to native-websocket client
let response_message = ClientRequest::Send {
recipient: return_address,
message: msg.into_bytes(),
with_reply_surb: false,
connection_id: conn_id,
};
let message = Message::Binary(response_message.serialize());
websocket_writer.send(message).await.unwrap();
} else {
log::error!("Exiting: channel closed!");
break;
}
},
Some(id) = closed_connection_rx.next() => {
let msg = ClientRequest::ClosedConnection(id);
let ws_msg = Message::Binary(msg.serialize());
websocket_writer.send(ws_msg).await.unwrap();
// TODO: wire SURBs in here once they're available
while let Some((msg, return_address)) = mix_reader.next().await {
if let Some(stats_collector) = stats_collector.as_ref() {
if let Some(remote_addr) = stats_collector
.connected_services
.read()
.await
.get(&msg.conn_id())
{
stats_collector
.response_stats_data
.write()
.await
.processed(remote_addr, msg.size() as u32);
}
}
// make 'request' to native-websocket client
let response_message = ClientRequest::Send {
recipient: return_address,
message: msg.into_bytes(),
with_reply_surb: false,
};
let message = Message::Binary(response_message.serialize());
websocket_writer.send(message).await.unwrap();
}
}
@@ -328,20 +309,12 @@ impl ServiceProvider {
let (mix_input_sender, mix_input_receiver) =
mpsc::unbounded::<(Socks5Message, Recipient)>();
// Used to notify tasks to shutdown. Not all tasks fully supports this (yet).
let shutdown = task::ShutdownNotifier::default();
// Channel for announcing closed (socks5) connections by the controller.
// The `mixnet_response_listener` will forward this info to the client using a
// `ClientRequest`.
let (closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
// Controller for managing all active connections.
// We provide it with a ShutdownListener since it requires it, even though for the network
// requester shutdown signalling is not yet fully implemented.
let shutdown = task::ShutdownNotifier::default();
let (mut active_connections_controller, mut controller_sender) =
Controller::new(closed_connection_tx, shutdown.subscribe());
Controller::new(shutdown.subscribe());
tokio::spawn(async move {
active_connections_controller.run().await;
});
@@ -368,7 +341,6 @@ impl ServiceProvider {
websocket_writer,
mix_input_receiver,
stats_collector_clone,
closed_connection_rx,
)
.await;
});
@@ -5,12 +5,11 @@ import { SxProps } from '@mui/system';
export interface BannerProps {
open: boolean;
onClick: () => void;
height?: number;
sx?: SxProps;
}
export const MaintenanceBanner = (props: BannerProps) => {
const { open, onClick, height, sx } = props;
const { open, onClick, sx } = props;
return (
<Box sx={{ width: '100%', ...sx }}>
@@ -18,7 +17,7 @@ export const MaintenanceBanner = (props: BannerProps) => {
<Alert
id="maintenance-banner"
action={
<IconButton aria-label="close" color="inherit" size="small" onClick={onClick}>
<IconButton aria-label="close" color="inherit" size="small" onClick={onClick} sx={{ paddingTop: 1 }}>
<CloseIcon fontSize="inherit" cursor="pointer" />
</IconButton>
}
@@ -29,19 +28,25 @@ export const MaintenanceBanner = (props: BannerProps) => {
backgroundColor: (t) => t.palette.nym.highlight,
borderRadius: 0,
color: (t) => t.palette.nym.networkExplorer.nav.text,
height: height || 'auto',
alignItems: 'flex-start',
}}
>
<Box display="flex">
<Typography variant="body1" fontWeight={700}>
NYM UPGRADE
SCHEDULED DISRUPTION
</Typography>
<Divider orientation="vertical" flexItem sx={{ mx: '16px', borderRightWidth: 2 }} />
<Typography variant="body2">
The Nym mixnet smart contract upgrade has happened!{' '}
On Tuesday 15th of November, 10AM GMT the migration to the new mixnet contract begins. This means all Nym
apps and{' '}
<Box sx={{ fontWeight: 700 }} display="inline">
Please make sure to upgrade your Nym services and apps to the latest version 1.1.0
</Box>
services will be temporarily on hold while the upgrade takes place.
</Box>{' '}
Bonding/unbonding, delegating/delegating{' '}
<Box sx={{ fontWeight: 700 }} display="inline">
will be frozen for up to 36 hours.
</Box>{' '}
You will still be able to transfer tokens between accounts, and use IBC.
</Typography>
</Box>
</Alert>