Feature/develop resync (#1844)

* Network-requester: throttle inbound connections (#1789)

* Return and handle ClientRequest::LaneQueueLenghts

* Pass lane queue lengths to inbound future

* Remove unused self reference

* Request lane queue lengths periodically for all open connections

* Add timeouts

* Rename to ConnectionCommandSender and Receiver

* Rename to client_connection_tx/rx

* Fix wasm build

* Replace bool with enum

* rust: bump required version to 1.65 in some crates that need it

* Add step to release GH actions (#1792)

* feat: add a release step to nym contracts GH action

* feat: add shrinking the size of wasm

* Possibilty to change gateway ws listener (#1779)

* add: set gatewayListener

* Update types.ts

* Update worker.ts

* Update contracts-build.yml

* real_traffic_stream: reduce frequency of status print (#1794)

* Update wallet and connect lock files (#1793)

* client-core: add warning when delay multiplier is larger than 1

* Fix decrypting stored received msg (#1786)

* Fix decrypting stored received msg

* rustfmt

* Moving binary message recovery to separate function

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Feature/use expect instead of panicking (#1797)

* Implementation of 'Debug' on 'RealMessage'

* expect with failed channel name instead of throwing empty panics

* Introduced Debug trait constraint in ProxyRunner

* Derive Debug for socks5_requests::Message

* Make connection_id optional in ClientRequest::Send (#1798)

* changelog: add missing entry for fixing message decrypt in gateway-client

* websocket-requests: fix length check before deserialize (#1799)

* Fix export dkg contract addr (#1800)

* Export dkg contract for mainnet when no config file present

* Remove redundant env files

* nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client

* Feature/gateway client protocol version (#1795)

* Introducing concept of gateway protocol version

* Remove version-based gateway filtering

* Fixed the unit test

* grammar

* Set build on latest release on schedule event

* Added nightly build workflow on second latest release

* socks5: if any task panics, signal all other tasks to shutdown (#1805)

* socks5: signal shutdown on error

* Mark as success

* Tidy

* Reduce wait to 5 sec

* Replace unwrap with expect

* Two more unwraps

* Update changelog

* client-core: less frequent status logging (#1806)

* Feature/nym connect UI updates (#1784)

* create custom titlebar

* create help page

* create generic modal component

* create separate connection time component

* link to shipyard docs

* move timer to separate component and update connection status component usage

* use separate component for copying ip and port details

* only show infomodal once after connection

* set service provider on tauri side

* Emit events when stopped

* listen and unlisten for tauri events

* connect: add trace log to get_services

* Add back CI notifications

* Update README

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>

* Use default serde value for upgrade (#1807)

* fix ui overflow bug (#1808)

* update nym connect error text (#1809)

* set flag to false

* Fix wait_for_signal_and_error on win (#1811)

* Add socks5-client changes to nym-connect changelog

* Fix links in nym-connect changelog

* More entries in nym-connect CHANGELOG

* Fix typo in changelog

* Update CHANGELOG.md

* Experiment/client refactoring (#1814)

* experimenting with extracting more common client code

* drying up the wasm client

* allowing some dead code for the time being

* fixed formatting in nym-connect

* made socks5 client inside nym-connect immutable

* made clippy a bit happier

* hidden away target locking for recv timeout

* New transactions for increasing amount of pledged tokens

* unit tests

* Added an option to pledge extra tokens through the vesting contract

* Introduced wallet endpoints for new operations

* Using updated pledge cap in the vesting contract

* Bumping version numbers

* Changelog for v1.1.1

* Bumping final version numbers for 1.1.1

* Bumping nym-cli version, missed it last time

* socks5-client: SOCKS4a support (#1822)

* socks5-client: SOCKS4a support

* Tidy

* Fix a few errors in socks5 client and network-requester (#1823)

* Fix two unwraps in socks5 and network-requester

* Make sure client task never sends shutdown signal

* Fix panic on getting socks version

* wip

* connecting to the back and making the requests work

* display details modal

* logs removal

* Feature/pledge more (#1679)

* New transactions for increasing amount of pledged tokens

* unit tests

* Added an option to pledge extra tokens through the vesting contract

* Introduced wallet endpoints for new operations

* Using updated pledge cap in the vesting contract

* Changelog update

* nym-connect: update lock file

* avoid mix tokens pools

* amount error

* envs/mainnet: update to latest mixnet contract and nymd validator url

* validator-api: add missing shortform for --config-env-file (#1830)

* gateway-client: handle shutdown listener (#1829)

* WIP

* WIP: try another approach

* WIP

* Reworked

* Tidy

* fix

* validator-api: remove storage dependency in contract cache (#1685)

* validator-api: remove storage dependency in contract cache

* validator-client: update detailed routes

* contract_cache: forward to new endpoints for compat

* Move reward_estimate

* client: add --no-cover and update --fastmode (#1831)

* adding a oversaturaded bonding more modal

* common/task: extract out spawn_with_report_error (#1837)

* stop panic on failed buffer request

* Compilable wasm client

* Enabled hard error on lack of gateway-client protocol version

* Missing generic parameter for ClientCoreError in BackendError

* Removed unused imports

* Additional wasm feature locking

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: Fran Arbanas <arbanasfran@gmail.com>
Co-authored-by: cgi-bin/ <6095048+sven-hash@users.noreply.github.com>
Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>
Co-authored-by: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Raphaël Walther <raphael@nymtech.net>
Co-authored-by: Fouad <fmtabbara@hotmail.co.uk>
Co-authored-by: Gala <calero.vg@gmail.com>
Co-authored-by: Dave Hrycyszyn <futurechimp@users.noreply.github.com>
This commit is contained in:
Jędrzej Stuczyński
2022-12-05 18:27:44 +00:00
committed by GitHub
parent 758ba5593c
commit 4578cda0ae
196 changed files with 4649 additions and 2496 deletions
+4 -4
View File
@@ -9,12 +9,12 @@ jobs:
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_release_matrix.json
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_release_matrix.json'
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-latest
@@ -174,7 +174,7 @@ jobs:
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: build
needs: [build,get_release]
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
@@ -192,7 +192,7 @@ jobs:
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
GIT_BRANCH: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
@@ -0,0 +1,203 @@
name: Nightly builds on second latest release
on:
schedule:
- cron: '24 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-latest
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features -- --ignored
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-latest' }}
with:
command: clean
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets --features=coconut -- -D warnings
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: [build,get_release]
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly-release"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -1,50 +0,0 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
}
]
+16 -16
View File
@@ -41,19 +41,19 @@ jobs:
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
# - name: Keybase - Send Notification
# env:
# NYM_NOTIFICATION_KIND: nym-connect
# NYM_PROJECT_NAME: "nym-connect"
# NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
# NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}"
# 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-nym-connect"
# IS_SUCCESS: "${{ job.status == 'success' }}"
# uses: docker://keybaseio/client:stable-node
# with:
# args: .github/workflows/support-files/notifications/entry_point.sh
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-connect
NYM_PROJECT_NAME: "nym-connect"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}"
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-nym-connect"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+14
View File
@@ -19,6 +19,13 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added
- socks5-client/network-requester: add support for socks4a protocol
## [v1.1.1](https://github.com/nymtech/nym/tree/v1.1.1) (2022-11-29)
### Added
- binaries: add `-c` shortform for `--config-env-file`
- websocket-requests: add server response signalling current packet queue length in the client
- contracts: DKG contract that handles coconut key generation ([#1678][#1708][#1747])
@@ -30,17 +37,24 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- clients,validator-api: take coconut signers from the chain instead of specifying them via CLI ([#1747])
- multisig contract: add DKG contract to the list of addresses that can create proposals ([#1747])
- socks5-client: wait closing inbound connection until data is sent, and throttle incoming data in general ([#1783])
- nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client
### Fixed
- gateway-client: fix decrypting stored messages on reconnect ([#1786])
### Fixed
- gateway-client: fix decrypting stored messages on reconnect ([#1786])
- socks5-client: fix shutting down all tasks if anyone of them panics or errors out ([#1805])
[#1678]: https://github.com/nymtech/nym/pull/1678
[#1708]: https://github.com/nymtech/nym/pull/1708
[#1720]: https://github.com/nymtech/nym/pull/1720
[#1747]: https://github.com/nymtech/nym/pull/1747
[#1783]: https://github.com/nymtech/nym/pull/1783
[#1786]: https://github.com/nymtech/nym/pull/1786
[#1805]: https://github.com/nymtech/nym/pull/1805
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
Generated
+16 -9
View File
@@ -586,7 +586,7 @@ dependencies = [
[[package]]
name = "client-core"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"async-trait",
"client-connections",
@@ -612,6 +612,7 @@ dependencies = [
"thiserror",
"time 0.3.14",
"tokio",
"tokio-stream",
"topology",
"url",
"validator-client",
@@ -1649,7 +1650,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"chrono",
"clap 3.2.8",
@@ -3139,7 +3140,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"anyhow",
"base64",
@@ -3156,6 +3157,7 @@ dependencies = [
"pretty_env_logger",
"serde",
"serde_json",
"tap",
"tokio",
"validator-client",
]
@@ -3181,6 +3183,7 @@ dependencies = [
"rand 0.6.5",
"serde",
"serde_json",
"tap",
"thiserror",
"time 0.3.14",
"toml",
@@ -3191,7 +3194,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"clap 3.2.8",
"client-connections",
@@ -3231,7 +3234,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3278,7 +3281,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"anyhow",
"bs58",
@@ -3320,7 +3323,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"async-trait",
"clap 3.2.8",
@@ -3368,7 +3371,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"clap 3.2.8",
"client-connections",
@@ -3435,7 +3438,7 @@ dependencies = [
[[package]]
name = "nym-validator-api"
version = "1.1.0"
version = "1.1.1"
dependencies = [
"anyhow",
"async-trait",
@@ -5714,8 +5717,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
name = "task"
version = "0.1.0"
dependencies = [
"futures",
"log",
"thiserror",
"tokio",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
+5 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.1.0"
version = "1.1.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
@@ -34,7 +34,11 @@ nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
task = { path = "../../common/task" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
version = "0.1.9"
features = ["time"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
@@ -50,9 +54,6 @@ rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
version = "0.2.4"
features = ["futures"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
path = "../../common/task"
[dev-dependencies]
tempfile = "3.1.0"
@@ -0,0 +1,481 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::KeyManager;
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
use crate::client::received_buffer::{
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
};
use crate::client::replies::reply_controller;
use crate::client::replies::reply_controller::{ReplyControllerReceiver, ReplyControllerSender};
use crate::client::replies::reply_storage::{
CombinedReplyStorage, PersistentReplyStorage, ReplyStorageBackend, SentReplyKeys,
};
use crate::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
use crate::error::ClientCoreError;
use crate::spawn_future;
use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use crypto::asymmetric::{encryption, identity};
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::info;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use std::sync::Arc;
use std::time::Duration;
use task::{ShutdownListener, ShutdownNotifier};
use url::Url;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub mod non_wasm_helpers;
pub struct ClientInput {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub connection_command_sender: ConnectionCommandSender,
pub input_sender: InputMessageSender,
}
pub struct ClientOutput {
pub received_buffer_request_sender: ReceivedBufferRequestSender,
}
pub enum ClientInputStatus {
AwaitingProducer { client_input: ClientInput },
Connected,
}
impl ClientInputStatus {
pub fn register_producer(&mut self) -> ClientInput {
match std::mem::replace(self, ClientInputStatus::Connected) {
ClientInputStatus::AwaitingProducer { client_input } => client_input,
ClientInputStatus::Connected => panic!("producer was already registered before"),
}
}
}
pub enum ClientOutputStatus {
AwaitingConsumer { client_output: ClientOutput },
Connected,
}
impl ClientOutputStatus {
pub fn register_consumer(&mut self) -> ClientOutput {
match std::mem::replace(self, ClientOutputStatus::Connected) {
ClientOutputStatus::AwaitingConsumer { client_output } => client_output,
ClientOutputStatus::Connected => panic!("consumer was already registered before"),
}
}
}
pub struct BaseClientBuilder<'a, B> {
// due to wasm limitations I had to split it like this : (
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
disabled_credentials: bool,
validator_api_endpoints: Vec<Url>,
reply_storage_backend: B,
bandwidth_controller: Option<BandwidthController>,
key_manager: KeyManager,
}
impl<'a, B> BaseClientBuilder<'a, B>
where
B: ReplyStorageBackend + Send + Sync + 'static,
{
pub fn new_from_base_config<T>(
base_config: &'a Config<T>,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
reply_storage_backend: B,
) -> BaseClientBuilder<'a, B> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: base_config.get_debug_config(),
disabled_credentials: base_config.get_disabled_credentials_mode(),
validator_api_endpoints: base_config.get_validator_api_endpoints(),
bandwidth_controller,
reply_storage_backend,
key_manager,
}
}
pub fn new(
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
reply_storage_backend: B,
disabled_credentials: bool,
validator_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, B> {
BaseClientBuilder {
gateway_config,
debug_config,
disabled_credentials,
validator_api_endpoints,
bandwidth_controller,
reply_storage_backend,
key_manager,
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
debug_config: &DebugConfig,
ack_key: Arc<AckKey>,
self_address: Recipient,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
ack_key,
debug_config.average_ack_delay,
debug_config.average_packet_delay,
debug_config.loop_cover_traffic_average_delay,
mix_tx,
self_address,
topology_accessor,
);
if let Some(size) = debug_config.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
controller_config: real_messages_control::Config,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
reply_storage: CombinedReplyStorage,
reply_controller_sender: ReplyControllerSender,
reply_controller_receiver: ReplyControllerReceiver,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: ShutdownListener,
) {
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_storage,
reply_controller_sender,
reply_controller_receiver,
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
local_encryption_keypair: Arc<encryption::KeyPair>,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
shutdown: ShutdownListener,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
local_encryption_keypair,
query_receiver,
mixnet_receiver,
reply_key_storage,
reply_controller_sender,
)
.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: ShutdownListener,
) -> GatewayClient {
let gateway_id = self.gateway_config.gateway_id.clone();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.gateway_config.gateway_owner.clone();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
let shared_key = if self.key_manager.gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
None
};
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
shared_key,
mixnet_message_sender,
ack_sender,
self.debug_config.gateway_response_timeout,
self.bandwidth_controller.take(),
shutdown,
);
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
validator_api_urls: Vec<Url>,
refresh_rate: Duration,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), ClientCoreError<B>> {
let topology_refresher_config = TopologyRefresherConfig::new(
validator_api_urls,
refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology);
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
async fn setup_persistent_reply_storage(
backend: B,
shutdown: ShutdownListener,
) -> Result<CombinedReplyStorage, ClientCoreError<B>> {
let persistent_storage = PersistentReplyStorage::new(backend);
let mem_store = persistent_storage
.load_state_from_backend()
.await
.map_err(|err| ClientCoreError::SurbStorageError { source: err })?;
let store_clone = mem_store.clone();
spawn_future(async move {
persistent_storage
.flush_on_shutdown(store_clone, shutdown)
.await
});
Ok(mem_store)
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError<B>> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
// channels responsible for dealing with reply-related fun
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::new_control_channels();
let self_address = self.as_mix_recipient();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
let reply_storage =
Self::setup_persistent_reply_storage(self.reply_storage_backend, shutdown.subscribe())
.await?;
Self::start_topology_refresher(
self.validator_api_endpoints.clone(),
self.debug_config.topology_refresh_rate,
shared_topology_accessor.clone(),
shutdown.subscribe(),
)
.await?;
Self::start_received_messages_buffer_controller(
self.key_manager.encryption_keypair(),
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
reply_controller_sender.clone(),
shutdown.subscribe(),
);
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
// controller that connections are closed.
let (client_connection_tx, client_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections (e.g socks5 for attached network-requesters)
let shared_lane_queue_lengths = LaneQueueLengths::new();
let mut controller_config = real_messages_control::Config::new(
self.debug_config,
self.key_manager.ack_key(),
self_address,
);
if let Some(size) = self.debug_config.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
Self::start_real_traffic_controller(
controller_config,
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
reply_storage,
reply_controller_sender,
reply_controller_receiver,
shared_lane_queue_lengths.clone(),
client_connection_rx,
shutdown.subscribe(),
);
if !self.debug_config.disable_loop_cover_traffic_stream {
Self::start_cover_traffic_stream(
self.debug_config,
self.key_manager.ack_key(),
self_address,
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
info!("Client startup finished!");
info!("The address of this client is: {self_address}");
Ok(BaseClient {
client_input: ClientInputStatus::AwaitingProducer {
client_input: ClientInput {
shared_lane_queue_lengths,
connection_command_sender: client_connection_tx,
input_sender,
},
},
client_output: ClientOutputStatus::AwaitingConsumer {
client_output: ClientOutput {
received_buffer_request_sender,
},
},
shutdown_notifier: shutdown,
})
}
}
pub struct BaseClient {
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
pub shutdown_notifier: ShutdownNotifier,
}
@@ -0,0 +1,51 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_storage::{
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
};
use crate::config::DebugConfig;
use crate::error::ClientCoreError;
use log::{error, info};
use std::path::Path;
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
db_path: P,
debug_config: &DebugConfig,
) -> Result<fs_backend::Backend, ClientCoreError<fs_backend::Backend>> {
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load the existing one
let db_path = db_path.as_ref();
if db_path.exists() {
info!("loading existing surb database");
match fs_backend::Backend::try_load(db_path).await {
Ok(backend) => Ok(backend),
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}");
Err(ClientCoreError::SurbStorageError { source: err })
}
}
} else {
info!("creating fresh surb database");
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
Ok(backend) => backend,
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}");
return Err(ClientCoreError::SurbStorageError { source: err });
}
};
// while I kinda hate that we're going to be creating `CombinedReplyStorage` twice,
// it will only be happening on the very first run and in practice won't incur huge
// costs since the storage is going to be empty
let mem_store = CombinedReplyStorage::new(
debug_config.minimum_reply_surb_storage_threshold,
debug_config.maximum_reply_surb_storage_threshold,
);
storage_backend
.init_fresh(&mem_store)
.await
.map_err(|err| ClientCoreError::SurbStorageError { source: err })?;
Ok(storage_backend)
}
}
@@ -143,6 +143,16 @@ impl LoopCoverTrafficStream<OsRng> {
self.packet_size = packet_size;
}
fn set_next_delay(&mut self, amount: Duration) {
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(amount));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(amount));
self.next_delay = next_delay;
}
async fn on_new_message(&mut self) {
trace!("next cover message!");
@@ -203,12 +213,11 @@ impl LoopCoverTrafficStream<OsRng> {
tokio::task::yield_now().await;
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
// we should set initial delay only when we actually start the stream
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.next_delay = Box::pin(time::sleep(sampled));
self.set_next_delay(sampled);
spawn_future(async move {
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
@@ -229,17 +238,16 @@ impl LoopCoverTrafficStream<OsRng> {
}
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("LoopCoverTrafficStream: Exiting");
})
}
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
// we should set initial delay only when we actually start the stream
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.next_delay = Box::pin(wasm_timer::Delay::new(sampled));
self.set_next_delay(sampled);
spawn_future(async move {
debug!("Started LoopCoverTrafficStream without graceful shutdown support");
@@ -149,6 +149,10 @@ impl KeyManager {
)
}
pub fn gateway_key_set(&self) -> bool {
self.gateway_shared_key.is_some()
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
@@ -77,7 +77,6 @@ impl MixTrafficController {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
spawn_future(async move {
debug!("Started MixTrafficController with graceful shutdown support");
@@ -98,12 +97,11 @@ impl MixTrafficController {
}
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("MixTrafficController: Exiting");
})
}
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
spawn_future(async move {
debug!("Started MixTrafficController without graceful shutdown support");
+3 -8
View File
@@ -1,5 +1,7 @@
use std::sync::atomic::AtomicBool;
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod base_client;
pub mod cover_traffic_stream;
pub mod inbound_messages;
pub mod key_manager;
@@ -8,10 +10,3 @@ pub mod real_messages_control;
pub mod received_buffer;
pub mod replies;
pub mod topology_control;
// This is *NOT* used to signal shutdown.
// It's critical that we don't have any tasks finishing early, this is an additional safety check
// that tasks exiting are doing so because shutdown has been signalled, and no other reason.
// In particular for tasks that rely on their associated channel being closed to signal shutdown,
// and don't have access to a shutdown listener channel.
pub static SHUTDOWN_HAS_BEEN_SIGNALLED: AtomicBool = AtomicBool::new(false);
@@ -71,7 +71,6 @@ impl AcknowledgementListener {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started AcknowledgementListener with graceful shutdown support");
@@ -89,11 +88,12 @@ impl AcknowledgementListener {
}
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("AcknowledgementListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started AcknowledgementListener without graceful shutdown support");
@@ -242,7 +242,6 @@ impl ActionController {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started ActionController with graceful shutdown support");
@@ -269,11 +268,15 @@ impl ActionController {
}
}
}
assert!(shutdown.is_shutdown_poll());
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("ActionController: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started ActionController without graceful shutdown support");
@@ -109,7 +109,6 @@ where
};
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started InputMessageListener with graceful shutdown support");
@@ -129,11 +128,12 @@ where
}
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("InputMessageListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started InputMessageListener without graceful shutdown support");
while let Some(input_msg) = self.input_receiver.recv().await {
@@ -252,7 +252,6 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
@@ -298,7 +297,8 @@ where
});
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) fn start(self) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
@@ -230,7 +230,6 @@ where
.await
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started RetransmissionRequestListener with graceful shutdown support");
@@ -248,11 +247,12 @@ where
}
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("RetransmissionRequestListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started RetransmissionRequestListener without graceful shutdown support");
@@ -43,7 +43,6 @@ impl SentNotificationListener {
.unwrap();
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started SentNotificationListener with graceful shutdown support");
@@ -67,7 +66,8 @@ impl SentNotificationListener {
log::debug!("SentNotificationListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started SentNotificationListener without graceful shutdown support");
@@ -35,8 +35,6 @@ use std::time::Duration;
use crate::config;
pub(crate) use acknowledgement_control::{AckActionSender, Action};
// #[cfg(feature = "reply-surb")]
// use crate::client::reply_key_storage::ReplyKeyStorage;
pub(crate) mod acknowledgement_control;
pub(crate) mod message_handler;
@@ -91,7 +89,7 @@ pub struct Config {
impl Config {
pub fn new(
base_client_debug_config: &config::Debug,
base_client_debug_config: &config::DebugConfig,
ack_key: Arc<AckKey>,
self_recipient: Recipient,
) -> Self {
@@ -239,7 +237,6 @@ impl RealMessagesController<OsRng> {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
@@ -493,49 +493,73 @@ where
} else {
log::debug!("{status_str}");
}
}
#[cfg(not(target_arch = "wasm32"))]
fn log_status_infrequent(&self) {
if self.sending_delay_controller.current_multiplier() > 1 {
log::warn!(
"Unable to send packets fast enough - sending delay multiplier set to: {}",
self.sending_delay_controller.current_multiplier()
)
};
if packets > 1000 {
log::warn!("{status_str}");
} else if packets > 0 {
log::info!("{status_str}");
} else {
log::debug!("{status_str}");
);
}
}
#[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));
#[cfg(not(target_arch = "wasm32"))]
{
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
let mut infrequent_status_timer = tokio::time::interval(Duration::from_secs(60));
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
_ = status_timer.tick() => {
self.log_status();
}
_ = infrequent_status_timer.tick() => {
self.log_status_infrequent();
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
_ = status_timer.tick() => {
self.log_status();
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
}
#[cfg(target_arch = "wasm32")]
{
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
}
}
assert!(shutdown.is_shutdown_poll());
log::debug!("OutQueueControl: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started OutQueueControl without graceful shutdown support");
@@ -82,7 +82,7 @@ impl SendingDelayController {
self.current_multiplier =
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
log::warn!(
"Increasing sending delay multiplier to: {}",
self.current_multiplier
);
@@ -405,7 +405,6 @@ impl RequestReceiver {
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started RequestReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
@@ -425,11 +424,12 @@ impl RequestReceiver {
},
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("RequestReceiver: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
async fn run(&mut self) {
debug!("Started RequestReceiver without graceful shutdown support");
@@ -455,7 +455,6 @@ impl FragmentedMessageReceiver {
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
@@ -474,11 +473,12 @@ impl FragmentedMessageReceiver {
}
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("FragmentedMessageReceiver: Exiting");
}
#[cfg(target_arch = "wasm32")]
// todo: think whether this is still required
#[allow(dead_code)]
async fn run(&mut self) {
debug!("Started FragmentedMessageReceiver without graceful shutdown support");
@@ -516,7 +516,6 @@ impl ReceivedMessagesBufferController {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut fragmented_message_receiver = self.fragmented_message_receiver;
let mut request_receiver = self.request_receiver;
@@ -478,12 +478,18 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started ReplyController with graceful shutdown support");
let polling_rate = Duration::from_secs(5);
let mut interval_timer = tokio::time::interval(polling_rate);
#[cfg(not(target_arch = "wasm32"))]
let mut interval =
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
@@ -498,7 +504,7 @@ where
break;
}
},
_ = interval_timer.tick() => {
_ = interval.next() => {
self.inspect_stale_entries().await
},
}
@@ -11,6 +11,17 @@ pub struct Backend {
empty: Empty,
}
impl Backend {
pub fn new(min_surb_threshold: usize, max_surb_threshold: usize) -> Self {
Backend {
empty: Empty {
min_surb_threshold,
max_surb_threshold,
},
}
}
}
#[async_trait]
impl ReplyStorageBackend for Backend {
type StorageError = <Empty as ReplyStorageBackend>::StorageError;
@@ -9,7 +9,7 @@ use log::{error, info};
use sqlx::ConnectOptions;
use std::path::Path;
#[derive(Clone)]
#[derive(Debug, Clone)]
pub(crate) struct StorageManager {
pub(crate) connection_pool: sqlx::SqlitePool,
}
@@ -22,6 +22,7 @@ mod error;
mod manager;
mod models;
#[derive(Debug)]
pub struct Backend {
temporary_old_path: Option<PathBuf>,
database_path: PathBuf,
@@ -53,7 +53,7 @@ impl ReplyStorageBackend for Empty {
#[async_trait]
pub trait ReplyStorageBackend: Sized {
type StorageError: Error;
type StorageError: Error + 'static;
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
Ok(())
@@ -6,7 +6,7 @@ use nymsphinx::anonymous_replies::encryption_key::EncryptionKeyDigest;
use nymsphinx::anonymous_replies::SurbEncryptionKey;
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use dashmap::iter::Iter;
#[derive(Debug, Clone)]
@@ -30,7 +30,7 @@ impl SentReplyKeys {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn from_raw(raw: Vec<(EncryptionKeyDigest, SurbEncryptionKey)>) -> SentReplyKeys {
SentReplyKeys {
inner: Arc::new(SentReplyKeysInner {
@@ -39,7 +39,7 @@ impl SentReplyKeys {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn as_raw_iter(&self) -> Iter<'_, EncryptionKeyDigest, SurbEncryptionKey> {
self.inner.data.iter()
}
@@ -34,7 +34,6 @@ where
}
// this will have to get enabled after merging develop
#[cfg(not(target_arch = "wasm32"))]
pub async fn flush_on_shutdown(
mut self,
mem_state: CombinedReplyStorage,
@@ -12,7 +12,7 @@ use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use dashmap::iter::Iter;
#[cfg(target_arch = "wasm32")]
@@ -48,7 +48,7 @@ impl ReceivedReplySurbsMap {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn from_raw(
min_surb_threshold: usize,
max_surb_threshold: usize,
@@ -63,7 +63,7 @@ impl ReceivedReplySurbsMap {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn as_raw_iter(&self) -> Iter<'_, AnonymousSenderTag, ReceivedReplySurbs> {
self.inner.data.iter()
}
@@ -211,7 +211,7 @@ impl ReceivedReplySurbs {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn new_retrieved(
surbs: Vec<ReplySurb>,
surbs_last_received_at: Instant,
@@ -223,7 +223,7 @@ impl ReceivedReplySurbs {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn surbs_ref(&self) -> &VecDeque<ReplySurb> {
&self.data
}
@@ -6,7 +6,7 @@ use nymsphinx::addressing::clients::{Recipient, RecipientBytes};
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use dashmap::iter::Iter;
#[derive(Debug, Clone)]
@@ -28,7 +28,7 @@ impl UsedSenderTags {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn from_raw(raw: Vec<(RecipientBytes, AnonymousSenderTag)>) -> UsedSenderTags {
UsedSenderTags {
inner: Arc::new(UsedSenderTagsInner {
@@ -37,7 +37,7 @@ impl UsedSenderTags {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub(crate) fn as_raw_iter(&self) -> Iter<'_, RecipientBytes, AnonymousSenderTag> {
self.inner.data.iter()
}
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
use futures::StreamExt;
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
@@ -9,7 +10,6 @@ use rand::seq::SliceRandom;
use rand::thread_rng;
use std::ops::Deref;
use std::sync::Arc;
use std::time;
use std::time::Duration;
use thiserror::Error;
use tokio::sync::{RwLock, RwLockReadGuard};
@@ -176,14 +176,14 @@ impl Default for TopologyAccessor {
pub struct TopologyRefresherConfig {
validator_api_urls: Vec<Url>,
refresh_rate: time::Duration,
refresh_rate: Duration,
client_version: String,
}
impl TopologyRefresherConfig {
pub fn new(
validator_api_urls: Vec<Url>,
refresh_rate: time::Duration,
refresh_rate: Duration,
client_version: String,
) -> Self {
TopologyRefresherConfig {
@@ -353,14 +353,22 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = tokio::time::sleep(self.refresh_rate) => {
_ = interval.next() => {
self.refresh().await;
},
_ = shutdown.recv() => {
@@ -368,19 +376,23 @@ impl TopologyRefresher {
},
}
}
assert!(shutdown.is_shutdown_poll());
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
use futures::StreamExt;
spawn_future(async move {
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while let Some(_) = interval.next().await {
while (interval.next().await).is_some() {
self.refresh().await;
}
})
+42 -24
View File
@@ -51,7 +51,7 @@ pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config<T> {
client: Client<T>,
@@ -59,15 +59,21 @@ pub struct Config<T> {
#[serde(default)]
logging: Logging,
#[serde(default)]
debug: Debug,
debug: DebugConfig,
}
impl<T: NymConfig> Config<T> {
pub fn new<S: Into<String>>(id: S) -> Self {
impl<T> Config<T> {
pub fn new<S: Into<String>>(id: S) -> Self
where
T: NymConfig,
{
Config::default().with_id(id)
}
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self {
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self
where
T: NymConfig,
{
let id = id.into();
// identity key setting
@@ -128,7 +134,7 @@ impl<T: NymConfig> Config<T> {
self.client.disabled_credentials_mode = disabled_credentials_mode;
}
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpoint) {
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
self.client.gateway_endpoint = gateway_endpoint;
}
@@ -146,8 +152,15 @@ impl<T: NymConfig> Config<T> {
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.average_packet_delay = Duration::from_millis(10);
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2_000_000); // basically don't really send cover messages
self.debug.message_sending_average_delay = Duration::from_millis(4); // 250 "real" messages / s
// basically don't really send cover messages
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2_000_000);
// 250 "real" messages / s
self.debug.message_sending_average_delay = Duration::from_millis(4);
}
pub fn set_no_cover_traffic(&mut self) {
self.debug.disable_loop_cover_traffic_stream = true;
self.debug.disable_main_poisson_packet_distribution = true;
}
pub fn set_custom_version(&mut self, version: &str) {
@@ -158,10 +171,6 @@ impl<T: NymConfig> Config<T> {
self.client.id.clone()
}
pub fn get_debug_config(&self) -> &Debug {
&self.debug
}
pub fn get_disabled_credentials_mode(&self) -> bool {
self.client.disabled_credentials_mode
}
@@ -214,7 +223,11 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_listener.clone()
}
pub fn get_gateway_endpoint(&self) -> &GatewayEndpoint {
pub fn get_gateway_endpoint_config(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
pub fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
@@ -231,6 +244,10 @@ impl<T: NymConfig> Config<T> {
}
// Debug getters
pub fn get_debug_config(&self) -> &DebugConfig {
&self.debug
}
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
}
@@ -320,7 +337,7 @@ impl<T: NymConfig> Default for Config<T> {
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
pub struct GatewayEndpoint {
pub struct GatewayEndpointConfig {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
@@ -332,10 +349,10 @@ pub struct GatewayEndpoint {
pub gateway_listener: String,
}
impl From<topology::gateway::Node> for GatewayEndpoint {
fn from(node: topology::gateway::Node) -> GatewayEndpoint {
impl From<topology::gateway::Node> for GatewayEndpointConfig {
fn from(node: topology::gateway::Node) -> GatewayEndpointConfig {
let gateway_listener = node.clients_address();
GatewayEndpoint {
GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_owner: node.owner,
gateway_listener,
@@ -343,7 +360,7 @@ impl From<topology::gateway::Node> for GatewayEndpoint {
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct Client<T> {
/// Version of the client for which this configuration was created.
#[serde(default = "missing_string_value")]
@@ -358,6 +375,7 @@ pub struct Client<T> {
disabled_credentials_mode: bool,
/// Addresses to nymd validators via which the client can communicate with the chain.
#[serde(default)]
validator_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
@@ -384,7 +402,7 @@ pub struct Client<T> {
ack_key_file: PathBuf,
/// Information regarding how the client should send data to gateway.
gateway_endpoint: GatewayEndpoint,
gateway_endpoint: GatewayEndpointConfig,
/// Path to the database containing bandwidth credentials of this client.
database_path: PathBuf,
@@ -458,13 +476,13 @@ impl<T: NymConfig> Client<T> {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Logging {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Debug {
pub struct DebugConfig {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
@@ -562,9 +580,9 @@ pub enum ExtendedPacketSize {
Extended32,
}
impl Default for Debug {
impl Default for DebugConfig {
fn default() -> Self {
Debug {
DebugConfig {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
+8 -1
View File
@@ -1,12 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_storage::ReplyStorageBackend;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum ClientCoreError {
pub enum ClientCoreError<B: ReplyStorageBackend> {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
@@ -26,4 +27,10 @@ pub enum ClientCoreError {
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("The current network topology seem to be insufficient to route any packets through")]
InsufficientNetworkTopology,
#[error("Unexpected exit")]
UnexpectedExit,
#[error("experienced a failure with our reply surb persistent storage: {source}")]
SurbStorageError { source: B::StorageError },
}
+26 -13
View File
@@ -18,16 +18,20 @@ use tap::TapFallible;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::client::replies::reply_storage::ReplyStorageBackend;
use crate::{
client::key_manager::KeyManager,
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError,
};
pub async fn query_gateway_details(
pub async fn query_gateway_details<B>(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<&str>,
) -> Result<gateway::Node, ClientCoreError> {
) -> Result<gateway::Node, ClientCoreError<B>>
where
B: ReplyStorageBackend,
{
let validator_api = validator_servers
.choose(&mut thread_rng())
.ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?;
@@ -59,12 +63,13 @@ pub async fn query_gateway_details(
}
}
pub async fn register_with_gateway_and_store_keys<T>(
pub async fn register_with_gateway_and_store_keys<T, B>(
gateway_details: gateway::Node,
config: &Config<T>,
) -> Result<(), ClientCoreError>
) -> Result<(), ClientCoreError<B>>
where
T: NymConfig,
B: ReplyStorageBackend,
{
let mut rng = OsRng;
let mut key_manager = KeyManager::new(&mut rng);
@@ -79,10 +84,13 @@ where
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
}
async fn register_with_gateway(
async fn register_with_gateway<B>(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
) -> Result<Arc<SharedKeys>, ClientCoreError> {
) -> Result<Arc<SharedKeys>, ClientCoreError<B>>
where
B: ReplyStorageBackend,
{
let timeout = Duration::from_millis(1500);
let mut gateway_client = GatewayClient::new_init(
gateway.clients_address(),
@@ -90,8 +98,6 @@ async fn register_with_gateway(
gateway.owner.clone(),
our_identity.clone(),
timeout,
#[cfg(not(target_arch = "wasm32"))]
None,
);
gateway_client
.establish_connection()
@@ -104,13 +110,17 @@ async fn register_with_gateway(
Ok(shared_keys)
}
pub fn show_address<T>(config: &Config<T>) -> Result<(), ClientCoreError>
pub fn show_address<T, B>(config: &Config<T>) -> Result<(), ClientCoreError<B>>
where
T: config::NymConfig,
B: ReplyStorageBackend,
{
fn load_identity_keys(
fn load_identity_keys<B>(
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
) -> Result<identity::KeyPair, ClientCoreError<B>>
where
B: ReplyStorageBackend,
{
let identity_keypair: identity::KeyPair =
pemstore::load_keypair(&pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
@@ -120,9 +130,12 @@ where
Ok(identity_keypair)
}
fn load_sphinx_keys(
fn load_sphinx_keys<B>(
pathfinder: &ClientKeyPathfinder,
) -> Result<encryption::KeyPair, ClientCoreError> {
) -> Result<encryption::KeyPair, ClientCoreError<B>>
where
B: ReplyStorageBackend,
{
let sphinx_keypair: encryption::KeyPair =
pemstore::load_keypair(&pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.0"
version = "1.1.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+6 -2
View File
@@ -3,7 +3,7 @@
use crate::client::config::template::config_template;
pub use client_core::config::MISSING_VALUE;
use client_core::config::{Config as BaseConfig, Debug};
use client_core::config::{Config as BaseConfig, DebugConfig};
use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use config::NymConfig;
use serde::{Deserialize, Serialize};
@@ -27,6 +27,10 @@ impl SocketType {
_ => SocketType::None,
}
}
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
@@ -100,7 +104,7 @@ impl Config {
&mut self.base
}
pub fn get_debug_settings(&self) -> &Debug {
pub fn get_debug_settings(&self) -> &DebugConfig {
self.get_base().get_debug_config()
}
+138 -457
View File
@@ -1,208 +1,48 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::client::config::Config;
use crate::error::ClientError;
use crate::websocket;
use client_connections::{
ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
use client_connections::TransmissionLane;
use client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput,
};
use client_core::client::inbound_messages::InputMessage;
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use client_core::client::real_messages_control;
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender,
ReceivedMessagesBufferController, ReconstructedMessagesReceiver,
};
use client_core::client::replies::reply_controller;
use client_core::client::replies::reply_controller::{
ReplyControllerReceiver, ReplyControllerSender,
};
use client_core::client::replies::reply_storage::{
fs_backend, CombinedReplyStorage, PersistentReplyStorage, SentReplyKeys,
};
use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
use nymsphinx::receiver::ReconstructedMessage;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use task::{wait_for_signal, ShutdownNotifier};
pub(crate) mod config;
pub struct NymClient {
pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates,
/// key filepaths, etc.
config: Config,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
/// through the mix network.
/// It is only available if the client started with the websocket listener disabled.
input_tx: Option<InputMessageSender>,
/// Channel used for obtaining reconstructed messages received from the mix network.
/// It is only available if the client started with the websocket listener disabled.
receive_tx: Option<ReconstructedMessagesReceiver>,
}
impl NymClient {
impl SocketClient {
pub fn new(config: Config) -> Self {
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
SocketClient {
config,
key_manager,
input_tx: None,
receive_tx: None,
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_loop_cover_traffic_average_delay(),
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
reply_storage: CombinedReplyStorage,
reply_controller_sender: ReplyControllerSender,
reply_controller_receiver: ReplyControllerReceiver,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: ShutdownListener,
) {
let mut controller_config = real_messages_control::Config::new(
self.config.get_debug_settings(),
self.key_manager.ack_key(),
self.as_mix_recipient(),
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_storage,
reply_controller_sender,
reply_controller_receiver,
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
shutdown: ShutdownListener,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
reply_key_storage,
reply_controller_sender,
)
.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: ShutdownListener,
) -> GatewayClient {
let gateway_id = self.config.get_base().get_gateway_id();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
@@ -215,158 +55,144 @@ impl NymClient {
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
Some(shutdown),
);
gateway_client
.set_disabled_credentials_mode(self.config.get_base().get_disabled_credentials_mode());
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
async fn setup_persistent_reply_storage(
&self,
shutdown: ShutdownListener,
) -> Result<CombinedReplyStorage, ClientError> {
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load the existing one
let db_path = self.config.get_base().get_reply_surb_database_path();
let (persistent_storage, mem_store) = if db_path.exists() {
info!("loading existing surb database");
let storage_backend = match fs_backend::Backend::try_load(db_path).await {
Ok(backend) => backend,
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}");
return Err(err.into());
}
};
let persistent_storage = PersistentReplyStorage::new(storage_backend);
let mem_store = persistent_storage.load_state_from_backend().await?;
(persistent_storage, mem_store)
} else {
info!("creating fresh surb database");
let storage_backend = match fs_backend::Backend::init(db_path).await {
Ok(backend) => backend,
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}");
return Err(err.into());
}
};
let persistent_storage = PersistentReplyStorage::new(storage_backend);
let mem_store = CombinedReplyStorage::new(
self.config
.get_base()
.get_minimum_reply_surb_storage_threshold(),
self.config
.get_base()
.get_maximum_reply_surb_storage_threshold(),
);
(persistent_storage, mem_store)
};
let store_clone = mem_store.clone();
tokio::spawn(async move {
persistent_storage
.flush_on_shutdown(store_clone, shutdown)
.await
});
Ok(mem_store)
bandwidth_controller
}
fn start_websocket_listener(
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
shared_lane_queue_lengths: LaneQueueLengths,
client_connection_tx: ConnectionCommandSender,
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
self_address: Recipient,
) {
info!("Starting websocket listener...");
let ClientInput {
shared_lane_queue_lengths,
connection_command_sender,
input_sender,
} = client_input;
let received_buffer_request_sender = client_output.received_buffer_request_sender;
let websocket_handler = websocket::Handler::new(
msg_input,
client_connection_tx,
buffer_requester,
&self.as_mix_recipient(),
input_sender,
connection_command_sender,
received_buffer_request_sender,
self_address,
shared_lane_queue_lengths,
);
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
websocket::Listener::new(config.get_listening_port()).start(websocket_handler);
}
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
pub async fn run_socket_forever(self) -> Result<(), ClientError> {
let shutdown = self.start_socket().await?;
wait_for_signal().await;
println!(
"Received signal - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
);
log::info!("Sending shutdown");
shutdown.signal_shutdown().ok();
// Some of these components have shutdown signalling implemented as part of socks5 work,
// but since it's not fully implemented (yet) for all the components of the native client,
// we don't try to wait and instead just stop immediately.
//log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
//shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-client");
Ok(())
}
pub async fn start_socket(self) -> Result<ShutdownNotifier, ClientError> {
if !self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
non_wasm_helpers::setup_fs_reply_surb_backend(
self.config.get_base().get_reply_surb_database_path(),
self.config.get_debug_settings(),
)
.await?,
);
let self_address = base_builder.as_mix_recipient();
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
Self::start_websocket_listener(&self.config, client_input, client_output, self_address);
info!("Client startup finished!");
info!("The address of this client is: {}", self_address);
Ok(started_client.shutdown_notifier)
}
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
if self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
let base_client = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
non_wasm_helpers::setup_fs_reply_surb_backend(
self.config.get_base().get_reply_surb_database_path(),
self.config.get_debug_settings(),
)
.await?,
);
let mut started_client = base_client.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
// register our receiver
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
client_output
.received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
Ok(DirectClient {
client_input,
reconstructed_receiver,
_shutdown_notifier: started_client.shutdown_notifier,
})
}
}
pub struct DirectClient {
client_input: ClientInput,
reconstructed_receiver: ReconstructedMessagesReceiver,
// we need to keep reference to this guy otherwise things will start dropping
_shutdown_notifier: ShutdownNotifier,
}
impl DirectClient {
/// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
@@ -374,9 +200,8 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.input_tx
.as_ref()
.expect("start method was not called before!")
self.client_input
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
@@ -394,9 +219,8 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
self.input_tx
.as_ref()
.expect("start method was not called before!")
self.client_input
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
@@ -409,9 +233,8 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
self.input_tx
.as_ref()
.expect("start method was not called before!")
self.client_input
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
@@ -426,151 +249,9 @@ impl NymClient {
pub async fn wait_for_messages(&mut self) -> Vec<ReconstructedMessage> {
use futures::StreamExt;
self.receive_tx
.as_mut()
.expect("start method was not called before!")
self.reconstructed_receiver
.next()
.await
.expect("buffer controller seems to have somehow died!")
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), ClientError> {
let mut shutdown = self.start().await?;
wait_for_signal().await;
println!(
"Received signal - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
);
log::info!("Sending shutdown");
shutdown.signal_shutdown().ok();
// Some of these components have shutdown signalling implemented as part of socks5 work,
// but since it's not fully implemented (yet) for all the components of the native client,
// we don't try to wait and instead just stop immediately.
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-client");
Ok(())
}
pub async fn start(&mut self) -> Result<ShutdownNotifier, ClientError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
// channels responsible for dealing with reply-related fun
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::new_control_channels();
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
let reply_storage = self
.setup_persistent_reply_storage(shutdown.subscribe())
.await?;
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
reply_controller_sender.clone(),
shutdown.subscribe(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
// controller that connections are closed.
let (client_connection_tx, client_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections (e.g socks5 for attached network-requesters)
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
reply_storage,
reply_controller_sender,
reply_controller_receiver,
shared_lane_queue_lengths.clone(),
client_connection_rx,
shutdown.subscribe(),
);
if !self
.config
.get_base()
.get_disabled_loop_cover_traffic_stream()
{
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
match self.config.get_socket_type() {
SocketType::WebSocket => self.start_websocket_listener(
received_buffer_request_sender,
input_sender,
shared_lane_queue_lengths,
client_connection_tx,
),
SocketType::None => {
// if we did not start the socket, it means we're running (supposedly) in the native mode
// and hence we should announce 'ourselves' to the buffer
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
self.receive_tx = Some(reconstructed_receiver);
self.input_tx = Some(input_sender);
}
}
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
Ok(shutdown)
}
}
+14 -6
View File
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Args;
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
use client_core::client::replies::reply_storage::fs_backend;
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
use config::NymConfig;
use crate::{
@@ -46,6 +47,10 @@ pub(crate) struct Init {
#[clap(long, hidden = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hidden = true)]
no_cover: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
@@ -61,6 +66,7 @@ impl From<Init> for OverrideConfig {
disable_socket: init_config.disable_socket,
port: init_config.port,
fastmode: init_config.fastmode,
no_cover: init_config.no_cover,
#[cfg(feature = "coconut")]
enabled_credentials_mode: init_config.enabled_credentials_mode,
@@ -121,10 +127,12 @@ pub(crate) async fn execute(args: &Init) {
);
println!("Client configuration completed.");
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
eprintln!("Failed to show address\nError: {err}");
std::process::exit(1)
});
client_core::init::show_address::<_, fs_backend::Backend>(config.get_base()).unwrap_or_else(
|err| {
eprintln!("Failed to show address\nError: {err}");
std::process::exit(1)
},
);
}
async fn setup_gateway(
@@ -132,7 +140,7 @@ async fn setup_gateway(
register: bool,
user_chosen_gateway_id: Option<&str>,
config: &Config,
) -> Result<GatewayEndpoint, ClientCoreError> {
) -> Result<GatewayEndpointConfig, ClientCoreError<fs_backend::Backend>> {
if register {
// Get the gateway details by querying the validator-api. Either pick one at random or use
// the chosen one if it's among the available ones.
+5
View File
@@ -80,6 +80,7 @@ pub(crate) struct OverrideConfig {
disable_socket: bool,
port: Option<u16>,
fastmode: bool,
no_cover: bool,
#[cfg(feature = "coconut")]
enabled_credentials_mode: bool,
@@ -141,6 +142,10 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config.get_base_mut().set_high_default_traffic_volume();
}
if args.no_cover {
config.get_base_mut().set_no_cover_traffic();
}
config
}
+13 -3
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{
client::{config::Config, NymClient},
client::{config::Config, SocketClient},
commands::{override_config, OverrideConfig},
error::ClientError,
};
@@ -39,6 +39,15 @@ pub(crate) struct Run {
#[clap(short, long)]
port: Option<u16>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hidden = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hidden = true)]
no_cover: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
@@ -53,7 +62,8 @@ impl From<Run> for OverrideConfig {
api_validators: run_config.api_validators,
disable_socket: run_config.disable_socket,
port: run_config.port,
fastmode: false,
fastmode: run_config.fastmode,
no_cover: run_config.no_cover,
#[cfg(feature = "coconut")]
enabled_credentials_mode: run_config.enabled_credentials_mode,
}
@@ -98,5 +108,5 @@ pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
return Err(ClientError::FailedLocalVersionCheck);
}
NymClient::new(config).run_forever().await
SocketClient::new(config).run_socket_forever().await
}
+7 -19
View File
@@ -1,29 +1,17 @@
use client_core::client::replies::reply_storage::fs_backend::StorageError;
use client_core::client::replies::reply_storage::fs_backend;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
ClientCoreError(#[from] ClientCoreError<fs_backend::Backend>),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
#[error("experienced a failure with our reply surb persistent storage: {source}")]
SurbStorageError {
#[source]
#[from]
source: StorageError,
},
#[error("Attempted to start the client in invalid socket mode")]
InvalidSocketMode,
}
+8 -4
View File
@@ -62,9 +62,13 @@ impl Clone for Handler {
impl Drop for Handler {
fn drop(&mut self) {
self.buffer_requester
if self
.buffer_requester
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
.expect("the buffer request failed!")
.is_err()
{
error!("we failed to disconnect the receiver from the buffer! presumably the shutdown procedure has been initiated!")
}
}
}
@@ -73,14 +77,14 @@ impl Handler {
msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: &Recipient,
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
) -> Self {
Handler {
msg_input,
client_connection_tx,
buffer_requester,
self_full_address: *self_full_address,
self_full_address,
socket: None,
received_response_type: Default::default(),
lane_queue_lengths,
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.0"
version = "1.1.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+2 -2
View File
@@ -3,7 +3,7 @@
use crate::client::config::template::config_template;
pub use client_core::config::MISSING_VALUE;
use client_core::config::{Config as BaseConfig, Debug};
use client_core::config::{Config as BaseConfig, DebugConfig};
use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
use config::NymConfig;
use nymsphinx::addressing::clients::Recipient;
@@ -91,7 +91,7 @@ impl Config {
&mut self.base
}
pub fn get_debug_settings(&self) -> &Debug {
pub fn get_debug_settings(&self) -> &DebugConfig {
self.get_base().get_debug_config()
}
+1 -1
View File
@@ -109,7 +109,7 @@ send_anonymously = {{ socks5.send_anonymously }}
# The following options should not be modified unless you know EXACTLY what you are doing
# as if set incorrectly, they may impact your anonymity.
[socks5_debug]
# [socks5_debug]
[debug]
+87 -403
View File
@@ -8,43 +8,18 @@ use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
use client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput,
};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use client_core::client::real_messages_control;
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
};
use client_core::client::replies::reply_controller;
use client_core::client::replies::reply_controller::{
ReplyControllerReceiver, ReplyControllerSender,
};
use client_core::client::replies::reply_storage::{
fs_backend, CombinedReplyStorage, PersistentReplyStorage, SentReplyKeys,
};
use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use std::sync::atomic::Ordering;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use std::error::Error;
use task::{wait_for_signal_and_error, ShutdownListener, ShutdownNotifier};
pub mod config;
@@ -78,131 +53,7 @@ impl NymClient {
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_loop_cover_traffic_average_delay(),
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
reply_storage: CombinedReplyStorage,
reply_controller_sender: ReplyControllerSender,
reply_controller_receiver: ReplyControllerReceiver,
client_connection_rx: ConnectionCommandReceiver,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
) {
let mut controller_config = real_messages_control::Config::new(
self.config.get_debug_settings(),
self.key_manager.ack_key(),
self.as_mix_recipient(),
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_storage,
reply_controller_sender,
reply_controller_receiver,
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
shutdown: ShutdownListener,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
reply_key_storage,
reply_controller_sender,
)
.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: ShutdownListener,
) -> GatewayClient {
let gateway_id = self.config.get_base().get_gateway_id();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
@@ -215,194 +66,97 @@ impl NymClient {
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
Some(shutdown),
);
gateway_client
.set_disabled_credentials_mode(self.config.get_base().get_disabled_credentials_mode());
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), Socks5ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
async fn setup_persistent_reply_storage(
&self,
shutdown: ShutdownListener,
) -> Result<CombinedReplyStorage, Socks5ClientError> {
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load the existing one
let db_path = self.config.get_base().get_reply_surb_database_path();
let (persistent_storage, mem_store) = if db_path.exists() {
info!("loading existing surb database");
let storage_backend = match fs_backend::Backend::try_load(db_path).await {
Ok(backend) => backend,
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}");
return Err(err.into());
}
};
let persistent_storage = PersistentReplyStorage::new(storage_backend);
let mem_store = persistent_storage.load_state_from_backend().await?;
(persistent_storage, mem_store)
} else {
info!("creating fresh surb database");
let storage_backend = match fs_backend::Backend::init(db_path).await {
Ok(backend) => backend,
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}");
return Err(err.into());
}
};
let persistent_storage = PersistentReplyStorage::new(storage_backend);
let mem_store = CombinedReplyStorage::new(
self.config
.get_base()
.get_minimum_reply_surb_storage_threshold(),
self.config
.get_base()
.get_maximum_reply_surb_storage_threshold(),
);
(persistent_storage, mem_store)
};
let store_clone = mem_store.clone();
tokio::spawn(async move {
persistent_storage
.flush_on_shutdown(store_clone, shutdown)
.await
});
Ok(mem_store)
bandwidth_controller
}
fn start_socks5_listener(
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
lane_queue_lengths: LaneQueueLengths,
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
self_address: Recipient,
shutdown: ShutdownListener,
) {
info!("Starting socks5 listener...");
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
let allowed_users: Vec<User> = Vec::new();
let ClientInput {
shared_lane_queue_lengths,
connection_command_sender,
input_sender,
} = client_input;
let received_buffer_request_sender = client_output.received_buffer_request_sender;
let authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = SphinxSocksServer::new(
self.config.get_listening_port(),
config.get_listening_port(),
authenticator,
self.config.get_provider_mix_address(),
self.as_mix_recipient(),
lane_queue_lengths,
config.get_provider_mix_address(),
self_address,
shared_lane_queue_lengths,
socks::client::Config::new(
self.config.get_send_anonymously(),
self.config.get_connection_start_surbs(),
self.config.get_per_request_surbs(),
config.get_send_anonymously(),
config.get_connection_start_surbs(),
config.get_per_request_surbs(),
),
shutdown.clone(),
);
task::spawn_with_report_error(
async move {
sphinx_socks
.serve(
input_sender,
received_buffer_request_sender,
connection_command_sender,
)
.await
},
shutdown,
);
tokio::spawn(async move {
sphinx_socks
.serve(msg_input, buffer_requester, client_connection_tx)
.await
});
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
wait_for_signal().await;
pub async fn run_forever(self) -> Result<(), Box<dyn Error + Send>> {
let mut shutdown = self
.start()
.await
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
let res = wait_for_signal_and_error(&mut shutdown).await;
log::info!("Sending shutdown");
client_core::client::SHUTDOWN_HAS_BEEN_SIGNALLED.store(true, Ordering::Relaxed);
shutdown.signal_shutdown().ok();
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
res
}
// Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen(
&mut self,
self,
mut receiver: Socks5ControlMessageReceiver,
) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
tokio::select! {
) -> Result<(), Box<dyn Error + Send>> {
// Start the main task
let mut shutdown = self
.start()
.await
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
let res = tokio::select! {
biased;
message = receiver.next() => {
log::debug!("Received message: {:?}", message);
match message {
@@ -413,126 +167,56 @@ impl NymClient {
log::info!("Channel closed, stopping");
}
}
Ok(())
}
Some(msg) = shutdown.wait_for_error() => {
log::info!("Task error: {:?}", msg);
Err(msg)
}
_ = tokio::signal::ctrl_c() => {
log::info!("Received SIGINT");
Ok(())
},
}
};
log::info!("Sending shutdown");
client_core::client::SHUTDOWN_HAS_BEEN_SIGNALLED.store(true, Ordering::Relaxed);
shutdown.signal_shutdown().ok();
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
res
}
pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
// let reply_key_storage =
// ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
// .expect("Failed to load reply key storage!");
// channels responsible for dealing with reply-related fun
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::new_control_channels();
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
let reply_storage = self
.setup_persistent_reply_storage(shutdown.subscribe())
.await?;
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
reply_controller_sender.clone(),
shutdown.subscribe(),
pub async fn start(self) -> Result<ShutdownNotifier, Socks5ClientError> {
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
non_wasm_helpers::setup_fs_reply_surb_backend(
self.config.get_base().get_reply_surb_database_path(),
self.config.get_debug_settings(),
)
.await?,
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
let self_address = base_builder.as_mix_recipient();
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channel for announcing closed (socks5) connections by the controller.
// This will be forwarded to `OutQueueControl`
let (client_connection_tx, client_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
reply_storage,
reply_controller_sender,
reply_controller_receiver,
client_connection_rx,
shared_lane_queue_lengths.clone(),
shutdown.subscribe(),
);
if !self
.config
.get_base()
.get_disabled_loop_cover_traffic_stream()
{
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
self.start_socks5_listener(
received_buffer_request_sender,
input_sender,
client_connection_tx,
shared_lane_queue_lengths,
shutdown.subscribe(),
Self::start_socks5_listener(
&self.config,
client_input,
client_output,
self_address,
started_client.shutdown_notifier.subscribe(),
);
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
info!("The address of this client is: {}", self_address);
Ok(shutdown)
Ok(started_client.shutdown_notifier)
}
}
+14 -7
View File
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Args;
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
use client_core::client::replies::reply_storage::fs_backend;
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
use config::NymConfig;
use crate::{
@@ -54,6 +55,10 @@ pub(crate) struct Init {
#[clap(long, hidden = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hidden = true)]
no_cover: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
@@ -69,7 +74,7 @@ impl From<Init> for OverrideConfig {
port: init_config.port,
use_anonymous_sender_tag: init_config.use_anonymous_sender_tag,
fastmode: init_config.fastmode,
no_cover: init_config.no_cover,
#[cfg(feature = "coconut")]
enabled_credentials_mode: init_config.enabled_credentials_mode,
}
@@ -130,10 +135,12 @@ pub(crate) async fn execute(args: &Init) {
);
println!("Client configuration completed.");
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
eprintln!("Failed to show address\nError: {err}");
std::process::exit(1)
});
client_core::init::show_address::<_, fs_backend::Backend>(config.get_base()).unwrap_or_else(
|err| {
eprintln!("Failed to show address\nError: {err}");
std::process::exit(1)
},
);
}
async fn setup_gateway(
@@ -141,7 +148,7 @@ async fn setup_gateway(
register: bool,
user_chosen_gateway_id: Option<&str>,
config: &Config,
) -> Result<GatewayEndpoint, ClientCoreError> {
) -> Result<GatewayEndpointConfig, ClientCoreError<fs_backend::Backend>> {
if register {
// Get the gateway details by querying the validator-api. Either pick one at random or use
// the chosen one if it's among the available ones.
+8 -2
View File
@@ -1,8 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
@@ -83,12 +84,13 @@ pub(crate) struct OverrideConfig {
port: Option<u16>,
use_anonymous_sender_tag: bool,
fastmode: bool,
no_cover: bool,
#[cfg(feature = "coconut")]
enabled_credentials_mode: bool,
}
pub(crate) async fn execute(args: &Cli) -> Result<(), Socks5ClientError> {
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send>> {
let bin_name = "nym-socks5-client";
match &args.command {
@@ -140,6 +142,10 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config.get_base_mut().set_high_default_traffic_volume();
}
if args.no_cover {
config.get_base_mut().set_no_cover_traffic();
}
config
}
+16 -5
View File
@@ -51,6 +51,15 @@ pub(crate) struct Run {
#[clap(short, long)]
port: Option<u16>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hidden = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hidden = true)]
no_cover: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
@@ -65,8 +74,8 @@ impl From<Run> for OverrideConfig {
api_validators: run_config.api_validators,
port: run_config.port,
use_anonymous_sender_tag: run_config.use_anonymous_sender_tag,
fastmode: false,
fastmode: run_config.fastmode,
no_cover: run_config.no_cover,
#[cfg(feature = "coconut")]
enabled_credentials_mode: run_config.enabled_credentials_mode,
}
@@ -95,14 +104,16 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send>> {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
return Err(Box::new(Socks5ClientError::FailedToLoadConfig(
id.to_string(),
)));
}
};
@@ -111,7 +122,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
if !version_check(&config) {
error!("failed the local version check");
return Err(Socks5ClientError::FailedLocalVersionCheck);
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
}
NymClient::new(config).run_forever().await
+12 -17
View File
@@ -1,29 +1,24 @@
use client_core::client::replies::reply_storage::fs_backend::StorageError;
use crate::socks::types::SocksProxyError;
use client_core::client::replies::reply_storage::fs_backend;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
ClientCoreError(#[from] ClientCoreError<fs_backend::Backend>),
#[error("SOCKS proxy error")]
SocksProxyError(SocksProxyError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
#[error("experienced a failure with our reply surb persistent storage: {source}")]
SurbStorageError {
#[source]
#[from]
source: StorageError,
},
#[error("Fail to bind address")]
FailToBindAddress,
}
+3 -2
View File
@@ -1,8 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use clap::{crate_version, Parser};
use error::Socks5ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
@@ -12,7 +13,7 @@ pub mod error;
pub mod socks;
#[tokio::main]
async fn main() -> Result<(), Socks5ClientError> {
async fn main() -> Result<(), Box<dyn Error + Send>> {
setup_logging();
println!("{}", banner());
+115 -43
View File
@@ -2,8 +2,8 @@
use super::authentication::{AuthenticationMethods, Authenticator, User};
use super::request::{SocksCommand, SocksRequest};
use super::types::{ResponseCode, SocksProxyError};
use super::{RESERVED, SOCKS_VERSION};
use super::types::{ResponseCodeV4, ResponseCodeV5, SocksProxyError};
use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION};
use client_connections::{LaneQueueLengths, TransmissionLane};
use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use futures::channel::mpsc;
@@ -150,14 +150,14 @@ impl Config {
/// A client connecting to the Socks proxy server, because
/// it wants to make a Nym-protected outbound request. Typically, this is
/// something like e.g. a wallet app running on your laptop connecting to
/// SphinxSocksServer.
/// `SphinxSocksServer`.
pub(crate) struct SocksClient {
config: Config,
controller_sender: ControllerSender,
stream: StreamState,
auth_nmethods: u8,
authenticator: Authenticator,
socks_version: u8,
socks_version: Option<SocksVersion>,
input_sender: InputMessageSender,
connection_id: ConnectionId,
service_provider: Recipient,
@@ -180,31 +180,34 @@ impl Drop for SocksClient {
}
impl SocksClient {
/// Create a new SOCKClient
#[allow(clippy::too_many_arguments)]
pub fn new(
config: Config,
stream: TcpStream,
authenticator: Authenticator,
input_sender: InputMessageSender,
service_provider: Recipient,
service_provider: &Recipient,
controller_sender: ControllerSender,
self_address: Recipient,
self_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown_listener: ShutdownListener,
mut shutdown_listener: ShutdownListener,
) -> Self {
// If this task fails and exits, we don't want to send shutdown signal
shutdown_listener.mark_as_success();
let connection_id = Self::generate_random();
SocksClient {
config,
controller_sender,
connection_id,
stream: StreamState::Available(stream),
auth_nmethods: 0,
socks_version: 0,
socks_version: None,
authenticator,
input_sender,
service_provider,
self_address,
service_provider: *service_provider,
self_address: *self_address,
started_proxy: false,
lane_queue_lengths,
shutdown_listener,
@@ -216,16 +219,49 @@ impl SocksClient {
rng.next_u64()
}
pub async fn send_error(&mut self, err: SocksProxyError) -> Result<(), SocksProxyError> {
let error_text = format!("{}", err);
let Some(ref version) = self.socks_version else {
log::error!("Trying to send error without knowing the version");
return Ok(());
};
match version {
SocksVersion::V4 => {
let response = ResponseCodeV4::RequestRejected;
self.send_error_v4(response).await
}
SocksVersion::V5 => {
let response = if error_text.contains("Host") {
ResponseCodeV5::HostUnreachable
} else if error_text.contains("Network") {
ResponseCodeV5::NetworkUnreachable
} else if error_text.contains("ttl") {
ResponseCodeV5::TtlExpired
} else {
ResponseCodeV5::Failure
};
self.send_error_v5(response).await
}
}
}
// Send an error back to the client
pub async fn error(&mut self, r: ResponseCode) -> Result<(), SocksProxyError> {
self.stream.write_all(&[5, r as u8]).await?;
pub async fn send_error_v4(&mut self, r: ResponseCodeV4) -> Result<(), SocksProxyError> {
self.stream.write_all(&[SOCKS4_VERSION, r as u8]).await?;
Ok(())
}
/// Shutdown the TcpStream to the client and end the session
pub async fn send_error_v5(&mut self, r: ResponseCodeV5) -> Result<(), SocksProxyError> {
self.stream.write_all(&[SOCKS5_VERSION, r as u8]).await?;
Ok(())
}
/// Shutdown the `TcpStream` to the client and end the session
pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> {
info!("client is shutting down its TCP stream");
self.stream.shutdown().await?;
self.shutdown_listener.mark_as_success();
Ok(())
}
@@ -233,25 +269,27 @@ impl SocksClient {
/// is in use and that the client is authenticated, then runs the request.
pub async fn run(&mut self) -> Result<(), SocksProxyError> {
debug!("New connection from: {}", self.stream.peer_addr()?.ip());
let mut header = [0u8; 2];
// Read a byte from the stream and determine the version being requested
let mut header = [0u8];
self.stream.read_exact(&mut header).await?;
self.socks_version = header[0];
self.auth_nmethods = header[1];
self.socks_version = match SocksVersion::try_from(header[0]) {
Ok(version) => Some(version),
Err(_err) => {
warn!("Init: Unsupported version: SOCKS{}", header[0]);
return self.shutdown().await;
}
};
// Handle SOCKS4 requests
if header[0] != SOCKS_VERSION {
warn!("Init: Unsupported version: SOCKS{}", self.socks_version);
self.shutdown().await
}
// Valid SOCKS5
else {
// Authenticate w/ client
self.authenticate().await?;
// Handle requests
self.handle_request().await
if self.socks_version == Some(SocksVersion::V5) {
let mut auth = [0u8];
self.stream.read_exact(&mut auth).await?;
self.auth_nmethods = auth[0];
self.authenticate_socks5().await?;
}
self.handle_request().await
}
async fn send_anonymous_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
@@ -299,10 +337,15 @@ impl SocksClient {
.await;
let stream = self.stream.run_proxy();
let local_stream_remote = stream
.peer_addr()
.expect("failed to extract peer address")
.to_string();
let peer_addr = match stream.peer_addr() {
Ok(peer_addr) => peer_addr,
Err(err) => {
log::error!("Unable to extract the remote peer address: {err}");
return;
}
};
let local_stream_remote = peer_addr.to_string();
let connection_id = self.connection_id;
let input_sender = self.input_sender.clone();
let anonymous = self.config.use_surbs_for_responses;
@@ -344,8 +387,17 @@ impl SocksClient {
async fn handle_request(&mut self) -> Result<(), SocksProxyError> {
debug!("Handling CONNECT Command");
let request = SocksRequest::from_stream(&mut self.stream).await?;
let remote_address = request.to_string();
let version = self
.socks_version
.as_ref()
.expect("Must read version before parsing request");
let request = match version {
SocksVersion::V4 => SocksRequest::from_stream_socks4(&mut self.stream).await?,
SocksVersion::V5 => SocksRequest::from_stream_socks5(&mut self.stream).await?,
};
let remote_address = request.address_string();
// setup for receiving from the mixnet
let (mix_sender, mix_receiver) = mpsc::unbounded();
@@ -354,7 +406,10 @@ impl SocksClient {
// Use the Proxy to connect to the specified addr/port
SocksCommand::Connect => {
trace!("Connecting to: {:?}", remote_address.clone());
self.acknowledge_socks5().await;
match version {
SocksVersion::V4 => self.acknowledge_socks4().await,
SocksVersion::V5 => self.acknowledge_socks5().await,
}
self.started_proxy = true;
self.controller_sender
@@ -385,8 +440,8 @@ impl SocksClient {
async fn acknowledge_socks5(&mut self) {
self.stream
.write_all(&[
SOCKS_VERSION,
ResponseCode::Success as u8,
SOCKS5_VERSION,
ResponseCodeV5::Success as u8,
RESERVED,
1,
127,
@@ -400,13 +455,30 @@ impl SocksClient {
.unwrap();
}
/// Writes a Socks4 header back to the requesting client's TCP stream,
async fn acknowledge_socks4(&mut self) {
self.stream
.write_all(&[
0, //SOCKS4_VERSION,
ResponseCodeV4::Granted as u8,
0,
0,
127,
0,
0,
1,
])
.await
.unwrap();
}
/// Authenticate the incoming request. Each request is checked for its
/// authentication method. A user/password request will extract the
/// username and password from the stream, then check with the Authenticator
/// to see if the resulting user is allowed.
///
/// A lot of this could probably be put into the `SocksRequest::from_stream()`
/// constructor, and/or cleaned up with tokio::codec. It's mostly just
/// constructor, and/or cleaned up with `tokio::codec`. It's mostly just
/// read-a-byte-or-two. The bytes being extracted look like this:
///
/// +----+------+----------+------+------------+
@@ -418,7 +490,7 @@ impl SocksClient {
/// Pulling out the stream code into its own home, and moving the if/else logic
/// into the Authenticator (where it'll be more easily testable)
/// would be a good next step.
async fn authenticate(&mut self) -> Result<(), SocksProxyError> {
async fn authenticate_socks5(&mut self) -> Result<(), SocksProxyError> {
debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip());
// Get valid auth methods
let methods = self.get_available_methods().await?;
@@ -427,7 +499,7 @@ impl SocksClient {
let mut response = [0u8; 2];
// Set the version in the response
response[0] = SOCKS_VERSION;
response[0] = SOCKS5_VERSION;
if methods.contains(&(AuthenticationMethods::UserPass as u8)) {
// Set the default auth method (NO AUTH)
response[1] = AuthenticationMethods::UserPass as u8;
@@ -463,11 +535,11 @@ impl SocksClient {
// Authenticate passwords
if self.authenticator.is_allowed(&user) {
debug!("Access Granted. User: {}", user.username);
let response = [1, ResponseCode::Success as u8];
let response = [1, ResponseCodeV5::Success as u8];
self.stream.write_all(&response).await?;
} else {
debug!("Access Denied. User: {}", user.username);
let response = [1, ResponseCode::Failure as u8];
let response = [1, ResponseCodeV5::Failure as u8];
self.stream.write_all(&response).await?;
// Shutdown
@@ -486,7 +558,7 @@ impl SocksClient {
response[1] = AuthenticationMethods::NoMethods as u8;
self.stream.write_all(&response).await?;
self.shutdown().await?;
Err(ResponseCode::Failure.into())
Err(ResponseCodeV5::Failure.into())
}
}
+6 -1
View File
@@ -1,3 +1,5 @@
use std::time::Duration;
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
@@ -103,7 +105,10 @@ impl MixnetResponseListener {
}
}
}
assert!(self.shutdown.is_shutdown_poll());
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("MixnetResponseListener: Exiting");
}
}
+26 -1
View File
@@ -1,5 +1,9 @@
#![forbid(unsafe_code)]
use std::convert::TryFrom;
use self::types::SocksProxyError;
pub mod authentication;
pub(crate) mod client;
pub(crate) mod mixnet_responses;
@@ -9,6 +13,27 @@ pub mod types;
pub mod utils;
/// Version of socks
const SOCKS_VERSION: u8 = 0x05;
const SOCKS4_VERSION: u8 = 0x04;
const SOCKS5_VERSION: u8 = 0x05;
const RESERVED: u8 = 0x00;
#[derive(Clone, PartialEq, Eq)]
pub enum SocksVersion {
V4 = 0x04,
V5 = 0x05,
}
pub struct InvalidSocksVersion;
impl TryFrom<u8> for SocksVersion {
type Error = SocksProxyError;
fn try_from(version: u8) -> Result<Self, Self::Error> {
match version {
SOCKS4_VERSION => Ok(Self::V4),
SOCKS5_VERSION => Ok(Self::V5),
_ => Err(SocksProxyError::UnsupportedProxyVersion(version)),
}
}
}
+109 -49
View File
@@ -1,5 +1,7 @@
use super::types::{AddrType, ResponseCode, SocksProxyError};
use super::{utils as socks_utils, SOCKS_VERSION};
use crate::socks::SOCKS4_VERSION;
use super::types::{AddrType, ResponseCodeV5, SocksProxyError};
use super::{utils as socks_utils, SOCKS5_VERSION};
use log::*;
use std::fmt::{self, Display};
use tokio::io::{AsyncRead, AsyncReadExt};
@@ -15,80 +17,114 @@ pub(crate) struct SocksRequest {
}
impl SocksRequest {
/// Parse a SOCKS5 request from a TcpStream
pub async fn from_stream<R>(stream: &mut R) -> Result<Self, SocksProxyError>
/// Parse a SOCKS4 request from a `TcpStream`
/// From documents at:
/// - SOCKS4: https://www.openssh.com/txt/socks4.protocol
/// - SOCKS4a: https://www.openssh.com/txt/socks4a.protocol
pub async fn from_stream_socks4<R>(stream: &mut R) -> Result<Self, SocksProxyError>
where
R: AsyncRead + Unpin,
{
log::trace!("read from stream socks4");
let mut packet = [0u8; 3];
stream.read_exact(&mut packet).await?;
// CD (command)
let Some(command) = SocksCommand::from(packet[0] as usize) else {
log::warn!("Invalid Command");
return Err(ResponseCodeV5::CommandNotSupported.into());
};
// DSTPORT
let mut port = [0u8; 2];
port.copy_from_slice(&packet[1..]);
let port = merge_u8_into_u16(port[0], port[1]);
// DSTIP
let mut ip = [0u8; 4];
stream.read_exact(&mut ip).await?;
// USERID
let _userid = read_until_zero(stream).await;
// SOCKS4a extension
// https://www.openssh.com/txt/socks4a.protocol
// If the IP is 0.0.0.x with x nonzero, read the domain name
let (addr, addr_type) = if ip[..3] == [0, 0, 0] && ip[3] != 0 {
(read_until_zero(stream).await?, AddrType::Domain)
} else {
(ip.to_vec(), AddrType::V4)
};
// Return parsed request
Ok(SocksRequest {
version: SOCKS4_VERSION,
command,
addr_type,
addr,
port,
})
}
/// Parse a SOCKS5 request from a `TcpStream`
/// From: https://www.rfc-editor.org/rfc/rfc1928
pub async fn from_stream_socks5<R>(stream: &mut R) -> Result<Self, SocksProxyError>
where
R: AsyncRead + Unpin,
{
log::info!("read from stream socks5");
let mut packet = [0u8; 4];
// Read a byte from the stream and determine the version being requested
stream.read_exact(&mut packet).await?;
if packet[0] != SOCKS_VERSION {
warn!("from_stream Unsupported version: SOCKS{}", packet[0]);
// VER
if packet[0] != SOCKS5_VERSION {
warn!("Unsupported version: SOCKS{}", packet[0]);
return Err(SocksProxyError::UnsupportedProxyVersion(packet[0]));
}
// Get command
let mut command: SocksCommand = SocksCommand::Connect;
match SocksCommand::from(packet[1] as usize) {
Some(com) => {
command = com;
Ok(())
}
None => {
warn!("Invalid Command");
Err(ResponseCode::CommandNotSupported)
}
}?;
// CMD
let Some(command) = SocksCommand::from(packet[1] as usize) else {
warn!("Invalid Command");
return Err(ResponseCodeV5::CommandNotSupported.into());
};
// DST.address
// RSV
// packet[2] is reserved
let mut addr_type: AddrType = AddrType::V6;
match AddrType::from(packet[3] as usize) {
Some(addr) => {
addr_type = addr;
Ok(())
}
None => {
error!("No Addr");
Err(ResponseCode::AddrTypeNotSupported)
}
}?;
// ATYP
let Some(addr_type) = AddrType::from(packet[3] as usize) else {
error!("No Addr");
return Err(ResponseCodeV5::AddrTypeNotSupported.into())
};
trace!("Getting Addr");
// Get Addr from addr_type and stream
let addr: Result<Vec<u8>, SocksProxyError> = match addr_type {
// DST.ADDR
let addr = match addr_type {
AddrType::Domain => {
let mut domain_length = [0u8; 1];
let mut domain_length = [0u8];
stream.read_exact(&mut domain_length).await?;
let mut domain = vec![0u8; domain_length[0] as usize];
stream.read_exact(&mut domain).await?;
Ok(domain)
domain
}
AddrType::V4 => {
let mut addr = [0u8; 4];
stream.read_exact(&mut addr).await?;
Ok(addr.to_vec())
addr.to_vec()
}
AddrType::V6 => {
let mut addr = [0u8; 16];
stream.read_exact(&mut addr).await?;
Ok(addr.to_vec())
addr.to_vec()
}
};
let addr = addr?;
// read DST.port
// DST.PORT
let mut port = [0u8; 2];
stream.read_exact(&mut port).await?;
// Merge two u8s into u16
let port = (u16::from(port[0]) << 8) | u16::from(port[1]);
let port = merge_u8_into_u16(port[0], port[1]);
// Return parsed request
Ok(SocksRequest {
version: packet[0],
command,
@@ -97,14 +133,18 @@ impl SocksRequest {
port,
})
}
/// Print out the address and port to a String.
/// This might return domain:port, ipv6:port, or ipv4:port.
pub fn address_string(&self) -> String {
let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr);
format!("{}:{}", address, self.port)
}
}
impl Display for SocksRequest {
/// Print out the address and port to a String.
/// This might return domain:port, ipv6:port, or ipv4:port.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr);
write!(f, "{}:{}", address, self.port)
write!(f, "{}", self.address_string())
}
}
@@ -127,3 +167,23 @@ impl SocksCommand {
}
}
}
fn merge_u8_into_u16(a: u8, b: u8) -> u16 {
(u16::from(a) << 8) | u16::from(b)
}
async fn read_until_zero<R>(stream: &mut R) -> Result<Vec<u8>, SocksProxyError>
where
R: AsyncRead + Unpin,
{
let mut result = Vec::new();
let mut char = [0u8];
loop {
stream.read_exact(&mut char).await?;
if char[0] == 0 {
break;
}
result.push(char[0]);
}
Ok(result)
}
+17 -36
View File
@@ -1,8 +1,7 @@
use super::authentication::Authenticator;
use super::client::SocksClient;
use crate::error::Socks5ClientError;
use super::{
mixnet_responses::MixnetResponseListener,
types::{ResponseCode, SocksProxyError},
authentication::Authenticator, client::SocksClient, mixnet_responses::MixnetResponseListener,
};
use crate::socks::client;
use client_connections::{ConnectionCommandSender, LaneQueueLengths};
@@ -13,6 +12,7 @@ use log::*;
use nymsphinx::addressing::clients::Recipient;
use proxy_helpers::connection_controller::{BroadcastActiveConnections, Controller};
use std::net::SocketAddr;
use tap::TapFallible;
use task::ShutdownListener;
use tokio::net::TcpListener;
@@ -60,8 +60,10 @@ impl SphinxSocksServer {
input_sender: InputMessageSender,
buffer_requester: ReceivedBufferRequestSender,
client_connection_tx: ConnectionCommandSender,
) -> Result<(), SocksProxyError> {
let listener = TcpListener::bind(self.listening_address).await.unwrap();
) -> Result<(), Socks5ClientError> {
let listener = TcpListener::bind(self.listening_address)
.await
.tap_err(|err| log::error!("Failed to bind to address: {err}"))?;
info!("Serving Connections...");
// controller for managing all active connections
@@ -87,48 +89,27 @@ impl SphinxSocksServer {
loop {
tokio::select! {
Ok((stream, _remote)) = listener.accept() => {
// TODO Optimize this
let mut client = SocksClient::new(
self.client_config,
stream,
self.authenticator.clone(),
input_sender.clone(),
self.service_provider,
&self.service_provider,
controller_sender.clone(),
self.self_address,
&self.self_address,
self.lane_queue_lengths.clone(),
self.shutdown.clone(),
);
tokio::spawn(async move {
{
match client.run().await {
Ok(_) => {}
Err(error) => {
error!("Error! {}", error);
let error_text = format!("{}", error);
let response: ResponseCode;
if error_text.contains("Host") {
response = ResponseCode::HostUnreachable;
} else if error_text.contains("Network") {
response = ResponseCode::NetworkUnreachable;
} else if error_text.contains("ttl") {
response = ResponseCode::TtlExpired
} else {
response = ResponseCode::Failure
}
if client.error(response).await.is_err() {
warn!("Failed to send error code");
};
if client.shutdown().await.is_err() {
warn!("Failed to shutdown TcpStream");
};
}
if let Err(err) = client.run().await {
error!("Error! {}", err);
if client.send_error(err).await.is_err() {
warn!("Failed to send error code");
};
if client.shutdown().await.is_err() {
warn!("Failed to shutdown TcpStream");
};
// client gets dropped here
}
});
},
+13 -3
View File
@@ -1,7 +1,17 @@
use snafu::Snafu;
#[derive(Debug, Snafu)]
/// SOCKS4 Response codes
#[allow(dead_code)]
pub(crate) enum ResponseCodeV4 {
Granted = 0x5a,
RequestRejected = 0x5b,
CannotConnectToIdent = 0x5c,
DifferentUserId = 0x5d,
}
/// Possible SOCKS5 Response Codes
pub(crate) enum ResponseCode {
#[derive(Debug, Snafu)]
pub(crate) enum ResponseCodeV5 {
Success = 0x00,
#[snafu(display("SOCKS5 Server Failure"))]
Failure = 0x01,
@@ -48,7 +58,7 @@ where
}
/// DST.addr variant types
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub(crate) enum AddrType {
V4 = 0x01,
Domain = 0x03,
+1 -1
View File
@@ -39,7 +39,7 @@ topology = { path = "../../common/topology" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
task = { path = "../../common/task" }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+1 -1
View File
@@ -24,7 +24,7 @@
},
"../pkg": {
"name": "@nymproject/nym-client-wasm",
"version": "1.0.0",
"version": "1.1.0",
"license": "Apache-2.0"
},
"node_modules/@discoveryjs/json-ext": {
-7
View File
@@ -61,16 +61,9 @@ async function main() {
// sets up better stack traces in case of in-rust panics
set_panic_hook();
console.error("the current mainnet is not compatible with v2! - either use the pre-merge branch or explicitly set the client to use one of V2 QA networks")
return
// validator server we will use to get topology from
// MAINNET (V1):
const validator = 'https://validator.nymtech.net/api'; //"http://localhost:8081";
const preferredGateway = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// QA (V2):
// const validator = 'https://qa-validator-api.nymtech.net/api'; //"http://localhost:8081";
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
+6 -3
View File
@@ -4,12 +4,15 @@
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
#![allow(clippy::drop_non_drop)]
use client_core::config::{Debug as ConfigDebug, ExtendedPacketSize, GatewayEndpoint};
use client_core::config::{DebugConfig as ConfigDebug, ExtendedPacketSize, GatewayEndpointConfig};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
/// ID specifies the human readable ID of this particular client.
pub(crate) id: String,
@@ -19,7 +22,7 @@ pub struct Config {
pub(crate) disabled_credentials_mode: bool,
/// Information regarding how the client should send data to gateway.
pub(crate) gateway_endpoint: GatewayEndpoint,
pub(crate) gateway_endpoint: GatewayEndpointConfig,
pub(crate) debug: ConfigDebug,
}
@@ -30,7 +33,7 @@ impl Config {
pub fn new(
id: String,
validator_server: String,
gateway_endpoint: GatewayEndpoint,
gateway_endpoint: GatewayEndpointConfig,
debug: Option<Debug>,
) -> Self {
Config {
+101 -342
View File
@@ -2,56 +2,46 @@
// SPDX-License-Identifier: Apache-2.0
use self::config::Config;
use client_connections::{ConnectionCommandReceiver, LaneQueueLengths, TransmissionLane};
use client_core::client::replies::reply_controller;
use client_core::client::replies::reply_controller::{
ReplyControllerReceiver, ReplyControllerSender,
};
use client_core::client::replies::reply_storage::{CombinedReplyStorage, SentReplyKeys};
use client_core::client::{
cover_traffic_stream::LoopCoverTrafficStream,
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
key_manager::KeyManager,
mix_traffic::{BatchMixMessageSender, MixTrafficController},
real_messages_control::{self, RealMessagesController},
received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender,
ReceivedMessagesBufferController,
},
topology_control::{TopologyAccessor, TopologyRefresher, TopologyRefresherConfig},
};
use crate::client::response_pusher::ResponsePusher;
use client_connections::TransmissionLane;
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
use client_core::client::replies::reply_storage::browser_backend;
use client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use nymsphinx::addressing::clients::Recipient;
use rand::rngs::OsRng;
use task::ShutdownNotifier;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::console_log;
use wasm_utils::{console_error, console_log};
pub mod config;
mod response_pusher;
#[wasm_bindgen]
pub struct NymClient {
config: Config,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
// due to disgusting workaround I had to wrap the below in an Option
// so that the interface wouldn't change (i.e. both `start` and `new` would still return a `NymClient`)
key_manager: Option<KeyManager>,
self_address: Option<String>,
storage_backend: Option<browser_backend::Backend>,
// TODO: this should be stored somewhere persistently
// received_keys: HashSet<SURBEncryptionKey>,
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
/// through the mix network.
input_tx: Option<InputMessageSender>,
client_input: Option<ClientInput>,
// callbacks
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
on_gateway_connect: Option<js_sys::Function>,
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_shutdown: Option<ShutdownNotifier>,
}
#[wasm_bindgen]
@@ -59,15 +49,21 @@ impl NymClient {
#[wasm_bindgen(constructor)]
pub fn new(config: Config) -> Self {
Self {
storage_backend: Some(Self::setup_reply_surb_storage_backend(&config)),
config,
key_manager: Self::setup_key_manager(),
key_manager: Some(Self::setup_key_manager()),
on_message: None,
on_binary_message: None,
on_gateway_connect: None,
input_tx: None,
client_input: None,
self_address: None,
_shutdown: None,
}
}
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
// perhaps this should be public?
fn setup_key_manager() -> KeyManager {
let mut rng = OsRng;
@@ -76,6 +72,15 @@ impl NymClient {
KeyManager::new(&mut rng)
}
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
browser_backend::Backend::new(
config.debug.minimum_reply_surb_storage_threshold,
config.debug.maximum_reply_surb_storage_threshold,
)
}
pub fn set_on_message(&mut self, on_message: js_sys::Function) {
self.on_message = Some(on_message);
}
@@ -89,325 +94,26 @@ impl NymClient {
}
fn as_mix_recipient(&self) -> Recipient {
// another disgusting (and hopefully temporary) workaround
let key_manager_ref = self
.key_manager
.as_ref()
.expect("attempting to call 'as_mix_recipient' after 'start'");
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
*key_manager_ref.identity_keypair().public_key(),
*key_manager_ref.encryption_keypair().public_key(),
identity::PublicKey::from_base58_string(&self.config.gateway_endpoint.gateway_id)
.expect("no gateway has been selected"),
)
}
pub fn self_address(&self) -> String {
self.as_mix_recipient().to_string()
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
) {
console_log!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.debug.average_ack_delay,
self.config.debug.average_packet_delay,
self.config.debug.loop_cover_traffic_average_delay,
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = &self.config.debug.use_extended_packet_size {
stream.set_custom_packet_size((*size).into());
if let Some(address) = &self.self_address {
address.clone()
} else {
self.as_mix_recipient().to_string()
}
stream.start();
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
reply_storage: CombinedReplyStorage,
reply_controller_sender: ReplyControllerSender,
reply_controller_receiver: ReplyControllerReceiver,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
) {
let mut controller_config = real_messages_control::Config::new(
&self.config.debug,
self.key_manager.ack_key(),
self.as_mix_recipient(),
);
if let Some(size) = &self.config.debug.use_extended_packet_size {
controller_config.set_custom_packet_size((*size).into());
}
console_log!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_storage,
reply_controller_sender,
reply_controller_receiver,
lane_queue_lengths,
client_connection_rx,
)
.start();
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
) {
console_log!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
reply_key_storage,
reply_controller_sender,
)
.start()
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
) -> GatewayClient {
let gateway_id = self.config.gateway_endpoint.gateway_id.clone();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_owner = self.config.gateway_endpoint.gateway_owner.clone();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_address = self.config.gateway_endpoint.gateway_listener.clone();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
None,
mixnet_message_sender,
ack_sender,
self.config.debug.gateway_response_timeout,
None,
);
gateway_client.set_disabled_credentials_mode(self.config.disabled_credentials_mode);
let shared_keys = gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
self.key_manager.insert_gateway_shared_key(shared_keys);
match self.on_gateway_connect.as_ref() {
Some(callback) => {
callback
.call0(&JsValue::null())
.expect("on connect callback failed!");
}
None => console_log!("Gateway connection established - no callback specified"),
};
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
vec![self.config.validator_api_url.clone()],
self.config.debug.topology_refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
console_log!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
);
}
console_log!("Starting topology refresher...");
// TODO: re-enable
topology_refresher.start();
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(gateway_client: GatewayClient) -> BatchMixMessageSender {
console_log!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start();
mix_tx
}
// TODO: this procedure is extremely overcomplicated, because it's based off native client's behaviour
// which doesn't fully apply in this case
fn start_reconstructed_pusher(
&mut self,
received_buffer_request_sender: ReceivedBufferRequestSender,
) {
let on_message = self.on_message.take();
let on_binary_message = self.on_binary_message.take();
spawn_local(async move {
let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
let this = JsValue::null();
while let Some(reconstructed) = reconstructed_receiver.next().await {
for msg in reconstructed {
if let Some(ref callback_binary) = on_binary_message {
let arg1 = serde_wasm_bindgen::to_value(&msg.message).unwrap();
callback_binary
.call1(&this, &arg1)
.expect("on binary message failed!");
}
if let Some(ref callback) = on_message {
if msg.sender_tag.is_some() {
console_log!("the received message contained a sender_tag meaning it also contained some reply surbs, but we do not know how to handle them (yet)")
}
let stringified = String::from_utf8_lossy(&msg.message).into_owned();
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!");
}
}
}
});
}
pub async fn start(mut self) -> NymClient {
console_log!("Starting wasm client '{}'", self.config.id);
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
// channels responsible for dealing with reply-related fun
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::new_control_channels();
// =====================
// =====================
// ======TEMPORARY======
// =====================
// =====================
// TODO: lower the value and improve the reliability when it's low (because it should still work in that case)
// (it goes insane at 10,200)
let reply_storage = CombinedReplyStorage::new(
self.config.debug.minimum_reply_surb_storage_threshold,
self.config.debug.maximum_reply_surb_storage_threshold,
);
// Channel that the real traffix controller can listed to for closing connections.
// Currently unused in the wasm client.
let (_client_connection_tx, client_connection_rx) = mpsc::unbounded();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
reply_controller_sender.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender = Self::start_mix_traffic_controller(gateway_client);
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
reply_storage,
reply_controller_sender,
reply_controller_receiver,
shared_lane_queue_lengths,
client_connection_rx,
);
if !self.config.debug.disable_loop_cover_traffic_stream {
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
}
self.start_reconstructed_pusher(received_buffer_request_sender);
self.input_tx = Some(input_sender);
self
}
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
@@ -427,13 +133,66 @@ impl NymClient {
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.input_tx
self.client_input
.as_ref()
.expect("start method was not called before!")
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
self
}
fn start_reconstructed_pusher(
client_output: ClientOutput,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
) {
ResponsePusher::new(client_output, on_message, on_binary_message).start()
}
pub async fn start(mut self) -> NymClient {
console_log!("Starting the wasm client");
let base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager.take().unwrap(),
None,
self.storage_backend.take().unwrap(),
true,
vec![self.config.validator_api_url.clone()],
);
self.self_address = Some(base_builder.as_mix_recipient().to_string());
let mut started_client = match base_builder.start_base().await {
Ok(base_client) => base_client,
Err(err) => {
console_error!("failed to start base client components - {}", err);
// proper error handling is left here as an exercise for the reader (hi Mark : ))
panic!("failed to start base client components - {err}")
}
};
match self.on_gateway_connect.as_ref() {
Some(callback) => {
callback
.call0(&JsValue::null())
.expect("on connect callback failed!");
}
None => console_log!("Gateway connection established - no callback specified"),
};
// those should be moved to a completely different struct, but I don't want to break compatibility for now
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let on_message = self.on_message.take();
let on_binary_message = self.on_binary_message.take();
Self::start_reconstructed_pusher(client_output, on_message, on_binary_message);
self.client_input = Some(client_input);
self._shutdown = Some(started_client.shutdown_notifier);
self
}
}
@@ -0,0 +1,71 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::client::base_client::ClientOutput;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use futures::channel::mpsc;
use futures::StreamExt;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::console_log;
pub(crate) struct ResponsePusher {
reconstructed_receiver: ReconstructedMessagesReceiver,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
}
impl ResponsePusher {
pub(crate) fn new(
client_output: ClientOutput,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
) -> Self {
if on_message.is_none() && on_binary_message.is_none() {
// exercise for the reader : )
panic!("neither 'on_message' nor 'on_binary_message' was set!")
}
// register our output
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
client_output
.received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
ResponsePusher {
reconstructed_receiver,
on_message,
on_binary_message,
}
}
pub(crate) fn start(mut self) {
spawn_local(async move {
let this = JsValue::null();
while let Some(reconstructed) = self.reconstructed_receiver.next().await {
for msg in reconstructed {
if let Some(ref callback_binary) = self.on_binary_message {
let arg1 = serde_wasm_bindgen::to_value(&msg.message).unwrap();
callback_binary
.call1(&this, &arg1)
.expect("on binary message failed!");
}
if let Some(ref callback) = self.on_message {
if msg.sender_tag.is_some() {
console_log!("the received message contained a sender tag (meaning we also got some surbs!), but we do not know how to handle that (yet)")
}
let stringified = String::from_utf8_lossy(&msg.message).into_owned();
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!");
}
}
}
})
}
}
+5 -5
View File
@@ -1,15 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::config::GatewayEndpoint;
use client_core::config::GatewayEndpointConfig;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpoint {
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpointConfig {
let validator_client = validator_client::client::ApiClient::new(api_server.parse().unwrap());
let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("failed to obtain list of all gateways - {}", err),
Err(err) => panic!("failed to obtain list of all gateways - {err}"),
Ok(gateways) => gateways,
};
@@ -18,7 +18,7 @@ pub async fn get_gateway(api_server: String, preferred: Option<String>) -> Gatew
.iter()
.find(|g| g.gateway.identity_key == preferred)
{
return GatewayEndpoint {
return GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
@@ -33,7 +33,7 @@ pub async fn get_gateway(api_server: String, preferred: Option<String>) -> Gatew
.first()
.expect("current topology holds no gateways");
GatewayEndpoint {
GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
+1 -3
View File
@@ -26,6 +26,7 @@ network-defaults = { path = "../../network-defaults" }
nymsphinx = { path = "../../nymsphinx" }
pemstore = { path = "../../pemstore" }
validator-client = { path = "../validator-client", optional = true }
task = { path = "../../task" }
[dependencies.tungstenite]
@@ -47,9 +48,6 @@ version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage]
path = "../../credential-storage"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
path = "../../task"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2"
@@ -26,8 +26,15 @@ use {
},
};
// TODO: make it nicer for wasm (I don't want to touch it for this experiment)
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[derive(Clone)]
pub struct BandwidthController<St: Storage> {
pub struct BandwidthController<St: Storage = PersistentStorage> {
#[allow(dead_code)]
storage: St,
#[cfg(feature = "coconut")]
+54 -66
View File
@@ -10,11 +10,11 @@ pub use crate::packet_router::{
use crate::socket_state::{PartiallyDelegated, SocketState};
use crate::{cleanup_socket_message, try_decrypt_binary_message};
use crypto::asymmetric::identity;
use futures::{FutureExt, SinkExt, StreamExt};
use futures::{SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION};
use log::*;
use network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
use nymsphinx::forwarding::packet::MixPacket;
@@ -22,6 +22,7 @@ use rand::rngs::OsRng;
use std::convert::TryFrom;
use std::sync::Arc;
use std::time::Duration;
use task::ShutdownListener;
use tungstenite::protocol::Message;
#[cfg(feature = "coconut")]
@@ -30,8 +31,6 @@ use coconut_interface::Credential;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use task::ShutdownListener;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(target_arch = "wasm32")]
@@ -67,9 +66,8 @@ pub struct GatewayClient {
/// Delay between each subsequent reconnection attempt.
reconnection_backoff: Duration,
#[cfg(not(target_arch = "wasm32"))]
/// Listen to shutdown messages.
shutdown: Option<ShutdownListener>,
shutdown: ShutdownListener,
}
impl GatewayClient {
@@ -85,7 +83,7 @@ impl GatewayClient {
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
shutdown: ShutdownListener,
) -> Self {
GatewayClient {
authenticated: false,
@@ -97,18 +95,12 @@ impl GatewayClient {
local_identity,
shared_key,
connection: SocketState::NotConnected,
packet_router: PacketRouter::new(
ack_sender,
mixnet_message_sender,
#[cfg(not(target_arch = "wasm32"))]
shutdown.clone(),
),
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
response_timeout_duration,
bandwidth_controller,
should_reconnect_on_failure: true,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
#[cfg(not(target_arch = "wasm32"))]
shutdown,
}
}
@@ -136,7 +128,6 @@ impl GatewayClient {
gateway_owner: String,
local_identity: Arc<identity::KeyPair>,
response_timeout_duration: Duration,
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
) -> Self {
use futures::channel::mpsc;
@@ -144,12 +135,8 @@ impl GatewayClient {
// perfectly fine here, because it's not meant to be used
let (ack_tx, _) = mpsc::unbounded();
let (mix_tx, _) = mpsc::unbounded();
let packet_router = PacketRouter::new(
ack_tx,
mix_tx,
#[cfg(not(target_arch = "wasm32"))]
shutdown.clone(),
);
let shutdown = ShutdownListener::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
GatewayClient {
authenticated: false,
@@ -167,7 +154,6 @@ impl GatewayClient {
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
#[cfg(not(target_arch = "wasm32"))]
shutdown,
}
}
@@ -295,42 +281,19 @@ impl GatewayClient {
// technically the `wasm_timer` also works outside wasm, but unless required,
// I really prefer to just stick to tokio
#[cfg(target_arch = "wasm32")]
let timeout = wasm_timer::Delay::new(self.response_timeout_duration);
let mut fused_timeout = timeout.fuse();
let mut fused_stream = conn.fuse();
// Bit of an ugly workaround for selecting on an `Option` without having access to
// `tokio::select`
#[cfg(not(target_arch = "wasm32"))]
let shutdown = {
let m_shutdown = self.shutdown.clone();
async {
if let Some(mut s) = m_shutdown {
s.recv().await
} else {
std::future::pending::<()>().await
}
}
.fuse()
};
#[cfg(not(target_arch = "wasm32"))]
tokio::pin!(shutdown);
#[cfg(target_arch = "wasm32")]
let mut shutdown = std::future::pending::<()>().fuse();
let mut timeout = wasm_timer::Delay::new(self.response_timeout_duration);
loop {
futures::select! {
_ = shutdown => {
tokio::select! {
_ = self.shutdown.recv() => {
log::trace!("GatewayClient control response: Received shutdown");
log::debug!("GatewayClient control response: Exiting");
break Err(GatewayClientError::ConnectionClosedGatewayShutdown);
}
_ = &mut fused_timeout => {
_ = &mut timeout => {
break Err(GatewayClientError::Timeout);
}
msg = fused_stream.next() => {
msg = conn.next() => {
let ws_msg = match cleanup_socket_message(msg) {
Err(err) => break Err(err),
Ok(msg) => msg
@@ -401,13 +364,10 @@ impl GatewayClient {
.batch_send_without_response(messages)
.await
{
error!("failed to batch send messages - {}...", err);
error!("failed to batch send messages - {err}...");
// we must ensure we do not leave the task still active
if let Err(err) = self.recover_socket_connection().await {
error!(
"... and the delegated stream has also errored out - {}",
err
)
error!("... and the delegated stream has also errored out - {err}")
}
Err(err)
} else {
@@ -427,13 +387,10 @@ impl GatewayClient {
SocketState::Available(ref mut conn) => Ok(conn.send(msg).await?),
SocketState::PartiallyDelegated(ref mut partially_delegated) => {
if let Err(err) = partially_delegated.send_without_response(msg).await {
error!("failed to send message without response - {}...", err);
error!("failed to send message without response - {err}...");
// we must ensure we do not leave the task still active
if let Err(err) = self.recover_socket_connection().await {
error!(
"... and the delegated stream has also errored out - {}",
err
)
error!("... and the delegated stream has also errored out - {err}")
}
Err(err)
} else {
@@ -445,6 +402,27 @@ impl GatewayClient {
}
}
fn check_gateway_protocol(
&self,
gateway_protocol: Option<u8>,
) -> Result<(), GatewayClientError> {
// right now there are no failure cases here, but this might change in the future
match gateway_protocol {
Some(v) if v == PROTOCOL_VERSION => {
info!("the gateway is using exactly the same protocol version as we are. We're good to continue!");
Ok(())
}
v => {
let err = GatewayClientError::IncompatibleProtocol {
gateway: v,
current: PROTOCOL_VERSION,
};
error!("{err}");
Err(err)
}
}
}
async fn register(&mut self) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -467,11 +445,20 @@ impl GatewayClient {
.map_err(GatewayClientError::RegistrationFailure),
_ => unreachable!(),
}?;
self.authenticated = match self.read_control_response().await? {
ServerResponse::Register { status } => Ok(status),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
ServerResponse::Register {
protocol_version,
status,
} => (status, protocol_version),
ServerResponse::Error { message } => {
return Err(GatewayClientError::GatewayError(message))
}
_ => return Err(GatewayClientError::UnexpectedResponse),
};
self.check_gateway_protocol(gateway_protocol)?;
self.authenticated = authentication_status;
if self.authenticated {
self.shared_key = Some(Arc::new(shared_key));
}
@@ -510,9 +497,11 @@ impl GatewayClient {
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate {
protocol_version,
status,
bandwidth_remaining,
} => {
self.check_gateway_protocol(protocol_version)?;
self.authenticated = status;
self.bandwidth_remaining = bandwidth_remaining;
Ok(())
@@ -748,7 +737,6 @@ impl GatewayClient {
.as_ref()
.expect("no shared key present even though we're authenticated!"),
),
#[cfg(not(target_arch = "wasm32"))]
self.shutdown.clone(),
)
}
@@ -85,6 +85,9 @@ pub enum GatewayClientError {
#[error("Failed to send mixnet message")]
MixnetMsgSenderFailedToSend,
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
}
impl GatewayClientError {
@@ -4,15 +4,13 @@
// JS: I personally don't like this name very much, but could not think of anything better.
// I will gladly take any suggestions on how to rename this.
use crate::error::GatewayClientError;
use futures::channel::mpsc;
use log::*;
use nymsphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
use nymsphinx::params::packet_sizes::PacketSize;
#[cfg(not(target_arch = "wasm32"))]
use task::ShutdownListener;
use crate::error::GatewayClientError;
pub type MixnetMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
pub type MixnetMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
@@ -23,20 +21,18 @@ pub type AcknowledgementReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
pub struct PacketRouter {
ack_sender: AcknowledgementSender,
mixnet_message_sender: MixnetMessageSender,
#[cfg(not(target_arch = "wasm32"))]
shutdown: Option<ShutdownListener>,
shutdown: ShutdownListener,
}
impl PacketRouter {
pub fn new(
ack_sender: AcknowledgementSender,
mixnet_message_sender: MixnetMessageSender,
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
shutdown: ShutdownListener,
) -> Self {
PacketRouter {
ack_sender,
mixnet_message_sender,
#[cfg(not(target_arch = "wasm32"))]
shutdown,
}
}
@@ -86,13 +82,10 @@ impl PacketRouter {
if !received_messages.is_empty() {
trace!("routing 'real'");
if let Err(err) = self.mixnet_message_sender.unbounded_send(received_messages) {
#[cfg(not(target_arch = "wasm32"))]
if let Some(shutdown) = &mut self.shutdown {
if shutdown.is_shutdown_poll() {
// This should ideally not happen, but it's ok
log::warn!("Failed to send mixnet message due to receiver task shutdown");
return Err(GatewayClientError::MixnetMsgSenderFailedToSend);
}
if self.shutdown.is_shutdown_poll() || self.shutdown.is_dummy() {
// This should ideally not happen, but it's ok
log::warn!("Failed to send mixnet message due to receiver task shutdown");
return Err(GatewayClientError::MixnetMsgSenderFailedToSend);
}
// This should never happen during ordinary operation the way it's currently used.
// Abort to be on the safe side
@@ -10,7 +10,6 @@ use futures::{SinkExt, StreamExt};
use gateway_requests::registration::handshake::SharedKeys;
use log::*;
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use task::ShutdownListener;
use tungstenite::Message;
@@ -85,7 +84,7 @@ impl PartiallyDelegated {
conn: WsConn,
packet_router: PacketRouter,
shared_key: Arc<SharedKeys>,
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
mut shutdown: ShutdownListener,
) -> Self {
// when called for, it NEEDS TO yield back the stream so that we could merge it and
// read control request responses.
@@ -99,27 +98,9 @@ impl PartiallyDelegated {
let mut chunk_stream = (&mut stream).ready_chunks(8);
let mut packet_router = packet_router;
// Bit of an ugly workaround for selecting on an `Option` without having access to
// `tokio::select`
#[cfg(not(target_arch = "wasm32"))]
let shutdown = {
async {
if let Some(mut s) = shutdown {
s.recv().await
} else {
std::future::pending::<()>().await
}
}
};
#[cfg(not(target_arch = "wasm32"))]
tokio::pin!(shutdown);
#[cfg(target_arch = "wasm32")]
let mut shutdown = std::future::pending::<()>();
let ret_err = loop {
tokio::select! {
_ = &mut shutdown => {
_ = shutdown.recv() => {
log::trace!("GatewayClient listener: Received shutdown");
log::debug!("GatewayClient listener: Exiting");
return;
@@ -142,7 +123,10 @@ impl PartiallyDelegated {
if match ret_err {
Err(err) => stream_sender.send(Err(err)),
Ok(_) => stream_sender.send(Ok(stream)),
Ok(_) => {
shutdown.mark_as_success();
stream_sender.send(Ok(stream))
}
}
.is_err()
{
@@ -180,6 +180,35 @@ pub trait MixnetSigningClient {
.await
}
async fn pledge_more(
&self,
additional_pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::PledgeMore {},
vec![additional_pledge],
)
.await
}
async fn pledge_more_on_behalf(
&self,
owner: AccountId,
additional_pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::PledgeMoreOnBehalf {
owner: owner.to_string(),
},
vec![additional_pledge],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
@@ -64,6 +64,21 @@ pub trait VestingSigningClient {
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_pledge_more(
&self,
additional_pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::PledgeMore {
amount: additional_pledge.into(),
},
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
@@ -136,7 +136,12 @@ impl Client {
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::DETAILED],
&[
routes::API_VERSION,
routes::STATUS,
routes::MIXNODES,
routes::DETAILED,
],
NO_PARAMS,
)
.await
@@ -161,6 +166,7 @@ impl Client {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::MIXNODES,
routes::ACTIVE,
routes::DETAILED,
@@ -252,6 +258,7 @@ impl Client {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::MIXNODES,
routes::REWARDED,
routes::DETAILED,
+1
View File
@@ -22,6 +22,7 @@ thiserror = "1"
time = { version = "0.3.6", features = ["parsing", "formatting"] }
toml = "0.5.6"
url = "2.2"
tap = "1"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmwasm-std = { version = "1.0.0" }
+6
View File
@@ -15,4 +15,10 @@ pub enum ContextError {
// TODO: improve this to return known errors
#[error("failed to create client - {0}")]
NymdError(String),
#[error("{0}")]
NymdErrorPassthrough(#[from] validator_client::nymd::error::NymdError),
#[error("{0}")]
ValidatorClientError(#[from] validator_client::ValidatorClientError),
}
+5 -4
View File
@@ -6,6 +6,7 @@ use network_defaults::{
var_names::{API_VALIDATOR, MIXNET_CONTRACT_ADDRESS, NYMD_VALIDATOR, VESTING_CONTRACT_ADDRESS},
NymNetworkDetails,
};
use tap::prelude::*;
use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient};
pub use validator_client::validator_api::Client as ValidatorApiClient;
@@ -58,7 +59,7 @@ pub fn create_signing_client(
network_details: &NymNetworkDetails,
) -> Result<SigningClient, ContextError> {
let client_config = nymd::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
// get mnemonic
let mnemonic = match std::env::var("MNEMONIC") {
@@ -87,7 +88,7 @@ pub fn create_query_client(
network_details: &NymNetworkDetails,
) -> Result<QueryClient, ContextError> {
let client_config = nymd::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
let nymd_url = network_details
.endpoints
@@ -107,7 +108,7 @@ pub fn create_signing_client_with_validator_api(
network_details: &NymNetworkDetails,
) -> Result<SigningClientWithValidatorAPI, ContextError> {
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
// get mnemonic
let mnemonic = match std::env::var("MNEMONIC") {
@@ -129,7 +130,7 @@ pub fn create_query_client_with_validator_api(
network_details: &NymNetworkDetails,
) -> Result<QueryClientWithValidatorAPI, ContextError> {
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network");
.tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
match validator_client::client::Client::new_query(client_config) {
Ok(client) => Ok(client),
@@ -3,11 +3,11 @@
use clap::Parser;
use cosmrs::AccountId;
use log::info;
use log::{error, info};
use validator_client::nymd::{Coin, VestingQueryClient};
use crate::context::SigningClient;
use crate::context::QueryClient;
use crate::utils::show_error;
use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
@@ -18,8 +18,16 @@ pub struct Args {
pub address: Option<AccountId>,
}
pub async fn balance(args: Args, client: SigningClient) {
let account_id = args.address.unwrap_or_else(|| client.address().clone());
pub async fn balance(args: Args, client: QueryClient, address_from_mnemonic: Option<AccountId>) {
if args.address.is_none() && address_from_mnemonic.is_none() {
error!("Please specify an account address or a mnemonic to get the balance for");
return;
}
let account_id = args
.address
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
let vesting_address = account_id.to_string();
let denom = client.current_chain_details().mix_denom.base.as_str();
@@ -4,11 +4,11 @@
use clap::Parser;
use cosmrs::AccountId;
use cosmwasm_std::Coin as CosmWasmCoin;
use log::info;
use log::{error, info};
use validator_client::nymd::{Coin, VestingQueryClient};
use crate::context::SigningClient;
use crate::context::QueryClient;
use crate::utils::show_error;
use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
@@ -19,8 +19,18 @@ pub struct Args {
pub address: Option<AccountId>,
}
pub async fn query(args: Args, client: SigningClient) {
let account_id = args.address.unwrap_or_else(|| client.address().clone());
pub async fn query(args: Args, client: QueryClient, address_from_mnemonic: Option<AccountId>) {
if args.address.is_none() && address_from_mnemonic.is_none() {
error!("Please specify an account address or a mnemonic to get the balance for");
return;
}
let account_id = args
.address
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
info!("Checking account {} for a vesting schedule...", account_id);
let vesting_address = account_id.to_string();
let denom = client.current_chain_details().mix_denom.base.as_str();
@@ -12,6 +12,8 @@ pub const EVENT_VERSION_PREFIX: &str = "v2_";
pub enum MixnetEventType {
MixnodeBonding,
PendingPledgeIncrease,
PledgeIncrease,
GatewayBonding,
GatewayUnbonding,
PendingMixnodeUnbonding,
@@ -51,6 +53,8 @@ impl ToString for MixnetEventType {
fn to_string(&self) -> String {
let event_name = match self {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
MixnetEventType::PledgeIncrease => "pledge_increase",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
@@ -330,6 +334,19 @@ pub fn new_mixnode_bonding_event(
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pending_pledge_increase_event(mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PendingPledgeIncrease)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PledgeIncrease)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
@@ -325,6 +325,14 @@ impl MixNodeRewarding {
Ok(())
}
pub fn increase_operator_uint128(
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
self.operator += amount.into_base_decimal()?;
Ok(())
}
pub fn increase_delegates_uint128(
&mut self,
amount: Uint128,
@@ -113,6 +113,10 @@ pub enum ExecuteMsg {
owner_signature: String,
owner: String,
},
PledgeMore {},
PledgeMoreOnBehalf {
owner: String,
},
UnbondMixnode {},
UnbondMixnodeOnBehalf {
owner: String,
@@ -223,6 +227,8 @@ impl ExecuteMsg {
ExecuteMsg::BondMixnodeOnBehalf { mix_node, .. } => {
format!("bonding mixnode {} on behalf", mix_node.identity_key)
}
ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(),
ExecuteMsg::PledgeMoreOnBehalf { .. } => "pledging additional tokens on behalf".into(),
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
@@ -34,6 +34,10 @@ pub enum PendingEpochEventKind {
mix_id: MixId,
proxy: Option<Addr>,
},
PledgeMore {
mix_id: MixId,
amount: Coin,
},
UnbondMixnode {
mix_id: MixId,
},
@@ -78,7 +82,6 @@ pub enum PendingIntervalEventKind {
mix_id: MixId,
new_costs: MixNodeCostParams,
},
UpdateRewardingParams {
update: IntervalRewardingParamsUpdate,
},
@@ -14,6 +14,7 @@ pub const VESTING_UNDELEGATION_EVENT_TYPE: &str = "vesting_undelegation";
pub const VESTING_GATEWAY_BONDING_EVENT_TYPE: &str = "vesting_gateway_bonding";
pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding";
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
@@ -112,6 +113,10 @@ pub fn new_vesting_mixnode_bonding_event() -> Event {
Event::new(VESTING_MIXNODE_BONDING_EVENT_TYPE)
}
pub fn new_vesting_pledge_more_event() -> Event {
Event::new(VESTING_PLEDGE_MORE_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
@@ -101,6 +101,9 @@ pub enum ExecuteMsg {
owner_signature: String,
amount: Coin,
},
PledgeMore {
amount: Coin,
},
UnbondMixnode {},
TrackUnbondMixnode {
owner: String,
@@ -145,6 +148,7 @@ impl ExecuteMsg {
ExecuteMsg::WithdrawVestedCoins { .. } => "VestingExecuteMsg::WithdrawVestedCoins",
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
-20
View File
@@ -1,20 +0,0 @@
CONFIGURED=true
RUST_LOG=info
RUST_BACKTRACE=1
BECH32_PREFIX=n
MIX_DENOM=unym
MIX_DENOM_DISPLAY=nym
STAKE_DENOM=unyx
STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://127.0.0.1:8090"
NYMD_VALIDATOR="https://rpc.nyx.nodes.guru/"
API_VALIDATOR="https://validator.nymtech.net/api/"
-20
View File
@@ -1,20 +0,0 @@
CONFIGURED=true
RUST_LOG=info
RUST_BACKTRACE=1
BECH32_PREFIX=n
MIX_DENOM=unym
MIX_DENOM_DISPLAY=nym
STAKE_DENOM=unyx
STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep
VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw
MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
NYMD_VALIDATOR="https://qa-validator.nymtech.net"
API_VALIDATOR="https://qa-validator-api.nymtech.net/api"
+8
View File
@@ -85,6 +85,10 @@ pub fn export_to_env() {
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_to_default(
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
COCONUT_DKG_CONTRACT_ADDRESS,
);
set_var_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
@@ -125,6 +129,10 @@ pub fn export_to_env_if_not_set() {
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
COCONUT_DKG_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
@@ -254,6 +254,10 @@ impl Controller {
},
}
}
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
.await
.expect("Task stopped without shutdown called");
assert!(self.shutdown.is_shutdown_poll());
log::debug!("SOCKS5 Controller: Exiting");
}
@@ -208,5 +208,6 @@ where
trace!("{} - inbound closed", connection_id);
shutdown_notify.notify_one();
shutdown_listener.mark_as_success();
reader
}
@@ -134,6 +134,7 @@ where
}
pub fn into_inner(mut self) -> (TcpStream, ConnectionReceiver) {
self.shutdown_listener.mark_as_success();
(
self.socket.take().unwrap(),
self.mix_receiver.take().unwrap(),
@@ -90,5 +90,6 @@ pub(super) async fn run_outbound(
trace!("{} - outbound closed", connection_id);
shutdown_notify.notify_one();
shutdown_listener.mark_as_success();
(writer, mix_receiver)
}
+13 -1
View File
@@ -6,8 +6,20 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3"
log = "0.4"
tokio = { version = "1.21.2", features = ["macros", "signal", "time", "sync"] }
thiserror = "1.0.37"
tokio = { version = "1.21.2", features = ["macros", "sync"] }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
version = "1.21.2"
features = ["signal", "time"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2.83"
[dev-dependencies]
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] }
+6 -1
View File
@@ -2,7 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
pub mod shutdown;
#[cfg(not(target_arch = "wasm32"))]
pub mod signal;
pub mod spawn;
pub use shutdown::{ShutdownListener, ShutdownNotifier};
pub use signal::wait_for_signal;
#[cfg(not(target_arch = "wasm32"))]
pub use signal::{wait_for_signal, wait_for_signal_and_error};
pub use spawn::spawn_with_report_error;
+196 -5
View File
@@ -1,27 +1,63 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
use std::{error::Error, time::Duration};
use tokio::sync::watch::{self, error::SendError};
use futures::{future::pending, FutureExt};
use tokio::{
sync::{
mpsc,
watch::{self, error::SendError},
},
time::sleep,
};
const DEFAULT_SHUTDOWN_TIMER_SECS: u64 = 5;
pub(crate) type SentError = Box<dyn Error + Send>;
type ErrorSender = mpsc::UnboundedSender<SentError>;
type ErrorReceiver = mpsc::UnboundedReceiver<SentError>;
#[derive(thiserror::Error, Debug)]
enum TaskError {
#[error("Task halted unexpectedly")]
UnexpectedHalt,
}
/// Used to notify other tasks to gracefully shutdown
#[derive(Debug)]
pub struct ShutdownNotifier {
// These channels have the dual purpose of signalling it's time to shutdown, but also to keep
// track of which tasks we are still waiting for.
notify_tx: watch::Sender<()>,
notify_rx: Option<watch::Receiver<()>>,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
shutdown_timer_secs: u64,
// If any task failed, it needs to report separately
task_return_error_tx: ErrorSender,
task_return_error_rx: Option<ErrorReceiver>,
// Also signal when the notifier is dropped, in case the task exits unexpectedly.
// Why are we not reusing the return error channel? Well, let me tell you kids, it's because I
// didn't manage to reliably get the explicitly sent error (and not the error sent during drop)
task_drop_tx: ErrorSender,
task_drop_rx: Option<ErrorReceiver>,
}
impl Default for ShutdownNotifier {
fn default() -> Self {
let (notify_tx, notify_rx) = watch::channel(());
let (task_halt_tx, task_halt_rx) = mpsc::unbounded_channel();
let (task_drop_tx, task_drop_rx) = mpsc::unbounded_channel();
Self {
notify_tx,
notify_rx: Some(notify_rx),
shutdown_timer_secs: DEFAULT_SHUTDOWN_TIMER_SECS,
task_return_error_tx: task_halt_tx,
task_return_error_rx: Some(task_halt_rx),
task_drop_tx,
task_drop_rx: Some(task_drop_rx),
}
}
}
@@ -40,6 +76,8 @@ impl ShutdownNotifier {
.as_ref()
.expect("Unable to subscribe to shutdown notifier that is already shutdown")
.clone(),
self.task_return_error_tx.clone(),
self.task_drop_tx.clone(),
)
}
@@ -47,16 +85,45 @@ impl ShutdownNotifier {
self.notify_tx.send(())
}
pub async fn wait_for_error(&mut self) -> Option<SentError> {
let mut error_rx = self
.task_return_error_rx
.take()
.expect("Unable to wait for error: attempt to wait twice?");
let mut drop_rx = self
.task_drop_rx
.take()
.expect("Unable to wait for error: attempt to wait twice?");
// During an error we are likely like to be swamped with drop notifications as well, this
// is a crude way to give priority to real errors (if there are any).
let drop_rx = drop_rx.recv().then(|msg| async move {
sleep(Duration::from_millis(50)).await;
msg
});
tokio::select! {
msg = error_rx.recv() => msg,
msg = drop_rx => msg
}
}
pub async fn wait_for_shutdown(&mut self) {
log::info!("Waiting for shutdown");
if let Some(notify_rx) = self.notify_rx.take() {
drop(notify_rx);
}
// in wasm we'll never get our shutdown anyway...
#[cfg(target_arch = "wasm32")]
futures::future::pending::<()>().await;
#[cfg(not(target_arch = "wasm32"))]
tokio::select! {
_ = self.notify_tx.closed() => {
log::info!("All registered tasks succesfully shutdown");
},
_ = tokio::signal::ctrl_c() => {
_ = tokio::signal::ctrl_c() => {
log::info!("Forcing shutdown");
}
_ = tokio::time::sleep(Duration::from_secs(self.shutdown_timer_secs)) => {
@@ -69,23 +136,71 @@ impl ShutdownNotifier {
/// Listen for shutdown notifications
#[derive(Clone, Debug)]
pub struct ShutdownListener {
// If a shutdown notification has been registered
shutdown: bool,
// Listen for shutdown notifications, as well as a mechanism to report back that we have
// finished (the receiver is closed).
notify: watch::Receiver<()>,
// Send back error if we stopped
return_error: ErrorSender,
// Also notify if we dropped without shutdown being registered
drop_error: ErrorSender,
// The current operating mode
mode: ShutdownListenerMode,
}
impl ShutdownListener {
fn new(notify: watch::Receiver<()>) -> ShutdownListener {
#[cfg(not(target_arch = "wasm32"))]
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
fn new(
notify: watch::Receiver<()>,
return_error: ErrorSender,
drop_error: ErrorSender,
) -> ShutdownListener {
ShutdownListener {
shutdown: false,
notify,
return_error,
drop_error,
mode: ShutdownListenerMode::Listening,
}
}
// Create a dummy that will never report that we should shutdown.
pub fn dummy() -> ShutdownListener {
let (_notify_tx, notify_rx) = watch::channel(());
let (task_halt_tx, _task_halt_rx) = mpsc::unbounded_channel();
let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel();
ShutdownListener {
shutdown: false,
notify: notify_rx,
return_error: task_halt_tx,
drop_error: task_drop_tx,
mode: ShutdownListenerMode::Dummy,
}
}
pub fn is_dummy(&self) -> bool {
self.mode.is_dummy()
}
pub fn is_shutdown(&self) -> bool {
self.shutdown
if self.mode.is_dummy() {
false
} else {
self.shutdown
}
}
pub async fn recv(&mut self) {
if self.mode.is_dummy() {
return pending().await;
}
if self.shutdown {
return;
}
@@ -93,7 +208,20 @@ impl ShutdownListener {
self.shutdown = true;
}
pub async fn recv_timeout(&mut self) {
if self.mode.is_dummy() {
return pending().await;
}
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Self::SHUTDOWN_TIMEOUT, self.recv())
.await
.expect("Task stopped without shutdown called");
}
pub fn is_shutdown_poll(&mut self) -> bool {
if self.mode.is_dummy() {
return false;
}
if self.shutdown {
return true;
}
@@ -111,6 +239,69 @@ impl ShutdownListener {
}
}
}
// This listener should to *not* notify the ShutdownNotifier to shutdown when dropped. For
// example when we clone the listener for a task handling connections, we often want to drop
// without signal failure.
pub fn mark_as_success(&mut self) {
self.mode.set_should_not_signal_on_drop();
}
pub fn send_we_stopped(&mut self, err: SentError) {
if self.mode.is_dummy() {
return;
}
log::trace!("Notifying we stopped: {:?}", err);
if self.return_error.send(err).is_err() {
log::error!("Failed to send back error message");
}
}
}
impl Drop for ShutdownListener {
fn drop(&mut self) {
if !self.mode.should_signal_on_drop() {
return;
}
if !self.is_shutdown_poll() {
log::trace!("Notifying stop on unexpected drop");
// If we can't send, well then there is not much to do
self.drop_error
.send(Box::new(TaskError::UnexpectedHalt))
.ok();
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum ShutdownListenerMode {
// Normal operations
Listening,
// Normal operations, but we don't report back if the we stop by getting dropped.
ListeningButDontReportHalt,
// Dummy mode, for when we don't do anything at all.
Dummy,
}
impl ShutdownListenerMode {
fn is_dummy(&self) -> bool {
self == &ShutdownListenerMode::Dummy
}
fn should_signal_on_drop(&self) -> bool {
match self {
ShutdownListenerMode::Listening => true,
ShutdownListenerMode::ListeningButDontReportHalt | ShutdownListenerMode::Dummy => false,
}
}
fn set_should_not_signal_on_drop(&mut self) {
use ShutdownListenerMode::{Dummy, Listening, ListeningButDontReportHalt};
*self = match &self {
ListeningButDontReportHalt | Listening => ListeningButDontReportHalt,
Dummy => Dummy,
};
}
}
#[cfg(test)]
+43
View File
@@ -1,3 +1,5 @@
use crate::{shutdown::SentError, ShutdownNotifier};
#[cfg(unix)]
pub async fn wait_for_signal() {
use tokio::signal::unix::{signal, SignalKind};
@@ -25,3 +27,44 @@ pub async fn wait_for_signal() {
},
}
}
#[cfg(unix)]
pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Result<(), SentError> {
use tokio::signal::unix::{signal, SignalKind};
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel");
let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel");
tokio::select! {
_ = tokio::signal::ctrl_c() => {
log::info!("Received SIGINT");
Ok(())
},
_ = sigterm.recv() => {
log::info!("Received SIGTERM");
Ok(())
}
_ = sigquit.recv() => {
log::info!("Received SIGQUIT");
Ok(())
}
Some(msg) = shutdown.wait_for_error() => {
log::info!("Task error: {:?}", msg);
Err(msg)
}
}
}
#[cfg(not(unix))]
pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Result<(), SentError> {
tokio::select! {
_ = tokio::signal::ctrl_c() => {
log::info!("Received SIGINT");
Ok(())
},
Some(msg) = shutdown.wait_for_error() => {
log::info!("Task error: {:?}", msg);
Err(msg)
}
}
}
+33
View File
@@ -0,0 +1,33 @@
use crate::ShutdownListener;
use std::future::Future;
#[cfg(target_arch = "wasm32")]
pub(crate) fn spawn<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn spawn<F>(future: F)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
tokio::spawn(future);
}
pub fn spawn_with_report_error<F, T, E>(future: F, mut shutdown: ShutdownListener)
where
F: Future<Output = Result<T, E>> + Send + 'static,
T: 'static,
E: std::error::Error + Send + 'static,
{
let future_that_sends = async move {
if let Err(err) = future.await {
shutdown.send_we_stopped(Box::new(err));
}
};
spawn(future_that_sends);
}
+3 -7
View File
@@ -217,18 +217,14 @@ impl NymTopology {
#[must_use]
pub fn filter_system_version(&self, expected_version: &str) -> Self {
self.filter_node_versions(expected_version, expected_version)
self.filter_node_versions(expected_version)
}
#[must_use]
pub fn filter_node_versions(
&self,
expected_mix_version: &str,
expected_gateway_version: &str,
) -> Self {
pub fn filter_node_versions(&self, expected_mix_version: &str) -> Self {
NymTopology {
mixes: self.mixes.filter_by_version(expected_mix_version),
gateways: self.gateways.filter_by_version(expected_gateway_version),
gateways: self.gateways.clone(),
}
}
}
+10
View File
@@ -57,6 +57,10 @@ pub enum PendingEpochEventData {
mix_id: MixId,
proxy: Option<String>,
},
PledgeMore {
mix_id: MixId,
amount: DecCoin,
},
UnbondMixnode {
mix_id: MixId,
},
@@ -91,6 +95,12 @@ impl PendingEpochEventData {
mix_id,
proxy: proxy.map(|p| p.into_string()),
}),
MixnetContractPendingEpochEventKind::PledgeMore { mix_id, amount } => {
Ok(PendingEpochEventData::PledgeMore {
mix_id,
amount: reg.attempt_convert_to_display_dec_coin(amount.into())?,
})
}
MixnetContractPendingEpochEventKind::UnbondMixnode { mix_id } => {
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
}

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