Compare commits

..

8 Commits

Author SHA1 Message Date
Tommy Verrall 3a80b9bf59 update branch for latest changes 2023-04-25 12:53:45 +02:00
durch 237597da90 Remove dependency 2023-04-12 14:33:01 +02:00
durch 8000100735 Log to $HOME/.nym/logs 2023-04-12 13:53:43 +02:00
durch 68b8993e84 Merge branch 'feature/cpu-mixnode-build' of https://github.com/nymtech/nym into feature/cpu-mixnode-build 2023-04-12 09:21:58 +02:00
durch 3a897ed08e Log if measurement is ON or OFF 2023-04-12 09:21:51 +02:00
Tommy Verrall 18a24fc10d update cpu-cycles 2023-04-12 09:20:30 +02:00
Tommy Verrall dfcf812243 change the binary build 2023-04-12 09:19:11 +02:00
Tommy Verrall bdc91bb324 build only the mixnode binary with cpu-cycles 2023-04-06 10:08:38 +02:00
580 changed files with 9359 additions and 46251 deletions
-14
View File
@@ -1,14 +0,0 @@
---
name: 'Documentation'
about: Suggest a fix or enhancement to the documentation or developer portal content
title: "[DOCS]"
labels: documentation
assignees: mfahampshire
---
Is your issue either:
- [ ] a fix to existing documentation (e.g. fixing a broken link or incorrect command)
- [ ] an enhancement (e.g. adding a description for an undocumented feature)
Please briefly describe your issue:
@@ -4,34 +4,11 @@ on:
workflow_dispatch:
push:
paths:
- 'clients/**'
- 'common/**'
- 'contracts/**'
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
pull_request:
paths:
- 'clients/**'
- 'common/**'
- 'contracts/**'
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
env:
NETWORK: mainnet
@@ -65,11 +42,11 @@ jobs:
with:
toolchain: stable
- name: Build all binaries
- name: Build all mixnode binary
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release --all
args: --manifest-path mixnode/Cargo.toml --release --features cpucycles
- name: Install Rust stable
uses: actions-rs/toolchain@v1
@@ -79,34 +56,12 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.112.0 wasm-opt
- name: Build release contracts
run: make wasm
- name: Prepare build output
shell: bash
env:
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
run: |
cp target/release/nym-client $OUTPUT_DIR
cp target/release/nym-gateway $OUTPUT_DIR
cp target/release/nym-mixnode $OUTPUT_DIR
cp target/release/nym-socks5-client $OUTPUT_DIR
cp target/release/nym-api $OUTPUT_DIR
cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/nym-credential-client $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
- name: Deploy branch to CI www
continue-on-error: true
-4
View File
@@ -9,14 +9,12 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/lib/socks5-listener/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
pull_request:
paths:
- 'clients/**'
@@ -25,14 +23,12 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/lib/socks5-listener/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
jobs:
build:
-79
View File
@@ -1,79 +0,0 @@
name: Upload nyxd to CI
on:
workflow_dispatch:
jobs:
publish-nyxd:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Prepare build output directory
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
rm -rf ci-builds || true
mkdir -p $OUTPUT_DIR
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools git
continue-on-error: true
- name: Update env variables to include go
run: |
sudo rm -rf /usr/local/go
curl https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf -
cat <<'EOF' >>$HOME/.profile
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export GO111MODULE=on
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
EOF
source $HOME/.profile
- name: Verify Go is installed
run: go version
- name: Clone nyxd repo
run: |
git clone https://github.com/tommyv1987/nyxd
cd nyxd
git checkout release/v0.30.2
- name: Run nyxd
run: |
pwd
cd nyxd && make build
sleep 10
ls /home/runner/work/nym/nym/nyxd/build
- name: Prepare build output
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
cp /home/runner/work/nym/nym/nyxd/build/nyxd $OUTPUT_DIR
WASMVM_SO=$(ldd /home/runner/work/nym/nym/nyxd/build/nyxd | grep "libwasm*" | awk '{ print $3 }')
ls $WASMVM_SO
sleep 3
cp $(echo $WASMVM_SO) $OUTPUT_DIR
- name: Deploy nyxd to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "ci-builds/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
EXCLUDE: "/dist/, /node_modules/"
+1 -3
View File
@@ -8,7 +8,6 @@
.idea
target
.env
.env.dev
/.vscode/settings.json
validator/.vscode
sample-configs/validator-config.toml
@@ -42,5 +41,4 @@ storybook-static
envs/qwerty.env
.parcel-cache
**/.DS_Store
cpu-cycles/libcpucycles/build
foxyfox.env
cpu-cycles/libcpucycles/build
-40
View File
@@ -4,46 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.19] (2023-05-16)
- nym-name-service endpoint in nym-api ([#3403])
- Implement key storage for WASM client using IndexedDB (for browser) ([#3329])
- Initial version of nym-name-service contract providing name aliases for nym-addresses ([#3274])
- Update Cargo.lock ([#3410])
[#3403]: https://github.com/nymtech/nym/issues/3403
[#3329]: https://github.com/nymtech/nym/issues/3329
[#3274]: https://github.com/nymtech/nym/issues/3274
[#3410]: https://github.com/nymtech/nym/pull/3410
## [v1.1.18] (2023-05-09)
- Implement heartbeat messages between socks5 proxy and network requester ([#3215])
[#3215]: https://github.com/nymtech/nym/issues/3215
## [v1.1.17] (2023-05-02)
- Add service-provider-directory-contract support to nym-cli ([#3334])
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312])
- Add service-provider-directory support to validator-client ([#3296])
- Allow topology injection in our WASM client ('test my node' feature) ([#3270])
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
- Cache service provider contract in nym-api ([#3241])
- Feature/1 1 17 docs ([#3370])
- adding a test for SP endpoint ([#3367])
- Feature/store cipher ([#3350])
[#3334]: https://github.com/nymtech/nym/issues/3334
[#3312]: https://github.com/nymtech/nym/issues/3312
[#3296]: https://github.com/nymtech/nym/issues/3296
[#3270]: https://github.com/nymtech/nym/issues/3270
[#3242]: https://github.com/nymtech/nym/issues/3242
[#3241]: https://github.com/nymtech/nym/issues/3241
[#3370]: https://github.com/nymtech/nym/pull/3370
[#3367]: https://github.com/nymtech/nym/pull/3367
[#3350]: https://github.com/nymtech/nym/pull/3350
## [v1.1.16] (2023-04-25)
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
Generated
+542 -919
View File
File diff suppressed because it is too large Load Diff
+10 -20
View File
@@ -37,7 +37,6 @@ members = [
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/name-service",
"common/cosmwasm-smart-contracts/service-provider-directory",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/credential-storage",
@@ -61,14 +60,12 @@ members = [
"common/nymsphinx/forwarding",
"common/nymsphinx/framing",
"common/nymsphinx/params",
"common/nymsphinx/routing",
"common/nymsphinx/types",
"common/pemstore",
"common/socks5-client-core",
"common/socks5/proxy-helpers",
"common/socks5/requests",
"common/statistics",
"common/store-cipher",
"common/task",
"common/topology",
"common/types",
@@ -78,7 +75,6 @@ members = [
"gateway/gateway-requests",
"integrations/bity",
"mixnode",
"sdk/lib/socks5-listener",
"sdk/rust/nym-sdk",
"service-providers/common",
"service-providers/network-requester",
@@ -113,24 +109,19 @@ license = "Apache-2.0"
[workspace.dependencies]
async-trait = "0.1.64"
anyhow = "1.0.71"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
cosmwasm-derive = "=1.2.5"
cosmwasm-schema = "=1.2.5"
cosmwasm-std = "=1.2.5"
cosmwasm-storage = "=1.2.5"
cosmrs = "=0.8.0"
cw-utils = "=1.0.1"
cw-storage-plus = "=1.0.1"
cw2 = { version = "=1.0.1" }
cw3 = { version = "=1.0.1" }
cw3-fixed-multisig = { version = "=1.0.1" }
cw4 = { version = "=1.0.1" }
cw-controllers = { version = "=1.0.1" }
cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
cw-utils = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw2 = { version = "=0.13.4" }
cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
cw4 = { version = "=0.13.4" }
dotenvy = "0.15.6"
generic-array = "0.14.7"
k256 = "0.11"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
@@ -141,4 +132,3 @@ tap = "1.0.1"
thiserror = "1.0.38"
tokio = "1.24.1"
url = "2.2"
zeroize = "1.6.0"
+1 -3
View File
@@ -60,7 +60,7 @@ clippy: clippy-$(1) clippy-examples-$(1)
check: check-$(1)
cargo-test: test-$(1)
cargo-test-expensive: test-expensive-$(1)
build: build-$(1) build-examples-$(1)
build: build-$(1) build-$(1)-examples
build-release-all: build-release-$(1)
fmt: fmt-$(1)
@@ -99,7 +99,6 @@ CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
VESTING_CONTRACT=$(CONTRACTS_OUT_DIR)/vesting_contract.wasm
MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
wasm: wasm-build wasm-opt
@@ -110,7 +109,6 @@ wasm-opt:
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
wasm-opt --disable-sign-ext -Os $(NAME_SERVICE_CONTRACT) -o $(NAME_SERVICE_CONTRACT)
# -----------------------------------------------------------------------------
# Misc
+3 -3
View File
@@ -32,7 +32,7 @@ For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
You can chat with us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes place in the **#dev** channel. Node operators should be in the **#node-operators** channel.
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
### Rewards
@@ -46,7 +46,7 @@ Node, node operator and delegator rewards are determined according to the princi
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitiveness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
@@ -70,7 +70,7 @@ Operator of node `i` is credited with the following amount:
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
Delegate with stake `s` receives:
Delegate with stake `s` recieves:
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.19"
version = "1.1.16"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+79 -56
View File
@@ -7,54 +7,84 @@ use crate::websocket;
use futures::channel::mpsc;
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::non_wasm_helpers::create_bandwidth_controller;
use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
};
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_validator_client::nyxd::QueryNyxdClient;
use nym_validator_client::Client;
use std::error::Error;
use tokio::sync::watch::error::SendError;
pub use nym_client_core::client::key_manager::KeyManager;
use nym_credential_storage::persistent_storage::PersistentStorage;
pub use nym_sphinx::addressing::clients::Recipient;
pub use nym_sphinx::receiver::ReconstructedMessage;
use nym_validator_client::Client;
pub mod config;
type NativeClientBuilder<'a> = BaseClientBuilder<'a, Client<QueryNyxdClient>, OnDiskPersistent>;
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,
}
impl SocketClient {
pub fn new(config: Config) -> Self {
SocketClient { config }
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
SocketClient {
config,
key_manager,
}
}
pub fn new_with_keys(config: Config, key_manager: KeyManager) -> Self {
SocketClient {
config,
key_manager,
}
}
async fn create_bandwidth_controller(
config: &Config,
) -> BandwidthController<Client<QueryNyxdClient>, PersistentStorage> {
let storage = nym_credential_storage::initialise_persistent_storage(
config.get_base().get_database_path(),
let details = nym_network_defaults::NymNetworkDetails::new_from_env();
let mut client_config =
nym_validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
let nyxd_url = config
.get_base()
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_base()
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, api_url);
let client = nym_validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(
nym_credential_storage::initialise_persistent_storage(
config.get_base().get_database_path(),
)
.await,
client,
)
.await;
create_bandwidth_controller(config.get_base(), storage)
}
fn start_websocket_listener(
@@ -64,7 +94,6 @@ impl SocketClient {
client_state: ClientState,
self_address: &Recipient,
shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
info!("Starting websocket listener...");
@@ -90,7 +119,6 @@ impl SocketClient {
self_address,
shared_lane_queue_lengths,
reply_controller_sender,
Some(packet_type),
);
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
@@ -106,13 +134,11 @@ impl SocketClient {
res
}
fn key_store(&self) -> OnDiskKeys {
let pathfinder = ClientKeyPathfinder::new_from_config(self.config.get_base());
OnDiskKeys::new(pathfinder)
}
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
if !self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
// TODO: see if this could also be shared with socks5 client / nym-sdk maybe
async fn create_base_client_builder(&self) -> Result<NativeClientBuilder, ClientError> {
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None
@@ -120,29 +146,19 @@ impl SocketClient {
Some(Self::create_bandwidth_controller(&self.config).await)
};
let base_client = BaseClientBuilder::new_from_base_config(
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_store(),
self.key_manager,
bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend(
self.config.get_base().get_reply_surb_database_path(),
&self.config.get_debug_settings().reply_surbs,
Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(),
)
.await?,
);
Ok(base_client)
}
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
if !self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
let base_builder = self.create_base_client_builder().await?;
let packet_type = self.config.get_base().get_packet_type();
let mut started_client = base_builder.start_base(packet_type).await?;
let self_address = started_client.address;
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();
let client_state = started_client.client_state;
@@ -154,11 +170,10 @@ impl SocketClient {
client_state,
&self_address,
started_client.task_manager.subscribe(),
packet_type,
);
info!("Client startup finished!");
info!("The address of this client is: {self_address}");
info!("The address of this client is: {}", self_address);
Ok(started_client.task_manager)
}
@@ -168,10 +183,27 @@ impl SocketClient {
return Err(ClientError::InvalidSocketMode);
}
let base_builder = self.create_base_client_builder().await?;
let packet_type = self.config.get_base().get_packet_type();
let mut started_client = base_builder.start_base(packet_type).await?;
let address = started_client.address;
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None
} else {
Some(Self::create_bandwidth_controller(&self.config).await)
};
let base_client = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend(
Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(),
)
.await?,
);
let address = base_client.as_mix_recipient();
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();
@@ -192,7 +224,6 @@ impl SocketClient {
reconstructed_receiver,
address,
shutdown_notifier: started_client.task_manager,
packet_type,
})
}
}
@@ -206,7 +237,6 @@ pub struct DirectClient {
// we need to keep reference to this guy otherwise things will start dropping
shutdown_notifier: TaskManager,
packet_type: PacketType,
}
impl DirectClient {
@@ -227,7 +257,7 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.client_input
.input_sender
@@ -246,13 +276,7 @@ impl DirectClient {
reply_surbs: u32,
) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_anonymous(
recipient,
message,
reply_surbs,
lane,
Some(self.packet_type),
);
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
self.client_input
.input_sender
@@ -266,8 +290,7 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg =
InputMessage::new_reply(recipient_tag, message, lane, Some(self.packet_type));
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
self.client_input
.input_sender
+3 -6
View File
@@ -9,8 +9,8 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_config::NymConfig;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use serde::Serialize;
@@ -152,9 +152,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::from_config(config.get_base());
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>(
&key_store,
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
register_gateway,
user_chosen_gateway_id,
config.get_base(),
@@ -171,8 +169,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
print_saved_config(&config);
let address =
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
let init_results = InitResults::new(&config, &address);
println!("{}", args.output.format(&init_results));
+3 -10
View File
@@ -14,7 +14,6 @@ use nym_client_core::client::{
use nym_client_websocket_requests::{requests::ClientRequest, responses::ServerResponse};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::connections::{
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
@@ -42,7 +41,6 @@ pub(crate) struct HandlerBuilder {
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl HandlerBuilder {
@@ -53,7 +51,6 @@ impl HandlerBuilder {
self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
) -> Self {
Self {
msg_input,
@@ -62,7 +59,6 @@ impl HandlerBuilder {
self_full_address: *self_full_address,
lane_queue_lengths,
reply_controller_sender,
packet_type,
}
}
@@ -77,7 +73,6 @@ impl HandlerBuilder {
received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
reply_controller_sender: self.reply_controller_sender.clone(),
packet_type: self.packet_type,
}
}
}
@@ -91,7 +86,6 @@ pub(crate) struct Handler {
received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl Drop for Handler {
@@ -166,7 +160,7 @@ impl Handler {
});
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.msg_input
.send(input_msg)
.await
@@ -197,8 +191,7 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
self.msg_input
.send(input_msg)
.await
@@ -225,7 +218,7 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
self.msg_input
.send(input_msg)
.await
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.19"
version = "1.1.16"
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"
+3 -7
View File
@@ -8,8 +8,8 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_config::NymConfig;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::config::Config;
use nym_sphinx::addressing::clients::Recipient;
@@ -91,7 +91,6 @@ impl From<Init> for OverrideConfig {
no_cover: init_config.no_cover,
nyxd_urls: init_config.nyxd_urls,
enabled_credentials_mode: init_config.enabled_credentials_mode,
outfox: false,
}
}
}
@@ -159,9 +158,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::from_config(config.get_base());
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>(
&key_store,
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
register_gateway,
user_chosen_gateway_id,
config.get_base(),
@@ -180,8 +177,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
print_saved_config(&config);
let address =
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
let init_results = InitResults::new(&config, &address);
println!("{}", args.output.format(&init_results));
-8
View File
@@ -10,7 +10,6 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::{NymConfig, OptionalSet};
use nym_socks5_client_core::config::old_config_v1_1_13::OldConfigV1_1_13;
use nym_socks5_client_core::config::{BaseConfig, Config};
use nym_sphinx::params::PacketType;
use std::error::Error;
pub mod init;
@@ -65,7 +64,6 @@ pub(crate) struct OverrideConfig {
no_cover: bool,
nyxd_urls: Option<Vec<url::Url>>,
enabled_credentials_mode: Option<bool>,
outfox: bool,
}
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
@@ -82,15 +80,9 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
}
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
let packet_type = if args.outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
config
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(BaseConfig::with_packet_type, packet_type)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port)
.with_optional_custom_env_ext(
+1 -7
View File
@@ -9,7 +9,6 @@ use crate::{
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::{config::Config, NymClient};
@@ -68,9 +67,6 @@ pub(crate) struct Run {
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
#[clap(long, hide = true, action)]
outfox: bool,
}
impl From<Run> for OverrideConfig {
@@ -83,7 +79,6 @@ impl From<Run> for OverrideConfig {
no_cover: run_config.no_cover,
nyxd_urls: run_config.nyxd_urls,
enabled_credentials_mode: run_config.enabled_credentials_mode,
outfox: run_config.outfox,
}
}
}
@@ -143,6 +138,5 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
}
let storage = OnDiskPersistent::from_config(config.get_base()).await?;
NymClient::new(config, storage).run_forever().await
NymClient::new(config).run_forever().await
}
+12 -23
View File
@@ -34,14 +34,7 @@ import {
StakeSaturationResponse,
UnbondedMixnodeResponse,
VestingAccountInfo,
ContractState,
VestingAccountsCoinPaged,
VestingAccountsPaged,
DelegationTimes,
Delegations,
Period,
VestingAccountNode,
DelegationBlock,
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
} from '@nymproject/types';
import QueryClient from './query-client';
import SigningClient, { ISigningClient } from './signing-client';
@@ -214,7 +207,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: UnbondedMixnodeResponse[] = [];
const limit = 50;
let startAfter;
for (;;) {
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
this.mixnetContract,
@@ -237,7 +230,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: MixNodeBond[] = [];
const limit = 50;
let startAfter;
for (;;) {
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds(
this.mixnetContract,
@@ -259,7 +252,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: MixNodeDetails[] = [];
const limit = 50;
let startAfter;
for (;;) {
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
this.mixnetContract,
@@ -291,7 +284,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (;;) {
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
this.mixnetContract,
@@ -314,7 +307,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (;;) {
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
this.mixnetContract,
@@ -337,7 +330,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (;;) {
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged(
this.mixnetContract,
@@ -525,9 +518,11 @@ export default class ValidatorClient implements INymClient {
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
}
// VESTING
// VESTING
// TODO - MOVE TO A DIFFERENT FILE
public async getVestingAccountsPaged(): Promise<VestingAccountsPaged> {
return this.client.getVestingAccountsPaged(this.vestingContract);
}
@@ -613,9 +608,9 @@ export default class ValidatorClient implements INymClient {
}
public async getDelegation(address: string, mix_id: number): Promise<DelegationBlock> {
return this.client.getDelegation(this.vestingContract, address, mix_id);
return this.client.getDelegation(this.vestingContract, address, mix_id );
}
public async getTotalDelegationAmount(address: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
return this.client.getTotalDelegationAmount(this.vestingContract, address, mix_id, block_timestamp_sec);
}
@@ -623,10 +618,4 @@ export default class ValidatorClient implements INymClient {
public async getCurrentVestingPeriod(address: string): Promise<Period> {
return this.client.getCurrentVestingPeriod(this.vestingContract, address);
}
// SIMULATE
public async simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
return (this.client as SigningClient).simulateSend(signingAddress, from, to, amount);
}
}
+9 -48
View File
@@ -40,18 +40,9 @@ import {
RewardingParams,
UnbondedMixnodeResponse,
VestingAccountInfo,
ContractState,
VestingAccountsCoinPaged,
VestingAccountsPaged,
DelegationTimes,
Delegations,
Period,
VestingAccountNode,
DelegationBlock,
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
} from '@nymproject/types';
import NymApiQuerier from './nym-api-querier';
import { makeBankMsgSend } from './utils';
import { ISimulateClient } from './types/simulate';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
@@ -157,7 +148,7 @@ export interface INymSigning {
clientAddress: string;
}
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning, ISimulateClient {
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
@@ -520,11 +511,11 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
);
}
// vesting related
// vesting related
getVestingAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsPaged> {
return this.nyxdQuerier.getVestingAccountsPaged(vestingContractAddress);
}
};
getVestingAmountsAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsCoinPaged> {
return this.nyxdQuerier.getVestingAmountsAccountsPaged(vestingContractAddress);
@@ -578,10 +569,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getEndTime(vestingContractAddress, vestingAccountAddress);
}
getOriginalVestingDetails(
vestingContractAddress: string,
vestingAccountAddress: string,
): Promise<OriginalVestingResponse> {
getOriginalVestingDetails(vestingContractAddress: string, vestingAccountAddress: string): Promise<OriginalVestingResponse> {
return this.nyxdQuerier.getOriginalVestingDetails(vestingContractAddress, vestingAccountAddress);
}
@@ -601,11 +589,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getGateway(vestingContractAddress, address);
}
getDelegationTimes(
vestingContractAddress: string,
mix_id: number,
delegatorAddress: string,
): Promise<DelegationTimes> {
getDelegationTimes(vestingContractAddress: string, mix_id: number, delegatorAddress: string): Promise<DelegationTimes> {
return this.nyxdQuerier.getDelegationTimes(vestingContractAddress, mix_id, delegatorAddress);
}
@@ -613,38 +597,15 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getAllDelegations(vestingContractAddress);
}
getDelegation(
vestingContractAddress: string,
vestingAccountAddress: string,
mix_id: number,
): Promise<DelegationBlock> {
getDelegation(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number): Promise<DelegationBlock> {
return this.nyxdQuerier.getDelegation(vestingContractAddress, vestingAccountAddress, mix_id);
}
getTotalDelegationAmount(
vestingContractAddress: string,
vestingAccountAddress: string,
mix_id: number,
block_timestamp_sec: number,
): Promise<Coin> {
return this.nyxdQuerier.getTotalDelegationAmount(
vestingContractAddress,
vestingAccountAddress,
mix_id,
block_timestamp_sec,
);
getTotalDelegationAmount(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
return this.nyxdQuerier.getTotalDelegationAmount(vestingContractAddress, vestingAccountAddress, mix_id, block_timestamp_sec);
}
getCurrentVestingPeriod(vestingContractAddress: string, address: string): Promise<Period> {
return this.nyxdQuerier.getCurrentVestingPeriod(vestingContractAddress, address);
}
// simulation
// TODO consider adding multipling factor
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
const sendMsg = makeBankMsgSend(from, to, amount);
return this.simulate(signingAddress, [sendMsg], 'simulate send tx');
}
}
@@ -1,31 +0,0 @@
import expect from 'expect';
import ValidatorClient from '../..';
const dotenv = require('dotenv');
dotenv.config();
// TODO: implement for QA with .env for mnemonics
describe('Simualtions', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connect(
process.env.mnemonic || '',
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can simulate sending tokens', async () => {
const res = await client.simulateSend(client.address, client.address, client.address, [
{ amount: '400000', denom: 'unym' },
]);
expect(typeof res).toBe('number');
}).timeout(10000);
});
-5
View File
@@ -1,5 +0,0 @@
import { Coin } from '@cosmjs/proto-signing';
export interface ISimulateClient {
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]): Promise<number>;
}
+1
View File
@@ -1,5 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
-5175
View File
File diff suppressed because it is too large Load Diff
+4 -7
View File
@@ -17,7 +17,6 @@ default = ["console_error_panic_hook"]
offline-test = []
[dependencies]
async-trait = "0.1.68"
bs58 = "0.4.0"
futures = "0.3"
js-sys = "0.3"
@@ -25,13 +24,12 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
serde-wasm-bindgen = "0.5"
serde-wasm-bindgen = "0.4"
tokio = { version = "1.24.1", features = ["sync"] }
url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
thiserror = "1.0.40"
zeroize = "1.6.0"
wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"}
@@ -42,14 +40,13 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
nym-crypto = { path = "../../common/crypto" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
nym-topology = { path = "../../common/topology" }
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
nym-task = { path = "../../common/task" }
wasm-utils = { path = "../../common/wasm-utils", features = ["storage"], default-features = false }
# 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
@@ -67,7 +64,7 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
wasm-opt = true
[profile.release]
lto = true
+18 -68
View File
@@ -28,10 +28,7 @@ const {
set_panic_hook,
Config,
GatewayEndpointConfig,
ClientStorage,
current_network_topology,
make_key,
make_key2
} = wasm_bindgen;
let client = null;
@@ -106,25 +103,29 @@ function printAndDisplayTestResult(result) {
});
}
function dummyGatewayConfig() {
return new GatewayEndpointConfig(
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
'n1rqqw8km7a0rvf8lr6k8dsdqvvkyn2mglj7xxfm',
'ws://85.159.212.96:9000',
)
}
async function testWithTester() {
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const gatewayConfig = dummyGatewayConfig();
// A) construct with hardcoded topology
const topology = dummyTopology()
const nodeTester = await new NymNodeTester(topology, dummyGateway);
const nodeTester = await new NymNodeTester(gatewayConfig, topology);
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(topology, dummyGateway);
// const nodeTester = await new NymNodeTester(gatewayConfig, topology);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(validator, dummyGateway)
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
// const topology = dummyTopology()
// const nodeTester = await new NymNodeTester(topology);
// const nodeTester = await NymNodeTester.new_with_api(gatewayConfig, validator)
self.onmessage = async event => {
if (event.data && event.data.kind) {
@@ -142,7 +143,7 @@ async function testWithTester() {
}
async function testWithNymClient() {
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const gatewayConfig = dummyGatewayConfig();
const topology = dummyTopology()
let received = 0
@@ -164,7 +165,7 @@ async function testWithNymClient() {
console.log('Instantiating WASM client...');
let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, dummyGateway)
let clientBuilder = NymClientBuilder.new_tester(gatewayConfig, topology, onMessageHandler)
console.log('Web worker creating WASM client...');
let local_client = await clientBuilder.start_client();
console.log('WASM client running!');
@@ -222,10 +223,10 @@ async function normalNymClientUsage() {
debug.topology_refresh_rate_ms = BigInt(60000)
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const gatewayConfig = dummyGatewayConfig();
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
const config = new Config('my-awesome-wasm-client', validator, dummyGateway, debug);
const config = new Config('my-awesome-wasm-client', validator, gatewayConfig, debug);
const onMessageHandler = (message) => {
console.log(message);
@@ -269,57 +270,6 @@ async function normalNymClientUsage() {
}
}
};
}
async function messWithStorage() {
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'TestPacket': {
const { mixnodeIdentity } = event.data.args;
console.log("button clicked...", mixnodeIdentity);
let id1 = "one";
let id2 = "two";
console.log("making store1 NO-ENC");
let _storage1 = await ClientStorage.new_unencrypted(id1);
console.log("making store2 ENC")
let _storage2 = await new ClientStorage(id2, "my-secret-password");
//
//
//
// console.log("attempting to use store1 WITH PASSWORD")
// let _storage1_alt = await new ClientStorage(id1, "password");
//
//
//
// console.log("attempting to use store2 WITHOUT PASSWORD")
// let _storage2_alt = await ClientStorage.new_unencrypted(id2);
//
//
//
// console.log("attempting to use store2 with WRONG PASSWORD")
// let _storage2_bad = await new ClientStorage(id2, "bad-password")
//
// console.log("read1: ", await storage1.read());
// console.log("read2: ", await storage2.read());
//
// console.log("store1: ", await storage1.store("FOOMP"));
//
// console.log("read1: ", await storage1.read());
// console.log("read2: ", await storage2.read());
}
}
}
};
}
async function main() {
@@ -331,13 +281,13 @@ async function main() {
set_panic_hook();
// run test on simplified and dedicated tester:
// await testWithTester()
await testWithTester()
// hook-up the whole client for testing
// await testWithNymClient()
// 'Normal' client setup (to send 'normal' messages)
await normalNymClientUsage()
// await normalNymClientUsage()
}
// Let's get started!
+8 -22
View File
@@ -1,4 +1,4 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
@@ -9,10 +9,10 @@
use nym_client_core::config::{
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic,
DebugConfig as ConfigDebug, GatewayConnection as ConfigGatewayConnection,
ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology, Traffic as ConfigTraffic,
GatewayEndpointConfig, ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology,
Traffic as ConfigTraffic,
};
use nym_sphinx::params::{PacketSize, PacketType};
use nym_validator_client::client::IdentityKey;
use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -29,13 +29,10 @@ pub struct Config {
pub(crate) disabled_credentials_mode: bool,
/// Information regarding how the client should choose gateway.
/// If unspecified, the client will attempt to load the config from the storage.
pub(crate) gateway: Option<IdentityKey>,
/// Information regarding how the client should send data to gateway.
pub(crate) gateway_endpoint: GatewayEndpointConfig,
pub(crate) debug: ConfigDebug,
pub(crate) packet_type: PacketType,
}
#[wasm_bindgen]
@@ -44,18 +41,9 @@ impl Config {
pub fn new(
id: String,
validator_server: String,
packet_type: Option<String>,
gateway: Option<IdentityKey>,
gateway_endpoint: GatewayEndpointConfig,
debug: Option<Debug>,
) -> Self {
let packet_type = if let Some(packet_type) = packet_type {
match packet_type.as_str() {
"outfox" => PacketType::Outfox,
_ => PacketType::Mix,
}
} else {
PacketType::Mix
};
Config {
id,
nym_api_url: Some(
@@ -64,9 +52,8 @@ impl Config {
.expect("provided url was malformed"),
),
disabled_credentials_mode: true,
gateway,
gateway_endpoint,
debug: debug.map(Into::into).unwrap_or_default(),
packet_type,
}
}
}
@@ -109,7 +96,6 @@ impl From<Traffic> for ConfigTraffic {
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: use_extended_packet_size,
packet_type: None,
}
}
}
+15 -4
View File
@@ -13,7 +13,7 @@ use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, simple_js_error};
use wasm_utils::{console_log, js_error, simple_js_error};
#[wasm_bindgen]
pub struct NymClientTestRequest {
@@ -142,9 +142,20 @@ impl WasmTopologyExt for Arc<ClientState> {
return Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into());
};
let ext = WasmTestMessageExt::new(test_id);
let test_msgs = NodeTestMessage::mix_plaintexts(mix, num_test_packets, ext)
.map_err(WasmClientError::from)?;
let mut test_msgs = Vec::with_capacity(num_test_packets as usize);
for i in 1..=num_test_packets {
let msg = NodeTestMessage::new_mix(
mix,
i,
num_test_packets,
WasmTestMessageExt::new(test_id),
);
let serialized = match msg.as_bytes() {
Ok(bytes) => bytes,
Err(err) => return Err(js_error!("failed to serialize test message: {err}")),
};
test_msgs.push(serialized);
}
let mut updated = current_topology.clone();
updated.set_mixes_in_layer(mix.layer.into(), vec![mix.to_owned()]);
+37 -82
View File
@@ -4,14 +4,10 @@
use self::config::Config;
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt};
use crate::client::response_pusher::ResponsePusher;
use crate::constants::NODE_TESTER_CLIENT_ID;
use crate::error::WasmClientError;
use crate::helpers::{
choose_gateway, gateway_from_topology, parse_recipient, parse_sender_tag,
setup_reply_surb_storage_backend,
parse_recipient, parse_sender_tag, setup_new_key_manager, setup_reply_surb_storage_backend,
};
use crate::storage::traits::FullWasmClientStorage;
use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
@@ -19,18 +15,18 @@ use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle,
};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config::{CoverTraffic, DebugConfig, Topology, Traffic};
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use nym_client_core::config::{
CoverTraffic, DebugConfig, GatewayEndpointConfig, Topology, Traffic,
};
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use rand::{thread_rng, RngCore};
use rand::RngCore;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
@@ -53,7 +49,6 @@ pub struct NymClient {
// 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
_task_manager: TaskManager,
packet_type: Option<PacketType>,
}
#[wasm_bindgen]
@@ -61,7 +56,9 @@ pub struct NymClientBuilder {
config: Config,
custom_topology: Option<NymTopology>,
storage_passphrase: Option<String>,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
reply_surb_storage_backend: browser_backend::Backend,
on_message: js_sys::Function,
@@ -70,26 +67,21 @@ pub struct NymClientBuilder {
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
disabled_credentials: bool,
packet_type: Option<PacketType>,
}
#[wasm_bindgen]
impl NymClientBuilder {
#[wasm_bindgen(constructor)]
pub fn new(
config: Config,
on_message: js_sys::Function,
storage_passphrase: Option<String>,
) -> Self {
pub fn new(config: Config, on_message: js_sys::Function) -> Self {
//, key_manager: Option<KeyManager>) {
NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(config.debug.reply_surbs),
config,
custom_topology: None,
storage_passphrase,
key_manager: setup_new_key_manager(),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
packet_type: None,
}
}
@@ -98,21 +90,19 @@ impl NymClientBuilder {
// hardcoded topology
// NOTE: you most likely want to use `[NymNodeTester]` instead.
pub fn new_tester(
gateway_config: GatewayEndpointConfig,
topology: WasmNymTopology,
on_message: js_sys::Function,
gateway: Option<IdentityKey>,
) -> Self {
if let Some(gateway_id) = &gateway {
if !topology.ensure_contains_gateway_id(gateway_id) {
panic!("the specified topology does not contain the gateway used by the client")
}
if !topology.ensure_contains(&gateway_config) {
panic!("the specified topology does not contain the gateway used by the client")
}
let full_config = Config {
id: NODE_TESTER_CLIENT_ID.to_string(),
id: "ephemeral-id".to_string(),
nym_api_url: None,
disabled_credentials_mode: true,
gateway,
gateway_endpoint: gateway_config,
debug: DebugConfig {
traffic: Traffic {
disable_main_poisson_packet_distribution: true,
@@ -128,7 +118,6 @@ impl NymClientBuilder {
},
..Default::default()
},
packet_type: PacketType::Mix,
};
NymClientBuilder {
@@ -137,11 +126,12 @@ impl NymClientBuilder {
),
config: full_config,
custom_topology: Some(topology.into()),
// 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
key_manager: setup_new_key_manager(),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
storage_passphrase: None,
packet_type: None,
}
}
@@ -149,7 +139,7 @@ impl NymClientBuilder {
ResponsePusher::new(client_output, on_message).start()
}
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider + Send + Sync>> {
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider>> {
if let Some(hardcoded_topology) = self.custom_topology.take() {
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology)))
} else {
@@ -160,45 +150,22 @@ impl NymClientBuilder {
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
console_log!("Starting the wasm client");
let maybe_topology_provider = self.topology_provider();
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
let nym_api_endpoints = match &self.config.nym_api_url {
Some(endpoint) => vec![endpoint.clone()],
let nym_api_endpoints = match self.config.nym_api_url {
Some(endpoint) => vec![endpoint],
None => Vec::new(),
};
// TODO: this will have to be re-used for surbs. but this is a problem for another PR.
let client_store =
ClientStorage::new_async(&self.config.id, self.storage_passphrase.take()).await?;
// if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way
let gateway_endpoint = if let Some(topology) = &self.custom_topology {
gateway_from_topology(
&mut thread_rng(),
self.config.gateway.as_deref(),
topology,
&client_store,
)
.await?
} else {
choose_gateway(
&client_store,
self.config.gateway.clone(),
&nym_api_endpoints,
)
.await?
};
let maybe_topology_provider = self.topology_provider();
let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = BaseClientBuilder::new(
&gateway_endpoint,
let mut base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
client_store,
self.key_manager,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
@@ -208,9 +175,8 @@ impl NymClientBuilder {
base_builder = base_builder.with_topology_provider(topology_provider);
}
let packet_type = self.config.packet_type;
let mut started_client = base_builder.start_base(packet_type).await?;
let self_address = started_client.address.to_string();
let self_address = base_builder.as_mix_recipient().to_string();
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();
@@ -223,7 +189,6 @@ impl NymClientBuilder {
client_state: Arc::new(started_client.client_state),
_full_topology: None,
_task_manager: started_client.task_manager,
packet_type: self.packet_type,
})
}
@@ -237,25 +202,16 @@ impl NymClient {
async fn _new(
config: Config,
on_message: js_sys::Function,
storage_passphrase: Option<String>,
) -> Result<NymClient, WasmClientError> {
NymClientBuilder::new(config, on_message, storage_passphrase)
NymClientBuilder::new(config, on_message)
.start_client_async()
.await
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(
config: Config,
on_message: js_sys::Function,
storage_passphrase: Option<String>,
) -> Promise {
future_to_promise(async move {
Self::_new(config, on_message, storage_passphrase)
.await
.into_promise_result()
})
pub fn new(config: Config, on_message: js_sys::Function) -> Promise {
future_to_promise(async move { Self::_new(config, on_message).await.into_promise_result() })
}
pub fn self_address(&self) -> String {
@@ -299,7 +255,7 @@ impl NymClient {
let input_msgs = request
.test_msgs
.into_iter()
.map(|p| InputMessage::new_regular(recipient, p, lane, None))
.map(|p| InputMessage::new_regular(recipient, p, lane))
.collect();
self.client_input.send_messages(input_msgs)
@@ -319,7 +275,7 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.client_input.send_message(input_msg)
}
@@ -346,8 +302,7 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
self.client_input.send_message(input_msg)
}
@@ -365,7 +320,7 @@ impl NymClient {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane, self.packet_type);
let input_msg = InputMessage::new_reply(sender_tag, message, lane);
self.client_input.send_message(input_msg)
}
}
-5
View File
@@ -1,5 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
+1 -24
View File
@@ -1,17 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::errors::ClientStorageError;
use crate::topology::WasmTopologyError;
use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_node_tester_utils::error::NetworkTestingError;
use nym_sphinx::addressing::clients::RecipientFormattingError;
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use thiserror::Error;
use wasm_bindgen::JsValue;
@@ -46,18 +43,12 @@ pub enum WasmClientError {
source: ValidatorClientError,
},
#[error("The provided wasm topology was invalid: {source}")]
#[error("The provided topology was invalid: {source}")]
WasmTopologyError {
#[from]
source: WasmTopologyError,
},
#[error("The provided nym topology was invalid: {source}")]
TopologyError {
#[from]
source: NymTopologyError,
},
#[error("failed to test the node: {source}")]
NodeTestingFailure {
#[from]
@@ -76,9 +67,6 @@ pub enum WasmClientError {
#[error("Mixnode {mixnode_identity} is not present in the current network topology")]
NonExistentMixnode { mixnode_identity: String },
#[error("Gateway {gateway_identity} is not present in the current network topology")]
NonExistentGateway { gateway_identity: String },
#[error("{raw} is not a valid Nym network recipient: {source}")]
MalformedRecipient {
raw: String,
@@ -90,17 +78,6 @@ pub enum WasmClientError {
raw: String,
source: InvalidAnonymousSenderTagRepresentation,
},
#[error(transparent)]
StorageError {
#[from]
source: ClientStorageError,
},
#[error("this client has already registered with a gateway: {gateway_config:?}")]
AlreadyRegistered {
gateway_config: GatewayEndpointConfig,
},
}
impl WasmClientError {
+8 -91
View File
@@ -2,25 +2,27 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::NymTopology;
use nym_validator_client::client::{IdentityKey, IdentityKeyRef};
use nym_validator_client::NymApiClient;
use rand::{CryptoRng, Rng};
use rand::rngs::OsRng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, PromisableResult};
pub(crate) fn setup_new_key_manager() -> KeyManager {
let mut rng = OsRng;
console_log!("generated new set of keys");
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
pub(crate) fn setup_reply_surb_storage_backend(
@@ -78,88 +80,3 @@ pub fn current_network_topology(nym_api_url: String) -> Promise {
.into_promise_result()
})
}
pub(crate) async fn choose_gateway(
client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url],
) -> Result<GatewayEndpointConfig, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?;
console_log!("loaded: {:?}", existing_gateway_config);
if let Some(existing) = existing_gateway_config {
if let Some(provided) = &chosen_gateway {
if provided != &existing.gateway_id {
return Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
});
}
}
return Ok(existing);
};
// if NOTHING is specified nor available, choose gateway randomly.
let setup = GatewaySetup::new(None, chosen_gateway, None);
let config = setup.try_get_gateway_details(nym_apis).await?;
// perform registration + persist the new gateway info
// TODO: this is actually quite bad. we shouldn't be persisting gateway info here since we did not have persisted
// the shared key yet. this will only happen when we start the base client itself.
// but unfortunately, we can't do much more until we do a bit more refactoring.
client_store.store_gateway_config(&config).await?;
console_log!("stored: {:?}", config);
Ok(config)
}
pub(crate) async fn gateway_from_topology<R: Rng + CryptoRng>(
rng: &mut R,
explicit_gateway: Option<IdentityKeyRef<'_>>,
topology: &NymTopology,
client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?;
console_log!("loaded: {:?}", existing_gateway_config);
let new_gateway: GatewayEndpointConfig = if let Some(provided) = explicit_gateway {
if let Some(existing) = existing_gateway_config {
// we have stored gateway info and explicitly provided identity key
//
// check if they match, otherwise return an error
return if provided != existing.gateway_id {
Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
})
} else {
Ok(existing)
};
} else {
// we have explicitly provided identity key and didn't have any prior stored data
//
// try to grab details from the topology
let gateway_identity = identity::PublicKey::from_base58_string(provided)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
if let Some(gateway) = topology.get_gateway(&gateway_identity) {
gateway.clone().into()
} else {
return Err(WasmClientError::NonExistentGateway {
gateway_identity: gateway_identity.to_base58_string(),
});
}
}
} else if let Some(existing) = existing_gateway_config {
// we have stored data and didn't provide anything separately - use what's stored!
return Ok(existing);
} else {
// we don't have anything stored nor we have provided anything
//
// just grab random gateway from our topology
topology.random_gateway(rng)?.clone().into()
};
console_log!("storing: {:?}", new_gateway);
client_store.store_gateway_config(&new_gateway).await?;
Ok(new_gateway)
}
+1 -6
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use wasm_bindgen::prelude::*;
@@ -7,13 +7,10 @@ use wasm_bindgen::prelude::*;
mod client;
#[cfg(target_arch = "wasm32")]
pub mod encoded_payload_helper;
#[cfg(target_arch = "wasm32")]
pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod gateway_selector;
#[cfg(target_arch = "wasm32")]
pub mod storage;
#[cfg(target_arch = "wasm32")]
pub mod tester;
#[cfg(target_arch = "wasm32")]
pub mod topology;
@@ -23,8 +20,6 @@ pub mod validation;
#[cfg(target_arch = "wasm32")]
mod helpers;
mod constants;
#[wasm_bindgen]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
-25
View File
@@ -1,25 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::simple_js_error;
use wasm_utils::storage::error::StorageError;
#[derive(Debug, Error)]
pub enum ClientStorageError {
#[error("failed to use the storage: {source}")]
StorageError {
#[from]
source: StorageError,
},
#[error("{typ} cryptographic key is not available in storage")]
CryptoKeyNotInStorage { typ: String },
}
impl From<ClientStorageError> for JsValue {
fn from(value: ClientStorageError) -> Self {
simple_js_error(value.to_string())
}
}
-260
View File
@@ -1,260 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::errors::ClientStorageError;
use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
use wasm_utils::PromisableResult;
use zeroize::Zeroizing;
pub(crate) mod errors;
pub(crate) mod traits;
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
const STORAGE_VERSION: u32 = 1;
// v1 tables
mod v1 {
// stores
pub const KEYS_STORE: &str = "keys";
pub const CORE_STORE: &str = "core";
// keys
// TODO: to replace with FULL config
pub const GATEWAY_CONFIG: &str = "gateway_config";
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
// TODO: for those we could actually use the subtle crypto storage
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
}
#[wasm_bindgen]
pub struct ClientStorage {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) inner: Arc<WasmStorage>,
}
#[wasm_bindgen]
impl ClientStorage {
fn db_name(client_id: &str) -> String {
format!("{STORAGE_NAME_PREFIX}-{client_id}")
}
pub(crate) async fn new_async(
client_id: &str,
passphrase: Option<String>,
) -> Result<Self, ClientStorageError> {
let name = Self::db_name(client_id);
// make sure the password is zeroized when no longer used, especially if we error out.
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
if old_version < 1 {
// migrating to version 1
let db = evt.db();
db.create_object_store(v1::KEYS_STORE)?;
db.create_object_store(v1::CORE_STORE)?;
}
Ok(())
});
let inner = WasmStorage::new(
&name,
STORAGE_VERSION,
migrate_fn,
passphrase.as_ref().map(|p| p.as_bytes()),
)
.await?;
Ok(ClientStorage {
inner: Arc::new(inner),
name,
})
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(client_id: String, passphrase: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, Some(passphrase))
.await
.into_promise_result()
})
}
pub fn new_unencrypted(client_id: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, None)
.await
.into_promise_result()
})
}
pub(crate) async fn read_gateway_config(
&self,
) -> Result<Option<GatewayEndpointConfig>, ClientStorageError> {
self.inner
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_CONFIG))
.await
.map_err(Into::into)
}
async fn may_read_identity_keypair(
&self,
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_encryption_keypair(
&self,
) -> Result<Option<encryption::KeyPair>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, ClientStorageError> {
self.inner
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
.await
.map_err(Into::into)
}
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
)
.await
.map_err(Into::into)
}
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, ClientStorageError> {
self.may_read_identity_keypair()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
})
}
async fn must_read_encryption_keypair(
&self,
) -> Result<encryption::KeyPair, ClientStorageError> {
self.may_read_encryption_keypair()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
})
}
async fn must_read_ack_key(&self) -> Result<AckKey, ClientStorageError> {
self.may_read_ack_key()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_ACK_KEY.to_string(),
})
}
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, ClientStorageError> {
self.may_read_gateway_shared_key()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
})
}
async fn store_identity_keypair(
&self,
keypair: &identity::KeyPair,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_encryption_keypair(
&self,
keypair: &encryption::KeyPair,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_ack_key(&self, key: &AckKey) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_ACK_KEY),
key,
)
.await
.map_err(Into::into)
}
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
key,
)
.await
.map_err(Into::into)
}
pub(crate) async fn store_gateway_config(
&self,
gateway_endpoint: &GatewayEndpointConfig,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::CORE_STORE,
JsValue::from_str(v1::GATEWAY_CONFIG),
gateway_endpoint,
)
.await
.map_err(Into::into)
}
}
-76
View File
@@ -1,76 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::errors::ClientStorageError;
use crate::storage::ClientStorage;
use async_trait::async_trait;
use nym_client_core::client::base_client::storage::MixnetClientStorage;
use nym_client_core::client::key_manager::persistence::KeyStore;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use wasm_utils::console_log;
// temporary until other variants are properly implemented (probably it should get changed into `ClientStorage`
// implementing all traits and everything getting combined
pub struct FullWasmClientStorage {
key_store: ClientStorage,
reply_storage: browser_backend::Backend,
credential_storage: EphemeralCredentialStorage,
}
impl MixnetClientStorage for FullWasmClientStorage {
type KeyStore = ClientStorage;
type ReplyStore = browser_backend::Backend;
type CredentialStore = EphemeralCredentialStorage;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_storage, self.credential_storage)
}
fn key_store(&self) -> &Self::KeyStore {
&self.key_store
}
fn reply_store(&self) -> &Self::ReplyStore {
&self.reply_storage
}
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_storage
}
}
#[async_trait(?Send)]
impl KeyStore for ClientStorage {
type StorageError = ClientStorageError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
console_log!("attempting to load cryptographic keys...");
// all keys implement `ZeroizeOnDrop`, so if we return an Error, whatever was already loaded will be cleared
let identity_keypair = self.must_read_identity_keypair().await?;
let encryption_keypair = self.must_read_encryption_keypair().await?;
let ack_keypair = self.must_read_ack_key().await?;
let gateway_shared_key = self.must_read_gateway_shared_key().await?;
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
gateway_shared_key,
ack_keypair,
))
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
console_log!("attempting to store cryptographic keys...");
self.store_identity_keypair(&keys.identity_keypair())
.await?;
self.store_encryption_keypair(&keys.encryption_keypair())
.await?;
self.store_ack_key(&keys.ack_key()).await?;
self.store_gateway_shared_key(&keys.gateway_shared_key())
.await
}
}
@@ -1,10 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::tester::helpers::{NodeTestResult, WasmTestMessageExt};
use crate::tester::helpers::NodeTestResult;
use crate::tester::NodeTestMessage;
use futures::StreamExt;
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use std::collections::HashSet;
use std::time::Duration;
@@ -21,7 +21,7 @@ pub(crate) struct EphemeralTestReceiver<'a> {
duplicate_acks: u32,
timeout_duration: Duration,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
}
impl<'a> EphemeralTestReceiver<'a> {
@@ -38,7 +38,7 @@ impl<'a> EphemeralTestReceiver<'a> {
pub(crate) fn new(
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
timeout: Duration,
) -> Self {
EphemeralTestReceiver {
@@ -53,18 +53,23 @@ impl<'a> EphemeralTestReceiver<'a> {
}
}
fn on_next_received_packet(&mut self, packet: Option<Received<WasmTestMessageExt>>) -> bool {
fn on_next_received_packet(&mut self, packet: Option<Received>) -> bool {
let Some(received_packet) = packet else {
// can't do anything more...
console_error!("packet receiver has stopped processing results!");
return true
};
match received_packet {
Received::Message(msg) => {
if !self.received_valid_messages.insert(msg.msg_id) {
self.duplicate_packets += 1;
Received::Message(msg) => match NodeTestMessage::try_recover(msg) {
Ok(test_msg) => {
if !self.received_valid_messages.insert(test_msg.msg_id) {
self.duplicate_packets += 1;
}
}
}
Err(err) => {
console_warn!("failed to recover test message from received packet: {err}")
}
},
Received::Ack(frag_id) => {
if self.expected_acks.contains(&frag_id) {
if !self.received_valid_acks.insert(frag_id) {
+4 -5
View File
@@ -4,8 +4,7 @@
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
#![allow(clippy::drop_non_drop)]
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::sync::atomic::{AtomicBool, Ordering};
@@ -15,10 +14,10 @@ use wasm_bindgen::prelude::*;
use wasm_utils::{console_log, console_warn};
#[derive(Clone)]
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver<WasmTestMessageExt>>>);
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver>>);
impl ReceivedReceiverWrapper {
pub(super) fn new(inner: ReceivedReceiver<WasmTestMessageExt>) -> Self {
pub(super) fn new(inner: ReceivedReceiver) -> Self {
ReceivedReceiverWrapper(Arc::new(AsyncMutex::new(inner)))
}
@@ -37,7 +36,7 @@ impl ReceivedReceiverWrapper {
}
}
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver<WasmTestMessageExt>> {
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver> {
self.0.lock().await
}
}
+46 -58
View File
@@ -1,10 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::NODE_TESTER_ID;
use crate::error::WasmClientError;
use crate::helpers::{current_network_topology_async, gateway_from_topology};
use crate::storage::ClientStorage;
use crate::helpers::{current_network_topology_async, setup_new_key_manager};
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
@@ -14,9 +12,10 @@ use futures::channel::mpsc;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::key_manager::ManagedKeys;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::config::GatewayEndpointConfig;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
use nym_node_tester_utils::{NodeTester, TestMessage};
@@ -26,9 +25,7 @@ use nym_sphinx::params::PacketSize;
use nym_sphinx::preparer::PreparedFragment;
use nym_task::TaskManager;
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use rand::{CryptoRng, Rng};
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex as SyncMutex};
@@ -36,7 +33,7 @@ use std::time::Duration;
use tokio::sync::Mutex as AsyncMutex;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{check_promise_result, console_log, PromisableResult};
use wasm_utils::{check_promise_result, console_log, console_warn, PromisableResult};
mod ephemeral_receiver;
pub(crate) mod helpers;
@@ -73,19 +70,22 @@ pub struct NymNodeTester {
#[wasm_bindgen]
pub struct NymNodeTesterBuilder {
gateway: Option<IdentityKey>,
gateway_config: GatewayEndpointConfig,
base_topology: NymTopology,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
// unimplemented
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
}
fn address(keys: &ManagedKeys, gateway_identity: NodeIdentity) -> Recipient {
fn address(keys: &KeyManager, gateway_identity: NodeIdentity) -> Recipient {
Recipient::new(
*keys.identity_public_key(),
*keys.encryption_public_key(),
*keys.identity_keypair().public_key(),
*keys.encryption_keypair().public_key(),
gateway_identity,
)
}
@@ -94,64 +94,57 @@ fn address(keys: &ManagedKeys, gateway_identity: NodeIdentity) -> Recipient {
impl NymNodeTesterBuilder {
#[wasm_bindgen(constructor)]
pub fn new(
gateway_config: GatewayEndpointConfig,
base_topology: WasmNymTopology,
gateway: Option<IdentityKey>,
) -> NymNodeTesterBuilder {
NymNodeTesterBuilder {
gateway,
gateway_config,
base_topology: base_topology.into(),
key_manager: setup_new_key_manager(),
bandwidth_controller: None,
}
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
let topology = current_network_topology_async(api_url).await?;
Ok(NymNodeTesterBuilder::new(topology, gateway))
Ok(NymNodeTesterBuilder::new(gateway_config, topology))
}
pub fn new_with_api(gateway: Option<IdentityKey>, api_url: String) -> Promise {
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(api_url, gateway)
Self::_new_with_api(gateway_config, api_url)
.await
.into_promise_result()
})
}
async fn gateway_info<R: Rng + CryptoRng>(
&self,
rng: &mut R,
client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> {
gateway_from_topology(
rng,
self.gateway.as_deref(),
&self.base_topology,
client_store,
)
.await
}
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let mut rng = OsRng;
let rng = OsRng;
let task_manager = TaskManager::default();
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
let gateway_identity =
identity::PublicKey::from_base58_string(self.gateway_config.gateway_id)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
let gateway_endpoint = self.gateway_info(&mut rng, &client_store).await?;
let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?;
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &client_store).await;
// we **REALLY** need persistence...
let shared_key = if self.key_manager.is_gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
console_warn!("Gateway key not set - will derive a fresh one.");
None
};
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let mut gateway_client = GatewayClient::new(
gateway_endpoint.gateway_listener,
managed_keys.identity_keypair(),
self.gateway_config.gateway_listener,
self.key_manager.identity_keypair(),
gateway_identity,
managed_keys.gateway_shared_key(),
shared_key,
mixnet_message_sender,
ack_sender,
Duration::from_secs(10),
@@ -161,26 +154,26 @@ impl NymNodeTesterBuilder {
gateway_client.set_disabled_credentials_mode(true);
let shared_keys = gateway_client.authenticate_and_start().await?;
managed_keys
.deal_with_gateway_key(shared_keys, &client_store)
.await?;
// currently pointless but might as well do it for the future ¯\_(ツ)_/¯
self.key_manager.insert_gateway_shared_key(shared_keys);
// TODO: make those values configurable later
let tester = NodeTester::new(
rng,
self.base_topology,
Some(address(&managed_keys, gateway_identity)),
address(&self.key_manager, gateway_identity),
PacketSize::default(),
Duration::from_millis(5),
Duration::from_millis(5),
managed_keys.ack_key(),
self.key_manager.ack_key(),
);
let (processed_sender, processed_receiver) = mpsc::unbounded();
let mut receiver = SimpleMessageReceiver::new_sphinx_receiver(
managed_keys.encryption_keypair(),
managed_keys.ack_key(),
self.key_manager.encryption_keypair(),
self.key_manager.ack_key(),
mixnet_message_receiver,
ack_receiver,
processed_sender,
@@ -241,24 +234,24 @@ async fn test_mixnode(
impl NymNodeTester {
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(topology: WasmNymTopology, gateway: Option<IdentityKey>) -> Promise {
pub fn new(gateway_config: GatewayEndpointConfig, topology: WasmNymTopology) -> Promise {
console_log!("constructing node tester!");
NymNodeTesterBuilder::new(topology, gateway).setup_client()
NymNodeTesterBuilder::new(gateway_config, topology).setup_client()
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
NymNodeTesterBuilder::_new_with_api(api_url, gateway)
NymNodeTesterBuilder::_new_with_api(gateway_config, api_url)
.await?
._setup_client()
.await
}
pub fn new_with_api(api_url: String, gateway: Option<IdentityKey>) -> Promise {
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(api_url, gateway)
Self::_new_with_api(gateway_config, api_url)
.await
.into_promise_result()
})
@@ -273,12 +266,7 @@ impl NymNodeTester {
let test_ext = WasmTestMessageExt::new(test_nonce);
let mut tester_permit = self.tester.lock().expect("mutex got poisoned");
tester_permit
.existing_identity_mixnode_test_packets(
mixnode_identity,
test_ext,
num_test_packets,
None,
)
.existing_identity_mixnode_test_packets(mixnode_identity, test_ext, num_test_packets)
.map_err(Into::into)
}
+2 -7
View File
@@ -6,7 +6,7 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::{Layer, MixnodeConversionError};
use nym_topology::{gateway, mix, MixLayer, NymTopology};
use nym_validator_client::client::{IdentityKeyRef, MixId};
use nym_validator_client::client::MixId;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
@@ -82,16 +82,11 @@ impl WasmNymTopology {
})
}
#[allow(dead_code)]
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
}
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
self.inner
.gateways()
.iter()
.any(|g| g.identity_key.to_base58_string() == gateway_id)
.any(|g| g.identity_key.to_base58_string() == gateway_config.gateway_id)
}
pub fn print(&self) {
+1 -1
View File
@@ -130,7 +130,7 @@ impl AsyncFileWatcher {
Ok(event) => {
let now = Instant::now();
if self.should_propagate(&event, now) {
self.last_received.insert(event.kind, now);
self.last_received.insert(event.kind.clone(), now);
if let Err(_err) = self.event_sender.unbounded_send(event) {
log::error!("the file watcher receiver has been dropped!");
}
@@ -55,16 +55,11 @@ where
Ok(state)
}
pub async fn get_credential<C, St>(
pub async fn get_credential<C: DkgQueryClient + Send + Sync, St: Storage>(
state: &State,
client: &C,
storage: &St,
) -> Result<(), BandwidthControllerError>
where
C: DkgQueryClient + Send + Sync,
St: Storage,
<St as Storage>::StorageError: Send + Sync + 'static,
{
) -> Result<(), BandwidthControllerError> {
let epoch_id = client.get_current_epoch().await?.epoch_id;
let threshold = client
.get_current_epoch_threshold()
@@ -88,6 +83,7 @@ where
signature.to_bs58(),
epoch_id.to_string(),
)
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
.await?;
Ok(())
}
+1 -5
View File
@@ -16,11 +16,7 @@ pub enum BandwidthControllerError {
Nyxd(#[from] nym_validator_client::nyxd::error::NyxdError),
#[error("There was a credential storage error - {0}")]
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
// this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now
#[error(transparent)]
StorageError(#[from] StorageError),
CredentialStorageError(#[from] StorageError),
#[error("Coconut error - {0}")]
CoconutError(#[from] CoconutError),
+4 -15
View File
@@ -26,7 +26,7 @@ pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod wasm_mockups;
pub struct BandwidthController<C, St> {
pub struct BandwidthController<C, St: Storage> {
storage: St,
client: C,
}
@@ -45,13 +45,8 @@ impl<C, St: Storage> BandwidthController<C, St> {
) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let bandwidth_credential = self
.storage
.get_next_coconut_credential()
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?;
let voucher_info = bandwidth_credential.voucher_info.clone();
@@ -87,16 +82,10 @@ impl<C, St: Storage> BandwidthController<C, St> {
))
}
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
where
<St as Storage>::StorageError: Send + Sync + 'static,
{
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError> {
// JS: shouldn't we send some contract/validator/gateway message here to actually, you know,
// consume it?
self.storage
.consume_coconut_credential(id)
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
Ok(self.storage.consume_coconut_credential(id).await?)
}
}
-2
View File
@@ -23,7 +23,6 @@ url = { version ="2.2", features = ["serde"] }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { version = "1.24.1", features = ["macros"]}
time = "0.3.17"
zeroize = { workspace = true }
# internal
nym-bandwidth-controller = { path = "../bandwidth-controller" }
@@ -39,7 +38,6 @@ nym-topology = { path = "../topology" }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-task = { path = "../task" }
nym-credential-storage = { path = "../credential-storage" }
nym-network-defaults = { path = "../network-defaults" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client]
path = "../client-libs/validator-client"
@@ -1,6 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//
use crate::{client::replies::reply_storage, config::DebugConfig};
pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty {
+82 -114
View File
@@ -2,11 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use super::received_buffer::ReceivedBufferMessage;
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ManagedKeys;
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;
@@ -28,7 +26,6 @@ use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::{debug, info};
use nym_bandwidth_controller::BandwidthController;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
@@ -37,27 +34,25 @@ use nym_gateway_client::{
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use rand::rngs::OsRng;
use std::sync::Arc;
use tap::TapFallible;
use url::Url;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
use nym_credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub mod non_wasm_helpers;
pub mod helpers;
pub mod storage;
#[derive(Clone)]
pub struct ClientInput {
@@ -156,32 +151,31 @@ impl From<bool> for CredentialsToggle {
}
}
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
pub struct BaseClientBuilder<'a, B, C, St: Storage> {
// due to wasm limitations I had to split it like this : (
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
disabled_credentials: bool,
nym_api_endpoints: Vec<Url>,
reply_storage_backend: S::ReplyStore,
key_store: S::KeyStore,
reply_storage_backend: B,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
managed_keys: ManagedKeys,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
bandwidth_controller: Option<BandwidthController<C, St>>,
key_manager: KeyManager,
}
impl<'a, C, S> BaseClientBuilder<'a, C, S>
impl<'a, B, C, St> BaseClientBuilder<'a, B, C, St>
where
S: MixnetClientStorage + 'static,
C: DkgQueryClient + Send + Sync + 'static,
B: ReplyStorageBackend + Send + Sync + 'static,
C: DkgQueryClient + Sync + Send + 'static,
St: Storage + 'static,
{
// TODO: combine all storages
pub fn new_from_base_config<T>(
base_config: &'a Config<T>,
key_store: S::KeyStore,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
reply_storage_backend: S::ReplyStore,
) -> BaseClientBuilder<'a, C, S> {
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController<C, St>>,
reply_storage_backend: B,
) -> BaseClientBuilder<'a, B, C, St> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: base_config.get_debug_config(),
@@ -189,22 +183,20 @@ where
nym_api_endpoints: base_config.get_nym_api_endpoints(),
bandwidth_controller,
reply_storage_backend,
key_store,
managed_keys: ManagedKeys::Invalidated,
key_manager,
custom_topology_provider: None,
}
}
// TODO: combine all storages
pub fn new(
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_store: S::KeyStore,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
reply_storage_backend: S::ReplyStore,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController<C, St>>,
reply_storage_backend: B,
credentials_toggle: CredentialsToggle,
nym_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, C, S> {
) -> BaseClientBuilder<'a, B, C, St> {
BaseClientBuilder {
gateway_config,
debug_config,
@@ -213,25 +205,19 @@ where
reply_storage_backend,
custom_topology_provider: None,
bandwidth_controller,
key_store,
managed_keys: ManagedKeys::Invalidated,
key_manager,
}
}
pub fn with_topology_provider(
mut self,
provider: Box<dyn TopologyProvider + Send + Sync>,
) -> Self {
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
self.custom_topology_provider = Some(provider);
self
}
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded
fn as_mix_recipient(&self) -> Recipient {
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.managed_keys.identity_public_key(),
*self.managed_keys.encryption_public_key(),
*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(),
@@ -276,7 +262,6 @@ where
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: TaskClient,
packet_type: PacketType,
) {
info!("Starting real traffic stream...");
@@ -292,7 +277,7 @@ where
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown, packet_type);
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
@@ -322,11 +307,7 @@ where
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: TaskClient,
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
where
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
{
) -> Result<GatewayClient<C, St>, ClientCoreError> {
let gateway_id = self.gateway_config.gateway_id.clone();
if gateway_id.is_empty() {
return Err(ClientCoreError::GatewayIdUnknown);
@@ -339,11 +320,19 @@ where
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
let shared_key = if self.key_manager.is_gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
log::info!("Gateway key not set! Will proceed anyway.");
None
};
let mut gateway_client = GatewayClient::new(
gateway_address,
self.managed_keys.identity_keypair(),
self.key_manager.identity_keypair(),
gateway_identity,
self.managed_keys.gateway_shared_key(),
shared_key,
mixnet_message_sender,
ack_sender,
self.debug_config
@@ -355,27 +344,19 @@ where
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
let shared_key = gateway_client
gateway_client
.authenticate_and_start()
.await
.tap_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}")
})?;
self.managed_keys
.deal_with_gateway_key(shared_key, &self.key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
Ok(gateway_client)
}
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
custom_provider: Option<Box<dyn TopologyProvider>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider + Send + Sync> {
) -> Box<dyn TopologyProvider> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
Box::new(NymApiTopologyProvider::new(
@@ -388,7 +369,7 @@ where
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
topology_provider: Box<dyn TopologyProvider>,
topology_config: config::Topology,
topology_accessor: TopologyAccessor,
mut shutdown: TaskClient,
@@ -428,70 +409,60 @@ where
Ok(())
}
// controller for sending packets to mixnet (either real traffic or cover traffic)
// 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<C, S::CredentialStore>,
gateway_client: GatewayClient<C, St>,
shutdown: TaskClient,
) -> BatchMixMessageSender
where
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
{
) -> 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
}
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
async fn setup_persistent_reply_storage(
backend: S::ReplyStore,
backend: B,
shutdown: TaskClient,
) -> Result<CombinedReplyStorage, ClientCoreError>
where
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
S::ReplyStore: Send + Sync,
<B as ReplyStorageBackend>::StorageError: Sync + Send,
{
log::trace!("Setup persistent reply storage");
let persistent_storage = PersistentReplyStorage::new(backend);
let mem_store = persistent_storage
.load_state_from_backend()
.await
.map_err(|err| ClientCoreError::SurbStorageError {
source: Box::new(err),
})?;
let store_clone = mem_store.clone();
spawn_future(async move {
persistent_storage
.flush_on_shutdown(store_clone, shutdown)
if backend.is_active() {
log::trace!("Setup persistent reply storage");
let persistent_storage = PersistentReplyStorage::new(backend);
let mem_store = persistent_storage
.load_state_from_backend()
.await
});
.map_err(|err| ClientCoreError::SurbStorageError {
source: Box::new(err),
})?;
Ok(mem_store)
let store_clone = mem_store.clone();
spawn_future(async move {
persistent_storage
.flush_on_shutdown(store_clone, shutdown)
.await
});
Ok(mem_store)
} else {
log::trace!("Setup inactive reply storage");
Ok(backend
.get_inactive_storage()
.map_err(|err| ClientCoreError::SurbStorageError {
source: Box::new(err),
})?)
}
}
async fn initial_key_setup(&mut self) {
assert!(!self.managed_keys.is_valid());
let mut rng = OsRng;
self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await;
}
pub async fn start_base(
mut self,
packet_type: PacketType,
) -> Result<BaseClient, ClientCoreError>
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
where
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
S::ReplyStore: Send + Sync,
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
<B as ReplyStorageBackend>::StorageError: Sync + Send,
{
info!("Starting nym client");
self.initial_key_setup().await;
// 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
@@ -545,7 +516,7 @@ where
.await?;
Self::start_received_messages_buffer_controller(
self.managed_keys.encryption_keypair(),
self.key_manager.encryption_keypair(),
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
@@ -553,11 +524,11 @@ where
task_manager.subscribe(),
);
// The message_sender is the transmitter for any component generating sphinx packets
// 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 message_sender =
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
@@ -570,7 +541,7 @@ where
let controller_config = real_messages_control::Config::new(
self.debug_config,
self.managed_keys.ack_key(),
self.key_manager.ack_key(),
self_address,
);
@@ -579,14 +550,13 @@ where
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
message_sender.clone(),
sphinx_message_sender.clone(),
reply_storage,
reply_controller_sender.clone(),
reply_controller_receiver,
shared_lane_queue_lengths.clone(),
client_connection_rx,
task_manager.subscribe(),
packet_type,
);
if !self
@@ -596,10 +566,10 @@ where
{
Self::start_cover_traffic_stream(
self.debug_config,
self.managed_keys.ack_key(),
self.key_manager.ack_key(),
self_address,
shared_topology_accessor.clone(),
message_sender,
sphinx_message_sender,
task_manager.subscribe(),
);
}
@@ -608,7 +578,6 @@ where
debug!("The address of this client is: {self_address}");
Ok(BaseClient {
address: self_address,
client_input: ClientInputStatus::AwaitingProducer {
client_input: ClientInput {
connection_command_sender: client_connection_tx,
@@ -631,7 +600,6 @@ where
}
pub struct BaseClient {
pub address: Recipient,
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
pub client_state: ClientState,
@@ -1,25 +1,19 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// 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;
use crate::config::Config;
use crate::config::DebugConfig;
use crate::error::ClientCoreError;
use log::{error, info};
use nym_bandwidth_controller::BandwidthController;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_validator_client::nyxd::QueryNyxdClient;
use nym_validator_client::Client;
use std::path::Path;
use std::{fs, io};
use time::OffsetDateTime;
use url::Url;
async fn setup_fresh_backend<P: AsRef<Path>>(
db_path: P,
surb_config: &config::ReplySurbs,
debug_config: &DebugConfig,
) -> Result<fs_backend::Backend, ClientCoreError> {
info!("creating fresh surb database");
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
@@ -36,8 +30,12 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
// 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(
surb_config.minimum_reply_surb_storage_threshold,
surb_config.maximum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
);
storage_backend
.init_fresh(&mem_store)
@@ -49,13 +47,17 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
Ok(storage_backend)
}
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
// info!("creating inactive surb database");
// fs_backend::Backend::new_inactive(
// surb_config.minimum_reply_surb_storage_threshold,
// surb_config.maximum_reply_surb_storage_threshold,
// )
// }
fn setup_inactive_backend(debug_config: &DebugConfig) -> fs_backend::Backend {
info!("creating inactive surb database");
fs_backend::Backend::new_inactive(
debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
)
}
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
let db_path = db_path.as_ref();
@@ -79,56 +81,28 @@ fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
}
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
db_path: P,
surb_config: &config::ReplySurbs,
db_path: Option<P>,
debug_config: &DebugConfig,
) -> Result<fs_backend::Backend, ClientCoreError> {
// 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}. We're going to create a fresh database instead. This behaviour might change in the future");
if let Some(db_path) = db_path {
// 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}. We're going to create a fresh database instead. This behaviour might change in the future");
archive_corrupted_database(db_path)?;
setup_fresh_backend(db_path, surb_config).await
archive_corrupted_database(db_path)?;
setup_fresh_backend(db_path, debug_config).await
}
}
} else {
setup_fresh_backend(db_path, debug_config).await
}
} else {
setup_fresh_backend(db_path, surb_config).await
Ok(setup_inactive_backend(debug_config))
}
}
pub fn create_bandwidth_controller<T, St: CredentialStorage>(
config: &Config<T>,
storage: St,
) -> BandwidthController<Client<QueryNyxdClient>, St> {
let nyxd_url = config
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
create_bandwidth_controller_with_urls(nyxd_url, api_url, storage)
}
pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
nyxd_url: Url,
nym_api_url: Url,
storage: St,
) -> BandwidthController<Client<QueryNyxdClient>, St> {
let details = nym_network_defaults::NymNetworkDetails::new_from_env();
let mut client_config = nym_validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, nym_api_url);
let client = nym_validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(storage, client)
}
@@ -1,140 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO: combine those more closely. Perhaps into a single underlying store.
// Like for persistent, on-disk, storage, what's the point of having 3 different databases?
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
use crate::client::replies::reply_storage;
use crate::client::replies::reply_storage::ReplyStorageBackend;
use nym_credential_storage::ephemeral_storage::{
EphemeralStorage as EphemeralCredentialStorage, EphemeralStorage,
};
use nym_credential_storage::storage::Storage as CredentialStorage;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::non_wasm_helpers;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::key_manager::persistence::OnDiskKeys;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::config::{persistence::key_pathfinder::ClientKeyPathfinder, Config};
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::error::ClientCoreError;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::replies::reply_storage::fs_backend;
pub trait MixnetClientStorage {
type KeyStore: KeyStore;
type ReplyStore: ReplyStorageBackend;
type CredentialStore: CredentialStorage;
// this is a TERRIBLE name...
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore);
fn key_store(&self) -> &Self::KeyStore;
fn reply_store(&self) -> &Self::ReplyStore;
fn credential_store(&self) -> &Self::CredentialStore;
}
#[derive(Default)]
pub struct Ephemeral {
key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty,
credential_store: EphemeralStorage,
}
impl Ephemeral {
pub fn new() -> Self {
Default::default()
}
}
impl MixnetClientStorage for Ephemeral {
type KeyStore = InMemEphemeralKeys;
type ReplyStore = reply_storage::Empty;
type CredentialStore = EphemeralCredentialStorage;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_store, self.credential_store)
}
fn key_store(&self) -> &Self::KeyStore {
&self.key_store
}
fn reply_store(&self) -> &Self::ReplyStore {
&self.reply_store
}
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_store
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub struct OnDiskPersistent {
pub(crate) key_store: OnDiskKeys,
pub(crate) reply_store: fs_backend::Backend,
pub(crate) credential_store: PersistentCredentialStorage,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
impl OnDiskPersistent {
pub fn new(
key_store: OnDiskKeys,
reply_store: fs_backend::Backend,
credential_store: PersistentCredentialStorage,
) -> Self {
Self {
key_store,
reply_store,
credential_store,
}
}
pub async fn from_config<T>(config: &Config<T>) -> Result<Self, ClientCoreError> {
let pathfinder = ClientKeyPathfinder::new_from_config(config);
let key_store = OnDiskKeys::new(pathfinder);
let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend(
config.get_reply_surb_database_path(),
&config.get_debug_config().reply_surbs,
)
.await?;
let credential_store =
nym_credential_storage::initialise_persistent_storage(config.get_database_path()).await;
Ok(OnDiskPersistent {
key_store,
reply_store,
credential_store,
})
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
impl MixnetClientStorage for OnDiskPersistent {
type KeyStore = OnDiskKeys;
type ReplyStore = fs_backend::Backend;
type CredentialStore = PersistentCredentialStorage;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_store, self.credential_store)
}
fn key_store(&self) -> &Self::KeyStore {
&self.key_store
}
fn reply_store(&self) -> &Self::ReplyStore {
&self.reply_store
}
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_store
}
}
@@ -45,7 +45,7 @@ where
#[cfg(target_arch = "wasm32")]
next_delay: Pin<Box<wasm_timer::Delay>>,
/// Channel used for sending prepared nym packets to `MixTrafficController` that sends them
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -194,7 +194,6 @@ impl LoopCoverTrafficStream<OsRng> {
self.average_ack_delay,
self.cover_traffic.loop_cover_traffic_average_delay,
cover_traffic_packet_size,
nym_sphinx::params::PacketType::Mix,
)
.expect("Somehow failed to generate a loop cover message with a valid topology");
@@ -4,7 +4,6 @@
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
@@ -54,49 +53,18 @@ pub enum InputMessage {
data: Vec<u8>,
lane: TransmissionLane,
},
MessageWrapper {
message: Box<InputMessage>,
packet_type: PacketType,
},
}
impl InputMessage {
pub fn new_premade(
msgs: Vec<MixPacket>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Self {
let message = InputMessage::Premade { msgs, lane };
if packet_type == PacketType::Mix {
message
} else {
InputMessage::new_wrapper(message, packet_type)
}
pub fn new_premade(msgs: Vec<MixPacket>, lane: TransmissionLane) -> Self {
InputMessage::Premade { msgs, lane }
}
pub fn new_wrapper(message: InputMessage, packet_type: PacketType) -> Self {
InputMessage::MessageWrapper {
message: Box::new(message),
packet_type,
}
}
pub fn new_regular(
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Regular {
pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
InputMessage::Regular {
recipient,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -105,18 +73,12 @@ impl InputMessage {
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Anonymous {
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -124,17 +86,11 @@ impl InputMessage {
recipient_tag: AnonymousSenderTag,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Reply {
InputMessage::Reply {
recipient_tag,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -144,7 +100,6 @@ impl InputMessage {
| InputMessage::Anonymous { lane, .. }
| InputMessage::Reply { lane, .. }
| InputMessage::Premade { lane, .. } => lane,
InputMessage::MessageWrapper { message, .. } => message.lane(),
}
}
}
@@ -0,0 +1,227 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
use log::*;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore};
use std::io;
use std::sync::Arc;
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
// And I guess there will need to be some mechanism for a grace period when you can still
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// shared key derived with the gateway during "registration handshake"
gateway_shared_key: Option<Arc<SharedKeys>>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
// The expected flow of a KeyManager "lifetime" is as follows:
/*
1. ::new() is called during client-init
2. after gateway registration is completed [in init] ::insert_gateway_shared_key() is called
3. ::store_keys() is called before init finishes execution.
4. ::load_keys() is called at the beginning of each subsequent client-run
5. [not implemented] ::rotate_keys() is called periodically during client-run I presume?
*/
impl KeyManager {
/// Creates new instance of a [`KeyManager`]
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
KeyManager {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
gateway_shared_key: None,
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: SharedKeys,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
ack_key: Arc::new(ack_key),
}
}
/// Loads previously stored client keys from the disk.
fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
client_pathfinder.public_identity_key().to_owned(),
))?;
let encryption_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_encryption_key().to_owned(),
client_pathfinder.public_encryption_key().to_owned(),
))?;
let ack_key: AckKey = nym_pemstore::load_key(client_pathfinder.ack_key())?;
Ok(KeyManager {
identity_keypair: Arc::new(identity_keypair),
encryption_keypair: Arc::new(encryption_keypair),
gateway_shared_key: None,
ack_key: Arc::new(ack_key),
})
}
/// Loads previously stored keys from the disk. Fails if not all, including the shared gateway
/// key, is available.
pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
let gateway_shared_key: SharedKeys =
nym_pemstore::load_key(client_pathfinder.gateway_shared_key())?;
key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key));
Ok(key_manager)
}
/// Loads previously stored keys from the disk. Fails if client keys are not availabe, but the
/// shared gateway key is optional.
pub fn load_keys_but_gateway_is_optional(
client_pathfinder: &ClientKeyPathfinder,
) -> io::Result<Self> {
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
let gateway_shared_key: Result<SharedKeys, io::Error> =
nym_pemstore::load_key(client_pathfinder.gateway_shared_key());
// It's ok if the gateway key was not found
let gateway_shared_key = match gateway_shared_key {
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
Ok(key) => Ok(Some(key)),
}?;
key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new);
Ok(key_manager)
}
/// Stores all available keys on the disk.
// While perhaps there is no much point in storing the `AckKey` on the disk,
// it is done so for the consistency sake so that you wouldn't require an rng instance
// during `load_keys` to generate the said key.
pub fn store_keys(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
nym_pemstore::store_keypair(
self.identity_keypair.as_ref(),
&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
client_pathfinder.public_identity_key().to_owned(),
),
)?;
nym_pemstore::store_keypair(
self.encryption_keypair.as_ref(),
&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_encryption_key().to_owned(),
client_pathfinder.public_encryption_key().to_owned(),
),
)?;
nym_pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?;
match self.gateway_shared_key.as_ref() {
None => debug!("No gateway shared key available to store!"),
Some(gate_key) => {
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
}
Ok(())
}
pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
match self.gateway_shared_key.as_ref() {
None => {
return Err(io::Error::new(
io::ErrorKind::Other,
"trying to store a non-existing key",
))
}
Some(gate_key) => {
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
}
Ok(())
}
/// Overwrite the existing identity keypair
pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) {
self.identity_keypair = Arc::new(id_keypair);
}
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
/// Overwrite the existing encryption keypair
pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) {
self.encryption_keypair = Arc::new(enc_keypair);
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Overwrite the existing ack key
pub fn set_ack_key(&mut self, ack_key: AckKey) {
self.ack_key = Arc::new(ack_key);
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
self.gateway_shared_key = Some(gateway_shared_key)
}
/// Gets an atomically reference counted pointer to [`SharedKey`].
// since this function is not fully public, it is not expected to be used externally and
// hence it's up to us to ensure it's called in correct context
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
Arc::clone(
self.gateway_shared_key
.as_ref()
.expect("tried to unwrap empty gateway key!"),
)
}
pub fn is_gateway_key_set(&self) -> bool {
self.gateway_shared_key.is_some()
}
}
@@ -1,270 +0,0 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::persistence::KeyStore;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore};
use std::sync::Arc;
use zeroize::ZeroizeOnDrop;
pub mod persistence;
pub enum ManagedKeys {
Initial(KeyManagerBuilder),
FullyDerived(KeyManager),
// I really hate the existence of this variant, but I couldn't come up with a better way to handle
// `Self::deal_with_gateway_key` otherwise.
Invalidated,
}
impl From<KeyManagerBuilder> for ManagedKeys {
fn from(value: KeyManagerBuilder) -> Self {
ManagedKeys::Initial(value)
}
}
impl From<KeyManager> for ManagedKeys {
fn from(value: KeyManager) -> Self {
ManagedKeys::FullyDerived(value)
}
}
impl ManagedKeys {
pub fn is_valid(&self) -> bool {
!matches!(self, ManagedKeys::Invalidated)
}
pub async fn try_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
Ok(ManagedKeys::FullyDerived(
KeyManager::load_keys(key_store).await?,
))
}
pub fn generate_new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
}
pub async fn load_or_generate<R, S>(rng: &mut R, key_store: &S) -> Self
where
R: RngCore + CryptoRng,
S: KeyStore,
{
Self::try_load(key_store)
.await
.unwrap_or_else(|_| Self::generate_new(rng))
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ack_key(&self) -> Arc<AckKey> {
match self {
ManagedKeys::Initial(keys) => keys.ack_key(),
ManagedKeys::FullyDerived(keys) => keys.ack_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
match self {
ManagedKeys::Initial(_) => None,
ManagedKeys::FullyDerived(keys) => Some(keys.gateway_shared_key()),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn identity_public_key(&self) -> &identity::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_public_key(&self) -> &encryption::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub async fn deal_with_gateway_key<S: KeyStore>(
&mut self,
gateway_shared_key: Arc<SharedKeys>,
key_store: &S,
) -> Result<(), S::StorageError> {
let key_manager = match std::mem::replace(self, ManagedKeys::Invalidated) {
ManagedKeys::Initial(keys) => {
let key_manager = keys.insert_gateway_shared_key(gateway_shared_key);
key_manager.persist_keys(key_store).await?;
key_manager
}
ManagedKeys::FullyDerived(key_manager) => {
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|| key_manager.gateway_shared_key != gateway_shared_key
{
// this should NEVER happen thus panic here
panic!("derived fresh gateway shared key whilst already holding one!")
}
key_manager
}
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
};
*self = ManagedKeys::FullyDerived(key_manager);
Ok(())
}
}
// all of the keys really shouldn't be wrapped in `Arc`, but due to how the gateway client is currently
// constructed, changing that would require more work than what it's worth
pub struct KeyManagerBuilder {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
impl KeyManagerBuilder {
/// Creates new instance of a [`KeyManager`]
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
KeyManagerBuilder {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn insert_gateway_shared_key(self, gateway_shared_key: Arc<SharedKeys>) -> KeyManager {
KeyManager {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
gateway_shared_key,
ack_key: self.ack_key,
}
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
}
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
// And I guess there will need to be some mechanism for a grace period when you can still
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// shared key derived with the gateway during "registration handshake"
gateway_shared_key: Arc<SharedKeys>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
impl KeyManager {
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: SharedKeys,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: Arc::new(gateway_shared_key),
ack_key: Arc::new(ack_key),
}
}
pub async fn load_keys<S: KeyStore>(store: &S) -> Result<Self, S::StorageError> {
store.load_keys().await
}
pub async fn persist_keys<S: KeyStore>(&self, store: &S) -> Result<(), S::StorageError> {
store.store_keys(self).await
}
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
/// Gets an atomically reference counted pointer to [`SharedKey`].
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
Arc::clone(&self.gateway_shared_key)
}
pub fn remove_gateway_key(self) -> KeyManagerBuilder {
if Arc::strong_count(&self.gateway_shared_key) > 1 {
panic!("attempted to remove gateway key whilst still holding multiple references!")
}
KeyManagerBuilder {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
ack_key: self.ack_key,
}
}
}
fn _assert_keys_zeroize_on_drop() {
fn _assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
_assert_zeroize_on_drop::<identity::KeyPair>();
_assert_zeroize_on_drop::<encryption::KeyPair>();
_assert_zeroize_on_drop::<AckKey>();
_assert_zeroize_on_drop::<SharedKeys>();
}
@@ -1,220 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::KeyManager;
use async_trait::async_trait;
use std::error::Error;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::Config;
#[cfg(not(target_arch = "wasm32"))]
use nym_crypto::asymmetric::{encryption, identity};
#[cfg(not(target_arch = "wasm32"))]
use nym_gateway_requests::registration::handshake::SharedKeys;
#[cfg(not(target_arch = "wasm32"))]
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
#[cfg(not(target_arch = "wasm32"))]
use nym_pemstore::KeyPairPath;
#[cfg(not(target_arch = "wasm32"))]
use nym_sphinx::acknowledgements::AckKey;
// we have to define it as an async trait since wasm storage is async
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait KeyStore {
type StorageError: Error;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError>;
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError>;
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, thiserror::Error)]
pub enum OnDiskKeysError {
#[error("failed to load {keys} keys from {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
KeyPairLoadFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
err: std::io::Error,
},
#[error("failed to store {keys} keys to {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
KeyPairStoreFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
err: std::io::Error,
},
#[error("failed to load {key} key from {path}: {err}")]
KeyLoadFailure {
key: String,
path: String,
err: std::io::Error,
},
#[error("failed to store {key} key to {path}: {err}")]
KeyStoreFailure {
key: String,
path: String,
err: std::io::Error,
},
}
#[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskKeys {
pathfinder: ClientKeyPathfinder,
}
#[cfg(not(target_arch = "wasm32"))]
impl From<ClientKeyPathfinder> for OnDiskKeys {
fn from(pathfinder: ClientKeyPathfinder) -> Self {
OnDiskKeys { pathfinder }
}
}
#[cfg(not(target_arch = "wasm32"))]
impl OnDiskKeys {
pub fn new(pathfinder: ClientKeyPathfinder) -> Self {
OnDiskKeys { pathfinder }
}
pub fn from_config<T>(config: &Config<T>) -> Self {
OnDiskKeys::new(ClientKeyPathfinder::new_from_config(config))
}
fn load_key<T: PemStorableKey>(
&self,
path: &std::path::Path,
name: impl Into<String>,
) -> Result<T, OnDiskKeysError> {
nym_pemstore::load_key(path).map_err(|err| OnDiskKeysError::KeyLoadFailure {
key: name.into(),
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
err,
})
}
fn load_keypair<T: PemStorableKeyPair>(
&self,
paths: KeyPairPath,
name: impl Into<String>,
) -> Result<T, OnDiskKeysError> {
nym_pemstore::load_keypair(&paths).map_err(|err| OnDiskKeysError::KeyPairLoadFailure {
keys: name.into(),
paths,
err,
})
}
fn store_key<T: PemStorableKey>(
&self,
key: &T,
path: &std::path::Path,
name: impl Into<String>,
) -> Result<(), OnDiskKeysError> {
nym_pemstore::store_key(key, path).map_err(|err| OnDiskKeysError::KeyStoreFailure {
key: name.into(),
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
err,
})
}
fn store_keypair<T: PemStorableKeyPair>(
&self,
keys: &T,
paths: KeyPairPath,
name: impl Into<String>,
) -> Result<(), OnDiskKeysError> {
nym_pemstore::store_keypair(keys, &paths).map_err(|err| {
OnDiskKeysError::KeyPairStoreFailure {
keys: name.into(),
paths,
err,
}
})
}
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
let identity_paths = self.pathfinder.identity_key_pair_path();
let encryption_paths = self.pathfinder.encryption_key_pair_path();
let identity_keypair: identity::KeyPair =
self.load_keypair(identity_paths, "identity keys")?;
let encryption_keypair: encryption::KeyPair =
self.load_keypair(encryption_paths, "encryption keys")?;
let ack_key: AckKey = self.load_key(self.pathfinder.ack_key(), "ack key")?;
let gateway_shared_key: SharedKeys =
self.load_key(self.pathfinder.gateway_shared_key(), "gateway shared keys")?;
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
gateway_shared_key,
ack_key,
))
}
fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> {
let identity_paths = self.pathfinder.identity_key_pair_path();
let encryption_paths = self.pathfinder.encryption_key_pair_path();
self.store_keypair(
keys.identity_keypair.as_ref(),
identity_paths,
"identity keys",
)?;
self.store_keypair(
keys.encryption_keypair.as_ref(),
encryption_paths,
"encryption keys",
)?;
self.store_key(keys.ack_key.as_ref(), self.pathfinder.ack_key(), "ack key")?;
self.store_key(
keys.gateway_shared_key.as_ref(),
self.pathfinder.gateway_shared_key(),
"gateway shared keys",
)?;
Ok(())
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyStore for OnDiskKeys {
type StorageError = OnDiskKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
self.load_keys()
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
self.store_keys(keys)
}
}
#[derive(Default)]
pub struct InMemEphemeralKeys;
#[derive(Debug, thiserror::Error)]
#[error("ephemeral keys can't be loaded from storage")]
pub struct EphemeralKeysError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
Err(EphemeralKeysError)
}
async fn store_keys(&self, _keys: &KeyManager) -> Result<(), Self::StorageError> {
Ok(())
}
}
+3 -4
View File
@@ -35,20 +35,19 @@ impl<C, St> MixTrafficController<C, St>
where
C: DkgQueryClient + Sync + Send + 'static,
St: Storage + 'static,
<St as Storage>::StorageError: Send + Sync + 'static,
{
pub fn new(
gateway_client: GatewayClient<C, St>,
) -> (MixTrafficController<C, St>, BatchMixMessageSender) {
let (message_sender, message_receiver) =
let (sphinx_message_sender, sphinx_message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
(
MixTrafficController {
gateway_client,
mix_rx: message_receiver,
mix_rx: sphinx_message_receiver,
consecutive_gateway_failure_count: 0,
},
message_sender,
sphinx_message_sender,
)
}
@@ -71,7 +71,7 @@ impl AcknowledgementListener {
while !shutdown.is_shutdown() {
tokio::select! {
acks = self.ack_receiver.next() => match acks {
Some(acks) => {self.handle_ack_receiver_item(acks).await}
Some(acks) => self.handle_ack_receiver_item(acks).await,
None => {
log::trace!("AcknowledgementListener: Stopping since channel closed");
break;
@@ -9,7 +9,6 @@ use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
@@ -72,11 +71,10 @@ where
recipient: Recipient,
content: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane, packet_type)
.try_send_plain_message(recipient, content, lane)
.await
{
warn!("failed to send a plain message - {err}")
@@ -89,11 +87,10 @@ where
content: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
.await
{
warn!("failed to send a repliable message - {err}")
@@ -106,17 +103,14 @@ where
recipient,
data,
lane,
} => {
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
.await
}
} => self.handle_plain_message(recipient, data, lane).await,
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
self.handle_repliable_message(recipient, data, reply_surbs, lane)
.await
}
InputMessage::Reply {
@@ -127,40 +121,6 @@ where
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
InputMessage::MessageWrapper {
message,
packet_type,
} => match *message {
InputMessage::Regular {
recipient,
data,
lane,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
}
InputMessage::Reply {
recipient_tag,
data,
lane,
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => {
self.handle_premade_packets(msgs, lane).await
}
// MessageWrappers can't be nested
InputMessage::MessageWrapper { .. } => unimplemented!(),
},
};
}
@@ -16,7 +16,7 @@ use futures::channel::mpsc;
use log::*;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::params::PacketSize;
use nym_sphinx::{
acknowledgements::AckKey,
addressing::clients::Recipient,
@@ -249,11 +249,7 @@ where
}
}
pub(super) fn start_with_shutdown(
self,
shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
pub(super) fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
let mut retransmission_request_listener = self.retransmission_request_listener;
@@ -279,7 +275,7 @@ where
let shutdown_handle = shutdown.clone();
spawn_future(async move {
retransmission_request_listener
.run_with_shutdown(shutdown_handle, packet_type)
.run_with_shutdown(shutdown_handle)
.await;
debug!("The retransmission request listener has finished execution!");
});
@@ -11,9 +11,9 @@ use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::chunking::fragment::Fragment;
use nym_sphinx::preparer::PreparedFragment;
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
@@ -48,20 +48,17 @@ where
&mut self,
packet_recipient: Recipient,
chunk_data: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> {
debug!("retransmitting normal packet...");
// TODO: Figure out retransmission packet type signaling
self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data)
.await
}
async fn on_retransmission_request(
&mut self,
weak_timed_out_ack: Weak<PendingAcknowledgement>,
packet_type: PacketType,
) {
let timed_out_ack = match weak_timed_out_ack.upgrade() {
Some(timed_out_ack) => timed_out_ack,
@@ -88,7 +85,6 @@ where
self.prepare_normal_retransmission_chunk(
**recipient,
timed_out_ack.message_chunk.clone(),
packet_type,
)
.await
}
@@ -144,17 +140,13 @@ where
.await
}
pub(super) async fn run_with_shutdown(
&mut self,
mut shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
debug!("Started RetransmissionRequestListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
None => {
log::trace!("RetransmissionRequestListener: Stopping since channel closed");
break;
@@ -15,7 +15,7 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
use nym_sphinx::Delay;
use nym_task::connections::TransmissionLane;
@@ -27,7 +27,7 @@ use std::time::Duration;
use thiserror::Error;
// TODO: move that error elsewhere since it seems to be contaminating different files
#[derive(Debug, Error)]
#[derive(Debug, Clone, Error)]
pub enum PreparationError {
#[error(transparent)]
NymTopologyError(#[from] NymTopologyError),
@@ -417,10 +417,9 @@ where
recipient: Recipient,
message: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane)
.await
}
@@ -429,9 +428,7 @@ where
message: NymMessage,
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
debug!("Sending non-reply message with packet type {packet_type}");
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
debug_assert!(!matches!(message, NymMessage::Reply(_)));
@@ -439,11 +436,7 @@ where
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let packet_size = if packet_type == PacketType::Outfox {
PacketSize::OutfoxRegularPacket
} else {
self.optimal_packet_size(&message)
};
let packet_size = self.optimal_packet_size(&message);
debug!("Using {packet_size} packets for {message}");
let fragments = self
.message_preparer
@@ -460,7 +453,6 @@ where
topology,
&self.config.ack_key,
&recipient,
packet_type,
)?;
let real_message = RealMessage::new(
@@ -484,9 +476,7 @@ where
&mut self,
recipient: Recipient,
amount: u32,
packet_type: PacketType,
) -> Result<(), PreparationError> {
debug!("Sending additional reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) =
self.generate_reply_surbs_with_keys(amount as usize).await?;
@@ -500,7 +490,6 @@ where
message,
recipient,
TransmissionLane::AdditionalReplySurbs,
packet_type,
)
.await?;
@@ -516,9 +505,7 @@ where
message: Vec<u8>,
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Sending message with reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) = self
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
@@ -527,7 +514,7 @@ where
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane)
.await?;
log::trace!("storing {} reply keys", reply_keys.len());
@@ -540,21 +527,13 @@ where
&mut self,
recipient: Recipient,
chunk: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> {
debug!("Sending single chunk with packet type {packet_type}");
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
&recipient,
packet_type,
)
.prepare_chunk_for_sending(chunk, topology, &self.config.ack_key, &recipient)
.unwrap();
Ok(prepared_fragment)
@@ -590,7 +569,6 @@ where
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.unwrap()
})
@@ -610,13 +588,7 @@ where
let prepared_fragment = self
.message_preparer
.prepare_reply_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.prepare_reply_chunk_for_sending(chunk, topology, &self.config.ack_key, reply_surb)
.unwrap();
Ok(prepared_fragment)
@@ -26,7 +26,6 @@ use log::*;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
@@ -208,7 +207,7 @@ impl RealMessagesController<OsRng> {
}
}
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient, packet_type: PacketType) {
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
let mut reply_control = self.reply_control;
@@ -224,6 +223,6 @@ impl RealMessagesController<OsRng> {
debug!("The reply controller has finished execution!");
});
ack_control.start_with_shutdown(shutdown, packet_type);
ack_control.start_with_shutdown(shutdown);
}
}
@@ -92,7 +92,7 @@ where
// messages.
sending_delay_controller: SendingDelayController,
/// Channel used for sending prepared packets to `MixTrafficController` that sends them
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -136,7 +136,7 @@ impl From<PreparedFragment> for RealMessage {
impl RealMessage {
pub(crate) fn packet_size(&self) -> usize {
self.mix_packet.packet().len()
self.mix_packet.sphinx_packet().len()
}
pub(crate) fn new(mix_packet: MixPacket, fragment_id: Option<FragmentIdentifier>) -> Self {
@@ -247,7 +247,6 @@ where
self.config.average_ack_delay,
self.config.traffic.average_packet_delay,
cover_traffic_packet_size,
self.config.traffic.packet_type.unwrap_or_default(),
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
@@ -387,7 +386,7 @@ where
// On every iteration we get new messages from upstream. Given that these come bunched
// in `Vec`, this ensures that on average we will fetch messages faster than we can
// send, which is a condition for being able to multiplex packets from multiple
// send, which is a condition for being able to multiplex sphinx packets from multiple
// data streams.
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
@@ -512,11 +512,7 @@ where
let to_send = min(remaining, 100);
if let Err(err) = self
.message_handler
.try_send_additional_reply_surbs(
recipient,
to_send,
nym_sphinx::params::PacketType::Mix,
)
.try_send_additional_reply_surbs(recipient, to_send)
.await
{
warn!("failed to send additional surbs to {recipient} - {err}");
@@ -5,6 +5,8 @@ use crate::client::replies::reply_storage::backend::Empty;
use crate::client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend};
use async_trait::async_trait;
use std::path::PathBuf;
// well, right now we don't have the browser storage : (
// so we keep everything in memory
#[derive(Debug)]
@@ -27,6 +29,22 @@ impl Backend {
impl ReplyStorageBackend for Backend {
type StorageError = <Empty as ReplyStorageBackend>::StorageError;
async fn new(
debug_config: &crate::config::DebugConfig,
_db_path: Option<PathBuf>,
) -> Result<Self, Self::StorageError> {
Ok(Backend {
empty: Empty {
min_surb_threshold: debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
},
})
}
async fn flush_surb_storage(
&mut self,
storage: &CombinedReplyStorage,
@@ -41,4 +59,8 @@ impl ReplyStorageBackend for Backend {
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
self.empty.load_surb_storage().await
}
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
self.empty.get_inactive_storage()
}
}
@@ -1,6 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::base_client::non_wasm_helpers;
use crate::client::replies::reply_storage::backend::fs_backend::manager::StorageManager;
use crate::client::replies::reply_storage::backend::fs_backend::models::{
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
@@ -22,11 +23,49 @@ mod error;
mod manager;
mod models;
#[derive(Debug)]
enum StorageManagerState {
Storage(StorageManager),
Inactive(InactiveMetadata),
}
// When the storage backaed is initialized as inactive, it will still contain metadata parameters
// that will be needed when the in-mem storage is fetched for use.
#[derive(Debug)]
struct InactiveMetadata {
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
}
impl StorageManagerState {
fn get(&self) -> &StorageManager {
match self {
StorageManagerState::Storage(manager) => manager,
StorageManagerState::Inactive(_) => {
panic!("tried to get storage of an inactive backend")
}
}
}
fn get_mut(&mut self) -> &mut StorageManager {
match self {
StorageManagerState::Storage(manager) => manager,
StorageManagerState::Inactive(_) => {
panic!("tried to get storage of an inactive backend")
}
}
}
fn is_active(&self) -> bool {
matches!(self, StorageManagerState::Storage(_))
}
}
#[derive(Debug)]
pub struct Backend {
temporary_old_path: Option<PathBuf>,
database_path: PathBuf,
manager: StorageManager,
manager: StorageManagerState,
}
impl Backend {
@@ -46,12 +85,26 @@ impl Backend {
let backend = Backend {
temporary_old_path: None,
database_path: owned_path,
manager,
manager: StorageManagerState::Storage(manager),
};
Ok(backend)
}
pub fn new_inactive(
minimum_reply_surb_storage_threshold: usize,
maximum_reply_surb_storage_threshold: usize,
) -> Self {
Backend {
temporary_old_path: None,
database_path: PathBuf::new(),
manager: StorageManagerState::Inactive(InactiveMetadata {
minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold,
}),
}
}
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
let owned_path: PathBuf = database_path.as_ref().into();
if owned_path.file_name().is_none() {
@@ -123,13 +176,12 @@ impl Backend {
Ok(Backend {
temporary_old_path: None,
database_path: owned_path,
// manager: StorageManagerState::Storage(manager),
manager,
manager: StorageManagerState::Storage(manager),
})
}
async fn close_pool(&mut self) {
self.manager.connection_pool.close().await;
self.manager.get_mut().connection_pool.close().await;
}
async fn rotate(&mut self) -> Result<(), StorageError> {
@@ -148,8 +200,9 @@ impl Backend {
fs::rename(&self.database_path, &temp_old)
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
self.manager = StorageManager::init(&self.database_path, true).await?;
self.manager.create_status_table().await?;
self.manager =
StorageManagerState::Storage(StorageManager::init(&self.database_path, true).await?);
self.manager.get_mut().create_status_table().await?;
self.temporary_old_path = Some(temp_old);
Ok(())
@@ -166,26 +219,27 @@ impl Backend {
}
async fn start_storage_flush(&self) -> Result<(), StorageError> {
Ok(self.manager.set_flush_status(true).await?)
Ok(self.manager.get().set_flush_status(true).await?)
}
async fn end_storage_flush(&self) -> Result<(), StorageError> {
self.manager
.get()
.set_previous_flush_timestamp(OffsetDateTime::now_utc().unix_timestamp())
.await?;
Ok(self.manager.set_flush_status(false).await?)
Ok(self.manager.get().set_flush_status(false).await?)
}
async fn start_client_use(&self) -> Result<(), StorageError> {
Ok(self.manager.set_client_in_use_status(true).await?)
Ok(self.manager.get().set_client_in_use_status(true).await?)
}
async fn stop_client_use(&self) -> Result<(), StorageError> {
Ok(self.manager.set_client_in_use_status(false).await?)
Ok(self.manager.get().set_client_in_use_status(false).await?)
}
async fn get_stored_tags(&self) -> Result<UsedSenderTags, StorageError> {
let stored = self.manager.get_tags().await?;
let stored = self.manager.get().get_tags().await?;
// stop at the first instance of corruption. if even a single entry is malformed,
// something weird has happened and we can't trust the rest of the data
@@ -201,6 +255,7 @@ impl Backend {
for map_ref in tags.as_raw_iter() {
let (recipient, tag) = map_ref.pair();
self.manager
.get()
.insert_tag(StoredSenderTag::new(*recipient, *tag))
.await?;
}
@@ -208,7 +263,7 @@ impl Backend {
}
async fn get_stored_reply_keys(&self) -> Result<SentReplyKeys, StorageError> {
let stored = self.manager.get_reply_keys().await?;
let stored = self.manager.get().get_reply_keys().await?;
// stop at the first instance of corruption. if even a single entry is malformed,
// something weird has happened and we can't trust the rest of the data
@@ -224,6 +279,7 @@ impl Backend {
for map_ref in reply_keys.as_raw_iter() {
let (digest, key) = map_ref.pair();
self.manager
.get()
.insert_reply_key(StoredReplyKey::new(*digest, *key))
.await?;
}
@@ -231,7 +287,7 @@ impl Backend {
}
async fn get_stored_reply_surbs(&self) -> Result<ReceivedReplySurbsMap, StorageError> {
let surb_senders = self.manager.get_surb_senders().await?;
let surb_senders = self.manager.get().get_surb_senders().await?;
let metadata = self.get_reply_surb_storage_metadata().await?;
let mut received_surbs = Vec::with_capacity(surb_senders.len());
@@ -241,6 +297,7 @@ impl Backend {
sender.try_into()?;
let stored_surbs = self
.manager
.get()
.get_reply_surbs(sender_id)
.await?
.into_iter()
@@ -268,6 +325,7 @@ impl Backend {
let (tag, received_surbs) = map_ref.pair();
let sender_id = self
.manager
.get()
.insert_surb_sender(StoredSurbSender::new(
*tag,
received_surbs.surbs_last_received_at(),
@@ -276,6 +334,7 @@ impl Backend {
for reply_surb in received_surbs.surbs_ref() {
self.manager
.get()
.insert_reply_surb(StoredReplySurb::new(sender_id, reply_surb))
.await?
}
@@ -287,6 +346,7 @@ impl Backend {
&self,
) -> Result<ReplySurbStorageMetadata, StorageError> {
self.manager
.get()
.get_reply_surb_storage_metadata()
.await
.map_err(Into::into)
@@ -297,6 +357,7 @@ impl Backend {
reply_surbs: &ReceivedReplySurbsMap,
) -> Result<(), StorageError> {
self.manager
.get()
.insert_reply_surb_storage_metadata(ReplySurbStorageMetadata::new(
reply_surbs.min_surb_threshold(),
reply_surbs.max_surb_threshold(),
@@ -310,6 +371,24 @@ impl Backend {
impl ReplyStorageBackend for Backend {
type StorageError = error::StorageError;
async fn new(
debug_config: &crate::config::DebugConfig,
db_path: Option<PathBuf>,
) -> Result<Self, Self::StorageError> {
non_wasm_helpers::setup_fs_reply_surb_backend(db_path, debug_config)
.await
.map_err(|err| {
log::error!("Failed to create storage: {err}");
Self::StorageError::FailedToCreateStorage {
source: Box::new(err),
}
})
}
fn is_active(&self) -> bool {
self.manager.is_active()
}
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
self.start_client_use().await
}
@@ -347,6 +426,18 @@ impl ReplyStorageBackend for Backend {
Ok(CombinedReplyStorage::load(reply_keys, reply_surbs, tags))
}
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
match self.manager {
StorageManagerState::Storage(_) => {
panic!("tried to get inactive storage from an active storage backend")
}
StorageManagerState::Inactive(ref state) => Ok(CombinedReplyStorage::new(
state.minimum_reply_surb_storage_threshold,
state.maximum_reply_surb_storage_threshold,
)),
}
}
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
self.stop_client_use().await
}
@@ -3,7 +3,7 @@
use crate::client::replies::reply_storage::CombinedReplyStorage;
use async_trait::async_trait;
use std::error::Error;
use std::{error::Error, path::PathBuf};
use thiserror::Error;
#[cfg(target_arch = "wasm32")]
@@ -26,19 +26,24 @@ pub struct Empty {
pub max_surb_threshold: usize,
}
impl Default for Empty {
fn default() -> Self {
Empty {
min_surb_threshold: 20,
max_surb_threshold: 200,
}
}
}
#[async_trait]
impl ReplyStorageBackend for Empty {
type StorageError = UndefinedError;
async fn new(
debug_config: &crate::config::DebugConfig,
_db_path: Option<PathBuf>,
) -> Result<Self, Self::StorageError> {
Ok(Self {
min_surb_threshold: debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
})
}
async fn flush_surb_storage(
&mut self,
_storage: &CombinedReplyStorage,
@@ -59,12 +64,28 @@ impl ReplyStorageBackend for Empty {
self.max_surb_threshold,
))
}
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
Ok(CombinedReplyStorage::new(
self.min_surb_threshold,
self.max_surb_threshold,
))
}
}
#[async_trait]
pub trait ReplyStorageBackend: Sized {
type StorageError: Error + 'static;
async fn new(
debug_config: &crate::config::DebugConfig,
db_path: Option<PathBuf>,
) -> Result<Self, Self::StorageError>;
fn is_active(&self) -> bool {
true
}
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
Ok(())
}
@@ -82,6 +103,11 @@ pub trait ReplyStorageBackend: Sized {
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError>;
/// In the case the storage backend is initialized in an inactive state (persisting data is
/// disabled), we might still need to fetch the (in-mem) storage and the parameters it was
/// created with.
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError>;
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
Ok(())
}
@@ -26,7 +26,7 @@ impl TopologyRefresherConfig {
}
pub struct TopologyRefresher {
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
topology_provider: Box<dyn TopologyProvider>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
@@ -37,7 +37,7 @@ impl TopologyRefresher {
pub fn new(
cfg: TopologyRefresherConfig,
topology_accessor: TopologyAccessor,
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
topology_provider: Box<dyn TopologyProvider>,
) -> Self {
TopologyRefresher {
topology_provider,
@@ -47,7 +47,7 @@ impl TopologyRefresher {
}
}
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider + Send + Sync>) {
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider>) {
self.topology_provider = provider;
}
@@ -28,7 +28,7 @@ impl SizedData for RealMessage {
impl SizedData for Fragment {
fn data_size(&self) -> usize {
// note that raw `Fragment` is smaller than packet payload
// note that raw `Fragment` is smaller than sphinx packet payload
// as it doesn't include surb-ack or the [shared] key materials
self.payload_size()
}
+2 -67
View File
@@ -3,30 +3,19 @@
use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_crypto::asymmetric::identity;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::time::Duration;
use url::Url;
use crate::error::ClientCoreError;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod old_config_v1_1_13;
pub mod persistence;
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
pub const DEFAULT_GATEWAY_KEYS_FILENAME: &str = "gateway_shared.pem";
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
pub const DEFAULT_REPLY_STORE_FILENAME: &str = "persistent_reply_store.sqlite";
pub const DEFAULT_CREDENTIAL_STORE_FILENAME: &str = CRED_DB_FILE_NAME;
pub const MISSING_VALUE: &str = "MISSING VALUE";
// 'DEBUG'
@@ -118,37 +107,6 @@ impl<T> Config<T> {
self
}
#[must_use]
#[doc(hidden)]
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
pub fn reset_data_directory<P: AsRef<Path>>(mut self, dir: P) -> Self {
self.client.private_identity_key_file =
dir.as_ref().join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME);
self.client.public_identity_key_file =
dir.as_ref().join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME);
self.client.private_encryption_key_file =
dir.as_ref().join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME);
self.client.public_encryption_key_file =
dir.as_ref().join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME);
self.client.gateway_shared_key_file = dir.as_ref().join(DEFAULT_GATEWAY_KEYS_FILENAME);
self.client.ack_key_file = dir.as_ref().join(DEFAULT_ACK_KEY_FILENAME);
self.client.reply_surb_database_path = dir.as_ref().join(DEFAULT_REPLY_STORE_FILENAME);
self.client.database_path = dir.as_ref().join(DEFAULT_CREDENTIAL_STORE_FILENAME);
self
}
#[must_use]
#[doc(hidden)]
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
pub fn reset_nym_root_directory<P: AsRef<Path>>(mut self, dir: P) -> Self
where
T: NymConfig,
{
self.client.nym_root_directory = dir.as_ref().to_owned();
self
}
pub fn set_empty_fields_to_defaults(&mut self) -> bool
where
T: NymConfig,
@@ -259,11 +217,6 @@ impl<T> Config<T> {
self
}
pub fn with_packet_type(mut self, packet_type: PacketType) -> Self {
self.client.packet_type = Some(packet_type);
self
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.traffic.average_packet_delay = Duration::from_millis(10);
// basically don't really send cover messages
@@ -451,10 +404,6 @@ impl<T> Config<T> {
pub fn get_maximum_reply_key_age(&self) -> Duration {
self.debug.reply_surbs.maximum_reply_key_age
}
pub fn get_packet_type(&self) -> PacketType {
self.client.packet_type.unwrap_or(PacketType::Mix)
}
}
impl<T: NymConfig> Default for Config<T> {
@@ -497,14 +446,6 @@ impl GatewayEndpointConfig {
}
}
// separate block so it wouldn't be exported via wasm bindgen
impl GatewayEndpointConfig {
pub fn try_get_gateway_identity_key(&self) -> Result<identity::PublicKey, ClientCoreError> {
identity::PublicKey::from_base58_string(&self.gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
}
}
impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
fn from(node: nym_topology::gateway::Node) -> GatewayEndpointConfig {
let gateway_listener = node.clients_address();
@@ -577,8 +518,6 @@ pub struct Client<T> {
#[serde(skip)]
pub super_struct: PhantomData<T>,
pub packet_type: Option<PacketType>,
}
impl<T: NymConfig> Default for Client<T> {
@@ -617,7 +556,6 @@ impl<T: NymConfig> Default for Client<T> {
reply_surb_database_path: Default::default(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
packet_type: Default::default(),
}
}
}
@@ -689,8 +627,6 @@ pub struct Traffic {
/// Note that its use decreases overall anonymity.
/// Do not set it it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: Option<PacketType>,
}
impl Traffic {
@@ -714,7 +650,6 @@ impl Default for Traffic {
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: None,
}
}
}
@@ -125,7 +125,6 @@ impl From<OldDebugConfigV1_1_13> for DebugConfig {
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: value.use_extended_packet_size.map(Into::into),
packet_type: None,
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
@@ -211,8 +210,8 @@ impl<T, U> From<OldConfigV1_1_13<T>> for Config<U> {
database_path: value.client.database_path,
reply_surb_database_path: value.client.reply_surb_database_path,
nym_root_directory: value.client.nym_root_directory,
super_struct: PhantomData,
packet_type: Some(nym_sphinx::params::PacketType::Mix),
},
logging: value.logging,
debug: value.debug.into(),
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use nym_config::NymConfig;
use std::path::{Path, PathBuf};
#[derive(Debug)]
@@ -28,7 +29,7 @@ impl ClientKeyPathfinder {
}
}
pub fn new_from_config<T>(config: &Config<T>) -> Self {
pub fn new_from_config<T: NymConfig>(config: &Config<T>) -> Self {
ClientKeyPathfinder {
identity_private_key: config.get_private_identity_key_file(),
identity_public_key: config.get_public_identity_key_file(),
@@ -39,20 +40,6 @@ impl ClientKeyPathfinder {
}
}
pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_identity_key().to_path_buf(),
self.public_identity_key().to_path_buf(),
)
}
pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_encryption_key().to_path_buf(),
self.public_encryption_key().to_path_buf(),
)
}
pub fn any_file_exists(&self) -> bool {
matches!(self.identity_public_key.try_exists(), Ok(true))
|| matches!(self.identity_private_key.try_exists(), Ok(true))
+2 -13
View File
@@ -6,7 +6,6 @@ use nym_gateway_client::error::GatewayClientError;
use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use std::error::Error;
#[derive(thiserror::Error, Debug)]
pub enum ClientCoreError {
@@ -42,18 +41,13 @@ pub enum ClientCoreError {
#[error("experienced a failure with our reply surb persistent storage: {source}")]
SurbStorageError {
source: Box<dyn Error + Send + Sync>,
},
#[error("experienced a failure with our cryptographic keys persistent storage: {source}")]
KeyStoreError {
source: Box<dyn Error + Send + Sync>,
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("The gateway id is invalid - {0}")]
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
#[error("The identity of the gateway is unknown - did you run init?")]
#[error("The identity of the gateway is unknwown - did you run init?")]
GatewayIdUnknown,
#[error("The owner of the gateway is unknown - did you run init?")]
@@ -92,11 +86,6 @@ pub enum ClientCoreError {
#[error("Unexpected exit")]
UnexpectedExit,
#[error(
"This operation would have resulted in clients keys being overwritten without permission"
)]
ForbiddenKeyOverwrite,
}
/// Set of messages that the client can send to listeners via the task manager
+53 -13
View File
@@ -1,15 +1,19 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::GatewayEndpointConfig;
use crate::error::ClientCoreError;
use crate::{
client::key_manager::KeyManager,
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError,
};
use futures::{SinkExt, StreamExt};
use log::{debug, info, trace, warn};
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, Rng};
use rand::{seq::SliceRandom, thread_rng, Rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use tungstenite::Message;
@@ -25,8 +29,10 @@ use tokio::time::Instant;
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
use nym_credential_storage::storage::Storage;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DirectSigningNyxdClient;
@@ -55,9 +61,9 @@ impl GatewayWithLatency {
}
}
pub(super) async fn current_gateways<R: Rng>(
async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
nym_apis: Vec<Url>,
) -> Result<Vec<gateway::Node>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
@@ -154,7 +160,7 @@ async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, C
Ok(GatewayWithLatency::new(gateway, avg))
}
pub(super) async fn choose_gateway_by_latency<R: Rng>(
async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
@@ -187,7 +193,7 @@ pub(super) async fn choose_gateway_by_latency<R: Rng>(
Ok(chosen.gateway.clone())
}
pub(super) fn uniformly_random_gateway<R: Rng>(
fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
@@ -197,14 +203,35 @@ pub(super) fn uniformly_random_gateway<R: Rng>(
.cloned()
}
pub(super) async fn register_with_gateway(
gateway: &GatewayEndpointConfig,
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<gateway::Node, ClientCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, validator_servers).await?;
// if we set an explicit gateway, use that one and nothing else
if let Some(explicitly_chosen) = chosen_gateway_id {
gateways
.into_iter()
.find(|gateway| gateway.identity_key == explicitly_chosen)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string()))
} else if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else {
uniformly_random_gateway(&mut rng, gateways)
}
}
pub(super) async fn register_with_gateway<St: Storage>(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
) -> Result<Arc<SharedKeys>, ClientCoreError> {
let timeout = Duration::from_millis(1500);
let mut gateway_client: GatewayClient<DirectSigningNyxdClient, _> = GatewayClient::new_init(
gateway.gateway_listener.clone(),
gateway.try_get_gateway_identity_key()?,
let mut gateway_client: GatewayClient<DirectSigningNyxdClient, St> = GatewayClient::new_init(
gateway.clients_address(),
gateway.identity_key,
our_identity.clone(),
timeout,
);
@@ -218,3 +245,16 @@ pub(super) async fn register_with_gateway(
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
Ok(shared_keys)
}
pub(super) fn store_keys<T>(
key_manager: &KeyManager,
config: &Config<T>,
) -> Result<(), ClientCoreError>
where
T: NymConfig,
{
let pathfinder = ClientKeyPathfinder::new_from_config(config);
Ok(key_manager
.store_keys(&pathfinder)
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
}
+65 -174
View File
@@ -1,12 +1,21 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! Collection of initialization steps used by client implementations
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::{KeyManager, ManagedKeys};
use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway};
use std::fmt::Display;
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
use rand::rngs::OsRng;
use serde::Serialize;
use tap::TapFallible;
use nym_config::NymConfig;
use nym_credential_storage::storage::Storage;
use nym_crypto::asymmetric::{encryption, identity};
use url::Url;
use crate::client::key_manager::KeyManager;
use crate::{
config::{
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
@@ -14,102 +23,9 @@ use crate::{
},
error::ClientCoreError,
};
use nym_config::NymConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use serde::Serialize;
use std::fmt::{Debug, Display};
use tap::TapFallible;
use url::Url;
mod helpers;
#[derive(Clone)]
pub enum GatewaySetup {
/// Specifies usage of a new, random, gateway.
New {
/// Should the new gateway be selected based on latency.
by_latency: bool,
},
Specified {
/// Identity key of the gateway we want to try to use.
gateway_identity: IdentityKey,
},
Predefined {
/// Full gateway configuration
config: GatewayEndpointConfig,
},
}
impl From<GatewayEndpointConfig> for GatewaySetup {
fn from(config: GatewayEndpointConfig) -> Self {
GatewaySetup::Predefined { config }
}
}
impl From<IdentityKey> for GatewaySetup {
fn from(gateway_identity: IdentityKey) -> Self {
GatewaySetup::Specified { gateway_identity }
}
}
impl Default for GatewaySetup {
fn default() -> Self {
GatewaySetup::New { by_latency: false }
}
}
impl GatewaySetup {
pub fn new(
full_config: Option<GatewayEndpointConfig>,
gateway_identity: Option<IdentityKey>,
latency_based_selection: Option<bool>,
) -> Self {
if let Some(config) = full_config {
GatewaySetup::Predefined { config }
} else if let Some(gateway_identity) = gateway_identity {
GatewaySetup::Specified { gateway_identity }
} else {
GatewaySetup::New {
by_latency: latency_based_selection.unwrap_or_default(),
}
}
}
pub async fn try_get_gateway_details(
self,
validator_servers: &[Url],
) -> Result<GatewayEndpointConfig, ClientCoreError> {
match self {
GatewaySetup::New { by_latency } => {
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else {
uniformly_random_gateway(&mut rng, gateways)
}
}
.map(Into::into),
GatewaySetup::Specified { gateway_identity } => {
let user_gateway = identity::PublicKey::from_base58_string(&gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
gateways
.into_iter()
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
}
.map(Into::into),
GatewaySetup::Predefined { config } => Ok(config),
}
}
}
/// Struct describing the results of the client initialization procedure.
#[derive(Debug, Serialize)]
pub struct InitResults {
@@ -148,55 +64,34 @@ impl Display for InitResults {
}
}
/// Recovers the already present gateway information or attempts to register with new gateway
/// and stores the newly obtained key
pub async fn get_registered_gateway<S>(
validator_servers: Vec<Url>,
key_store: &S::KeyStore,
setup: GatewaySetup,
overwrite_keys: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
where
S: MixnetClientStorage,
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
{
/// Create a new set of client keys.
pub fn new_client_keys() -> KeyManager {
let mut rng = OsRng;
KeyManager::new(&mut rng)
}
// try load keys
let mut managed_keys = match ManagedKeys::try_load(key_store).await {
Ok(_) => {
// if we loaded something and we don't have full gateway details, check if we can overwrite the data
if let GatewaySetup::Predefined { config } = setup {
// we already have defined gateway details AND a shared key, so nothing more for us to do
return Ok(config);
} else if overwrite_keys {
ManagedKeys::generate_new(&mut rng)
} else {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
}
Err(_) => ManagedKeys::generate_new(&mut rng),
};
/// Authenticate and register with a gateway.
/// Either pick one at random by querying the available gateways from the nym-api, or use the
/// chosen one if it's among the available ones.
/// The shared key is added to the supplied `KeyManager` and the endpoint details are returned.
pub async fn register_with_gateway<St: Storage>(
key_manager: &mut KeyManager,
nym_api_endpoints: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError> {
// Get the gateway details of the gateway we will use
let gateway =
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
log::debug!("Querying gateway gives: {}", gateway);
// choose gateway
let gateway_details = setup.try_get_gateway_details(&validator_servers).await?;
// get our identity key
let our_identity = managed_keys.identity_keypair();
let our_identity = key_manager.identity_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
let shared_keys = helpers::register_with_gateway(&gateway_details, our_identity).await?;
let shared_keys = helpers::register_with_gateway::<St>(&gateway, our_identity).await?;
key_manager.insert_gateway_shared_key(shared_keys);
managed_keys
.deal_with_gateway_key(shared_keys, key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
// TODO: here we should be probably persisting gateway details as opposed to returning them
Ok(gateway_details)
Ok(gateway.into())
}
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
@@ -206,8 +101,7 @@ where
/// b. Create a new gateway configuration but keep existing keys. This assumes that the caller
/// knows what they are doing and that the keys match the requested gateway.
/// c. Create a new gateway configuration with a newly registered gateway and keys.
pub async fn setup_gateway_from_config<C, T, KSt>(
key_store: &KSt,
pub async fn setup_gateway_from_config<C, T, St>(
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config<T>,
@@ -216,8 +110,7 @@ pub async fn setup_gateway_from_config<C, T, KSt>(
where
C: NymConfig + ClientCoreConfigTrait,
T: NymConfig,
KSt: KeyStore,
<KSt as KeyStore>::StorageError: Send + Sync + 'static,
St: Storage,
{
let id = config.get_id();
@@ -228,42 +121,35 @@ where
return load_existing_gateway_config::<C>(&id);
}
let gateway_setup = GatewaySetup::new(
None,
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(by_latency),
);
// Else, we proceed by querying the nym-api
let gateway = gateway_setup
.try_get_gateway_details(&config.get_nym_api_endpoints())
.await?;
log::debug!("Querying gateway gives: {:?}", gateway);
let gateway = helpers::query_gateway_details(
config.get_nym_api_endpoints(),
user_chosen_gateway_id,
by_latency,
)
.await?;
log::debug!("Querying gateway gives: {}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and
// wants to keep the,
if !register_gateway && user_chosen_gateway_id.is_some() {
eprintln!("Using gateway provided by user, keeping existing keys");
return Ok(gateway);
return Ok(gateway.into());
}
let mut rng = OsRng;
let mut managed_keys =
crate::client::key_manager::ManagedKeys::load_or_generate(&mut rng, key_store).await;
// Create new keys and derive our identity
let our_identity = managed_keys.identity_keypair();
let mut key_manager = new_client_keys();
let our_identity = key_manager.identity_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
eprintln!("Registering with new gateway");
let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?;
managed_keys
.deal_with_gateway_key(shared_keys, key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
let shared_keys = helpers::register_with_gateway::<St>(&gateway, our_identity).await?;
key_manager.insert_gateway_shared_key(shared_keys);
Ok(gateway)
// Write all keys to storage and just return the gateway endpoint config. It is assumed that we
// will load keys from storage when actually connecting.
helpers::store_keys(&key_manager, config)?;
Ok(gateway.into())
}
/// Read and reuse the existing gateway configuration from a file that was generate earlier.
@@ -300,8 +186,7 @@ pub fn get_client_address(
}
/// Get the client address by loading the keys from stored files.
// TODO: rethink that sucker
pub fn get_client_address_from_stored_ondisk_keys<T>(
pub fn get_client_address_from_stored_keys<T>(
config: &Config<T>,
) -> Result<Recipient, ClientCoreError>
where
@@ -311,8 +196,11 @@ where
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
))
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
@@ -320,8 +208,11 @@ where
pathfinder: &ClientKeyPathfinder,
) -> Result<encryption::KeyPair, ClientCoreError> {
let sphinx_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&pathfinder.encryption_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
pathfinder.public_encryption_key().to_owned(),
))
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
Ok(sphinx_keypair)
}
+47 -55
View File
@@ -12,8 +12,6 @@ use futures::{SinkExt, StreamExt};
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_coconut_interface::Credential;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use nym_gateway_requests::iv::IV;
@@ -28,6 +26,7 @@ use std::sync::Arc;
use std::time::Duration;
use tungstenite::protocol::Message;
use nym_credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient;
#[cfg(not(target_arch = "wasm32"))]
@@ -43,7 +42,7 @@ use wasm_utils::websocket::JSWebsocket;
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient<C, St> {
pub struct GatewayClient<C, St: Storage> {
authenticated: bool,
disabled_credentials_mode: bool,
bandwidth_remaining: i64,
@@ -69,14 +68,17 @@ pub struct GatewayClient<C, St> {
shutdown: TaskClient,
}
impl<C, St> GatewayClient<C, St> {
impl<C, St> GatewayClient<C, St>
where
C: Sync + Send,
St: Storage,
{
// TODO: put it all in a Config struct
#[allow(clippy::too_many_arguments)]
pub fn new(
gateway_address: String,
local_identity: Arc<identity::KeyPair>,
gateway_identity: identity::PublicKey,
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
shared_key: Option<Arc<SharedKeys>>,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
@@ -120,6 +122,40 @@ impl<C, St> GatewayClient<C, St> {
self.reconnection_backoff = backoff
}
pub fn new_init(
gateway_address: String,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
response_timeout_duration: Duration,
) -> Self {
use futures::channel::mpsc;
// note: this packet_router is completely invalid in normal circumstances, but "works"
// perfectly fine here, because it's not meant to be used
let (ack_tx, _) = mpsc::unbounded();
let (mix_tx, _) = mpsc::unbounded();
let shutdown = TaskClient::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
GatewayClient::<C, St> {
authenticated: false,
disabled_credentials_mode: true,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
shared_key: None,
connection: SocketState::NotConnected,
packet_router,
response_timeout_duration,
bandwidth_controller: None,
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
shutdown,
}
}
pub fn gateway_identity(&self) -> identity::PublicKey {
self.gateway_identity
}
@@ -531,9 +567,7 @@ impl<C, St> GatewayClient<C, St> {
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError>
where
C: DkgQueryClient + Send + Sync,
St: CredentialStorage,
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
C: DkgQueryClient,
{
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -571,7 +605,7 @@ impl<C, St> GatewayClient<C, St> {
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
packets
.iter()
.map(|packet| packet.packet().len())
.map(|packet| packet.sphinx_packet().len())
.sum::<usize>() as i64
}
@@ -579,8 +613,6 @@ impl<C, St> GatewayClient<C, St> {
&mut self,
packets: Vec<MixPacket>,
) -> Result<(), GatewayClientError> {
debug!("Sending {} mix packets", packets.len());
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
@@ -625,10 +657,9 @@ impl<C, St> GatewayClient<C, St> {
) -> Result<(), GatewayClientError> {
if let Err(err) = self.send_websocket_message_without_response(msg).await {
if err.is_closed_connection() && self.should_reconnect_on_failure {
debug!("Going to attempt a reconnection");
info!("Going to attempt a reconnection");
self.attempt_reconnection().await
} else {
warn!("{err}");
Err(err)
}
} else {
@@ -655,9 +686,9 @@ impl<C, St> GatewayClient<C, St> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if (mix_packet.packet().len() as i64) > self.bandwidth_remaining {
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.packet().len() as i64,
mix_packet.sphinx_packet().len() as i64,
self.bandwidth_remaining,
));
}
@@ -740,9 +771,7 @@ impl<C, St> GatewayClient<C, St> {
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError>
where
C: DkgQueryClient + Send + Sync,
St: CredentialStorage,
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
C: DkgQueryClient,
{
if !self.connection.is_established() {
self.establish_connection().await?;
@@ -761,40 +790,3 @@ impl<C, St> GatewayClient<C, St> {
Ok(shared_key)
}
}
impl<C> GatewayClient<C, EphemeralCredentialStorage> {
// for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic...
pub fn new_init(
gateway_address: String,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
response_timeout_duration: Duration,
) -> Self {
use futures::channel::mpsc;
// note: this packet_router is completely invalid in normal circumstances, but "works"
// perfectly fine here, because it's not meant to be used
let (ack_tx, _) = mpsc::unbounded();
let (mix_tx, _) = mpsc::unbounded();
let shutdown = TaskClient::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
GatewayClient::<C, EphemeralCredentialStorage> {
authenticated: false,
disabled_credentials_mode: true,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
shared_key: None,
connection: SocketState::NotConnected,
packet_router,
response_timeout_duration,
bandwidth_controller: None,
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
shutdown,
}
}
}
+2 -3
View File
@@ -1,17 +1,16 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::GatewayClientError;
pub use client::GatewayClient;
use log::warn;
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_gateway_requests::BinaryResponse;
pub use packet_router::{
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
};
use tungstenite::{protocol::Message, Error as WsError};
pub use nym_gateway_requests::registration::handshake::SharedKeys;
pub mod client;
pub mod error;
pub mod packet_router;
@@ -50,15 +50,10 @@ impl PacketRouter {
let ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
for received_packet in unwrapped_packets {
if received_packet.len() == PacketSize::AckPacket.plaintext_size()
|| received_packet.len() == PacketSize::OutfoxAckPacket.plaintext_size()
{
if received_packet.len() == PacketSize::AckPacket.plaintext_size() {
received_acks.push(received_packet);
} else if received_packet.len()
== PacketSize::RegularPacket.plaintext_size() - ack_overhead
|| received_packet.len()
== PacketSize::OutfoxRegularPacket.plaintext_size() - ack_overhead
|| received_packet.len() == PacketSize::OutfoxRegularPacket.size() - 6
{
trace!("routing regular packet");
received_messages.push(received_packet);
+18 -15
View File
@@ -4,11 +4,10 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::params::PacketType;
use nym_sphinx::NymPacket;
use nym_sphinx::framing::codec::SphinxCodec;
use nym_sphinx::framing::packet::FramedSphinxPacket;
use nym_sphinx::params::PacketMode;
use nym_sphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
@@ -51,8 +50,8 @@ pub trait SendWithoutResponse {
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
packet: SphinxPacket,
packet_mode: PacketMode,
) -> io::Result<()>;
}
@@ -62,12 +61,12 @@ pub struct Client {
}
struct ConnectionSender {
channel: mpsc::Sender<FramedNymPacket>,
channel: mpsc::Sender<FramedSphinxPacket>,
current_reconnection_attempt: Arc<AtomicU32>,
}
impl ConnectionSender {
fn new(channel: mpsc::Sender<FramedNymPacket>) -> Self {
fn new(channel: mpsc::Sender<FramedSphinxPacket>) -> Self {
ConnectionSender {
channel,
current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
@@ -85,7 +84,7 @@ impl Client {
async fn manage_connection(
address: SocketAddr,
receiver: mpsc::Receiver<FramedNymPacket>,
receiver: mpsc::Receiver<FramedSphinxPacket>,
connection_timeout: Duration,
current_reconnection: &AtomicU32,
) {
@@ -97,7 +96,7 @@ impl Client {
debug!("Managed to establish connection to {}", address);
// if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release);
Framed::new(stream, NymCodec)
Framed::new(stream, SphinxCodec)
}
Err(err) => {
debug!(
@@ -149,7 +148,11 @@ impl Client {
}
}
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
fn make_connection(
&mut self,
address: NymNodeRoutingAddress,
pending_packet: FramedSphinxPacket,
) {
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity
@@ -197,12 +200,12 @@ impl SendWithoutResponse for Client {
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
packet: SphinxPacket,
packet_mode: PacketMode,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let framed_packet =
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version);
FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) {
@@ -59,14 +59,14 @@ impl PacketForwarder {
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
let next_hop = mix_packet.next_hop();
let packet_type = mix_packet.packet_type();
let packet = mix_packet.into_packet();
let packet_mode = mix_packet.packet_mode();
let sphinx_packet = mix_packet.into_sphinx_packet();
// we don't care about responses, we just want to fire packets
// as quickly as possible
if let Err(err) =
self.mixnet_client
.send_without_response(next_hop, packet, packet_type)
.send_without_response(next_hop, sphinx_packet, packet_mode)
{
debug!("failed to forward the packet - {err}")
}
+2 -12
View File
@@ -17,9 +17,7 @@ nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-con
nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" }
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
nym-name-service-common = { path = "../../cosmwasm-smart-contracts/name-service" }
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" }
nym-vesting-contract = { path = "../../../contracts/vesting" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@@ -40,7 +38,7 @@ nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
async-trait = { workspace = true, optional = true }
bip39 = { workspace = true, features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true }
cosmrs = { workspace = true, features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true }
# note that this has the same version as used by cosmrs
eyre = { version = "0.6", optional = true }
cw3 = { workspace = true, optional = true }
@@ -54,7 +52,7 @@ cosmwasm-std = { workspace = true, optional = true }
[dev-dependencies]
bip39 = { workspace = true }
cosmrs = { workspace = true, features = ["rpc", "bip32"] }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32"] }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
ts-rs = "6.1.2"
@@ -65,14 +63,6 @@ name = "offline_signing"
# (traits would need to be moved around and refactored themselves)
required-features = ["nyxd-client"]
[[example]]
name = "query_service_provider_directory"
required-features = ["nyxd-client"]
[[example]]
name = "query_name_service"
required-features = ["nyxd-client"]
[features]
nyxd-client = [
"async-trait",
@@ -1,35 +0,0 @@
use std::str::FromStr;
use cosmrs::AccountId;
use nym_name_service_common::Address;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_validator_client::nyxd::traits::NameServiceQueryClient;
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
let client = nym_validator_client::Client::new_query(config).unwrap();
let config = client.nyxd.get_name_service_config().await.unwrap();
println!("config: {config:?}");
let names_paged = client.nyxd.get_names_paged(None, None).await.unwrap();
println!("names (paged): {names_paged:#?}");
let names = client.nyxd.get_all_names().await.unwrap();
println!("names: {names:#?}");
let owner = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
let names_by_owner = client.nyxd.get_names_by_owner(owner).await.unwrap();
println!("names (by owner): {names_by_owner:#?}");
let nym_address = Address::new("client_id.client_key@gateway_id");
let names_by_address = client.nyxd.get_names_by_address(nym_address).await.unwrap();
println!("names (by address): {names_by_address:#?}");
let service_info = client.nyxd.get_name_entry(1).await;
println!("service info: {service_info:#?}");
}
@@ -1,43 +0,0 @@
use std::str::FromStr;
use cosmrs::AccountId;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_service_provider_directory_common::NymAddress;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
let client = nym_validator_client::Client::new_query(config).unwrap();
let config = client.nyxd.get_service_config().await.unwrap();
println!("config: {config:?}");
let services_paged = client.nyxd.get_services_paged(None, None).await.unwrap();
println!("services (paged): {services_paged:#?}");
let services = client.nyxd.get_all_services().await.unwrap();
println!("services: {services:#?}");
let announcer = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
let services_by_announcer = client
.nyxd
.get_services_by_announcer(announcer)
.await
.unwrap();
println!("services (by announcer): {services_by_announcer:#?}");
let nym_address = NymAddress::new("foo.bar@gateway");
let services_by_nym_address = client
.nyxd
.get_services_by_nym_address(nym_address)
.await
.unwrap();
assert_eq!(services_by_announcer, services_by_nym_address);
let service_info = client.nyxd.get_service_info(1).await;
println!("service info: {service_info:#?}");
}
@@ -11,9 +11,7 @@ use nym_api_requests::models::{
};
use nym_coconut_dkg_common::types::NodeIndex;
use nym_coconut_interface::VerificationKey;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId,
};
pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId};
use url::Url;
#[cfg(feature = "nyxd-client")]
@@ -33,7 +31,7 @@ use nym_mixnet_contract_common::{
families::{Family, FamilyHead},
mixnode::MixNodeBond,
pending_events::{PendingEpochEvent, PendingIntervalEvent},
Delegation, RewardedSetNodeStatus, UnbondedMixnode,
Delegation, IdentityKey, RewardedSetNodeStatus, UnbondedMixnode,
};
#[cfg(feature = "nyxd-client")]
use nym_network_defaults::NymNetworkDetails;
@@ -1,6 +1,6 @@
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Config as ClientConfig, NyxdClient, QueryNyxdClient};
use crate::{NymApiClient, ValidatorClientError};
use crate::NymApiClient;
use crate::nyxd::traits::MixnetQueryClient;
use colored::Colorize;
@@ -45,23 +45,6 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
)
}
pub async fn test_nyxd_url_connection(
network: NymNetworkDetails,
nyxd_url: Url,
address: cosmrs::AccountId,
) -> Result<bool, ValidatorClientError> {
let config = ClientConfig::try_from_nym_network_details(&network)
.expect("failed to create valid nyxd client config");
let mut nyxd_client = NyxdClient::<QueryNyxdClient>::connect(config, nyxd_url.as_str())?;
// possibly redundant, but lets just leave it here
nyxd_client.set_mixnet_contract_address(address);
match test_nyxd_connection(network, &nyxd_url, &nyxd_client).await {
ConnectionResult::Nyxd(_, _, res) => Ok(res),
_ => Ok(false), // ✶ not possible to happens
}
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nyxd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
@@ -122,7 +105,7 @@ async fn test_nyxd_connection(
{
Ok(Err(NyxdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::warn!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
log::debug!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
false
}
Ok(Err(NyxdError::AbciError { code, log, .. })) => {
@@ -134,13 +117,13 @@ async fn test_nyxd_connection(
);
code == 18
}
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
log::warn!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => {
log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::warn!(
log::debug!(
"Checking: nyxd_url: {url}: {}, but with error: {e}",
"success".green()
);
@@ -151,7 +134,7 @@ async fn test_nyxd_connection(
true
}
Err(e) => {
log::warn!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
log::debug!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
false
}
};
@@ -9,9 +9,6 @@ pub enum NymAPIError {
source: reqwest::Error,
},
#[error("Not found")]
NotFound,
#[error("Request failed with error message - {0}")]
GenericRequestFailure(String),
@@ -15,9 +15,7 @@ use nym_api_requests::models::{
};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
use nym_service_provider_directory_common::response::ServicesListResponse;
use reqwest::{Response, StatusCode};
use reqwest::Response;
use serde::{Deserialize, Serialize};
use url::Url;
@@ -78,8 +76,6 @@ impl Client {
let res = self.send_get_request(path, params).await?;
if res.status().is_success() {
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(NymAPIError::NotFound)
} else {
Err(NymAPIError::GenericRequestFailure(res.text().await?))
}
@@ -484,19 +480,6 @@ impl Client {
)
.await
}
pub async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
log::trace!("Getting service providers");
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
.await
}
//pub async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
pub async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
log::trace!("Getting registered names");
self.query_nym_api(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -32,6 +32,3 @@ pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
pub const SERVICE_PROVIDERS: &str = "services";
pub const REGISTERED_NAMES: &str = "names";
@@ -21,8 +21,8 @@ use std::{io, time::Duration};
#[derive(Debug, Error)]
pub enum NyxdError {
#[error("No contract address is available to perform the call: {0}")]
NoContractAddressAvailable(String),
#[error("No contract address is available to perform the call")]
NoContractAddressAvailable,
#[error(transparent)]
WalletError(#[from] DirectSecp256k1HdWalletError),
@@ -162,7 +162,7 @@ fn try_parse_abci_log(log: &abci::Log) -> Option<String> {
.value()
.contains("Maximum amount of locked coins has already been pledged")
{
Some("Maximum amount of locked tokens has already been used. You can only use up to 10% of your locked tokens for bonding and delegating.".to_string())
Some("Maximum amount of locked tokens has alredy been used. You can only use up to 10% of your locked tokens for bonding and delegating.".to_string())
} else {
None
}
@@ -127,7 +127,7 @@ impl GasAdjustable for Gas {
mod sealed {
use cosmrs::tx::{self, Gas};
use cosmrs::Coin as CosmosCoin;
use cosmrs::{AccountId, Denom as CosmosDenom};
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
@@ -144,11 +144,29 @@ mod sealed {
}
}
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
val.to_string().parse().unwrap()
}
// at the time of writing it the current cosmrs' Decimal is extremely limited...
#[derive(Serialize, Deserialize)]
#[serde(remote = "CosmosDecimal")]
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
impl From<Decimal> for CosmosDecimal {
fn from(val: Decimal) -> Self {
val.0.into()
}
}
#[derive(Serialize, Deserialize, Clone)]
struct Coin {
#[serde(with = "Denom")]
denom: CosmosDenom,
amount: u128,
#[serde(with = "Decimal")]
amount: CosmosDecimal,
}
impl From<Coin> for CosmosCoin {
@@ -16,7 +16,7 @@ use cosmrs::rpc::query::Query;
use cosmrs::rpc::Error as TendermintRpcError;
use cosmrs::rpc::HttpClientUrl;
use cosmrs::tx::Msg;
use log::{debug, trace};
use log::debug;
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
@@ -39,7 +39,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{self, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{bip32, AccountId, Denom};
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
use cosmwasm_std::Addr;
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
@@ -67,8 +67,6 @@ pub struct Config {
pub(crate) group_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
pub(crate) service_provider_contract_address: Option<AccountId>,
pub(crate) name_service_contract_address: Option<AccountId>,
// TODO: add this in later commits
// pub(crate) gas_price: GasPrice,
}
@@ -79,8 +77,8 @@ impl Config {
expected_prefix: &str,
) -> Result<Option<AccountId>, NyxdError> {
if let Some(address) = raw {
trace!("Raw address:{:?}", raw);
trace!("Expected prefix:{:?}", expected_prefix);
debug!("Raw address:{:?}", raw);
debug!("Expected prefix:{:?}", expected_prefix);
let parsed: AccountId = address
.parse()
.map_err(|_| NyxdError::MalformedAccountAddress(address.clone()))?;
@@ -133,17 +131,6 @@ impl Config {
details.contracts.coconut_dkg_contract_address.as_ref(),
prefix,
)?,
service_provider_contract_address: Self::parse_optional_account(
details
.contracts
.service_provider_directory_contract_address
.as_ref(),
prefix,
)?,
name_service_contract_address: Self::parse_optional_account(
details.contracts.name_service_contract_address.as_ref(),
prefix,
)?,
})
}
}
@@ -259,10 +246,6 @@ impl<C> NyxdClient<C> {
self.config.multisig_contract_address = Some(address);
}
pub fn set_service_provider_contract_address(&mut self, address: AccountId) {
self.config.service_provider_contract_address = Some(address);
}
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address,
@@ -321,16 +304,6 @@ impl<C> NyxdClient<C> {
self.config.coconut_dkg_contract_address.as_ref().unwrap()
}
// The service provider directory contract is optional, so we return an Option not a Result
pub fn service_provider_contract_address(&self) -> Option<&AccountId> {
self.config.service_provider_contract_address.as_ref()
}
// The name service contract is optional, so we return an Option not a Result
pub fn name_service_contract_address(&self) -> Option<&AccountId> {
self.config.name_service_contract_address.as_ref()
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier;
}
@@ -16,25 +16,15 @@ mod mixnet_signing_client;
mod multisig_signing_client;
mod vesting_signing_client;
mod sp_directory_query_client;
mod sp_directory_signing_client;
mod name_service_query_client;
mod name_service_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use dkg_query_client::DkgQueryClient;
pub use group_query_client::GroupQueryClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use multisig_query_client::MultisigQueryClient;
pub use name_service_query_client::NameServiceQueryClient;
pub use sp_directory_query_client::SpDirectoryQueryClient;
pub use vesting_query_client::VestingQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_signing_client::DkgSigningClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use name_service_signing_client::NameServiceSigningClient;
pub use sp_directory_signing_client::SpDirectorySigningClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -1,109 +0,0 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_name_service_common::{
msg::QueryMsg as NameQueryMsg,
response::{ConfigResponse, NamesListResponse, PagedNamesListResponse},
Address, NameEntry, NameId,
};
use serde::Deserialize;
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
#[async_trait]
pub trait NameServiceQueryClient {
async fn query_name_service_contract<T>(&self, query: NameQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>;
async fn get_name_service_config(&self) -> Result<ConfigResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::Config {})
.await
}
async fn get_name_entry(&self, name_id: NameId) -> Result<NameEntry, NyxdError> {
self.query_name_service_contract(NameQueryMsg::NameId { name_id })
.await
}
async fn get_names_paged(
&self,
start_after: Option<NameId>,
limit: Option<u32>,
) -> Result<PagedNamesListResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::All { limit, start_after })
.await
}
async fn get_names_by_owner(&self, owner: AccountId) -> Result<NamesListResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::ByOwner {
owner: owner.to_string(),
})
.await
}
async fn get_names_by_address(&self, address: Address) -> Result<NamesListResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::ByAddress { address })
.await
}
async fn get_name_service_contract_version(
&self,
) -> Result<ContractBuildInformation, NyxdError> {
self.query_name_service_contract(NameQueryMsg::GetContractVersion {})
.await
}
async fn get_all_names(&self) -> Result<Vec<NameEntry>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_names_paged(start_after.take(), None).await?;
let last_id = paged_response.names.last().map(|serv| serv.name_id);
services.append(&mut paged_response.names);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(services)
}
}
#[async_trait]
impl<C> NameServiceQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_name_service_contract<T>(&self, query: NameQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(
self.name_service_contract_address().ok_or(
NyxdError::NoContractAddressAvailable("name service contract".to_string()),
)?,
&query,
)
.await
}
}
#[async_trait]
impl<C> NameServiceQueryClient for crate::Client<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_name_service_contract<T>(&self, query: NameQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.nyxd.query_name_service_contract(query).await
}
}
@@ -1,96 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_name_service_common::{msg::ExecuteMsg as NameExecuteMsg, Address, NameId, NymName};
use crate::nyxd::{
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
SigningCosmWasmClient,
};
#[async_trait]
pub trait NameServiceSigningClient {
async fn execute_name_service_contract(
&self,
fee: Option<Fee>,
msg: NameExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn register_name(
&self,
name: NymName,
address: Address,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(
fee,
NameExecuteMsg::Register { name, address },
vec![deposit],
)
.await
}
async fn delete_name_by_id(
&self,
name_id: NameId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(fee, NameExecuteMsg::DeleteId { name_id }, vec![])
.await
}
async fn delete_service_provider_by_name(
&self,
name: NymName,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(fee, NameExecuteMsg::DeleteName { name }, vec![])
.await
}
async fn update_deposit_required(
&self,
deposit_required: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(
fee,
NameExecuteMsg::UpdateDepositRequired {
deposit_required: deposit_required.into(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> NameServiceSigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_name_service_contract(
&self,
fee: Option<Fee>,
msg: NameExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.name_service_contract_address().ok_or(
NyxdError::NoContractAddressAvailable("name service contract".to_string()),
)?,
&msg,
fee,
memo,
funds,
)
.await
}
}
@@ -1,120 +0,0 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
};
use serde::Deserialize;
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
#[async_trait]
pub trait SpDirectoryQueryClient {
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>;
async fn get_service_config(&self) -> Result<ConfigResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::Config {})
.await
}
async fn get_service_info(
&self,
service_id: ServiceId,
) -> Result<ServiceInfoResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ServiceId { service_id })
.await
}
async fn get_services_paged(
&self,
start_after: Option<ServiceId>,
limit: Option<u32>,
) -> Result<PagedServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::All { limit, start_after })
.await
}
async fn get_services_by_announcer(
&self,
announcer: AccountId,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByAnnouncer {
announcer: announcer.to_string(),
})
.await
}
async fn get_services_by_nym_address(
&self,
nym_address: NymAddress,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByNymAddress { nym_address })
.await
}
async fn get_sp_contract_version(&self) -> Result<ContractBuildInformation, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::GetContractVersion {})
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(services)
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&query,
)
.await
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for crate::Client<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.nyxd.query_service_provider_contract(query).await
}
}
@@ -1,111 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
};
use crate::nyxd::{
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
SigningCosmWasmClient,
};
#[async_trait]
pub trait SpDirectorySigningClient {
async fn execute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
},
vec![deposit],
)
.await
}
async fn delete_service_provider_by_id(
&self,
service_id: ServiceId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteId { service_id },
vec![],
)
.await
}
async fn delete_service_provider_by_nym_address(
&self,
nym_address: NymAddress,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteNymAddress { nym_address },
vec![],
)
.await
}
async fn update_deposit_required(
&self,
deposit_required: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::UpdateDepositRequired {
deposit_required: deposit_required.into(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> SpDirectorySigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&msg,
fee,
memo,
funds,
)
.await
}
}
+2 -4
View File
@@ -14,7 +14,7 @@ clap = { version = "4.0", features = ["derive"] }
cw-utils = { workspace = true }
handlebars = "3.0.1"
humantime-serde = "1.0"
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
log = { workspace = true }
rand = {version = "0.6", features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
@@ -25,7 +25,7 @@ toml = "0.5.6"
url = "2.2"
tap = "1"
cosmrs = { workspace = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmwasm-std = { workspace = true }
nym-validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
@@ -38,5 +38,3 @@ nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-cont
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
@@ -56,8 +56,6 @@ pub async fn generate(args: Args) {
.expect("threshold can't be converted to Decimal"),
},
max_voting_period: Duration::Time(args.max_voting_period),
executor: None,
proposal_deposit: None,
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
};
@@ -5,8 +5,6 @@ use clap::{Args, Subcommand};
pub mod gateway;
pub mod mixnode;
pub mod name;
pub mod service;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -21,8 +19,4 @@ pub enum MixnetOperatorsCommands {
Mixnode(mixnode::MixnetOperatorsMixnode),
/// Manage your gateway
Gateway(gateway::MixnetOperatorsGateway),
/// Manage your service
ServiceProvider(service::MixnetOperatorsService),
/// Manage your registered name
Name(name::MixnetOperatorsName),
}
@@ -1,25 +0,0 @@
use clap::Parser;
use log::{error, info};
use nym_name_service_common::NameId;
use nym_validator_client::nyxd::{error::NyxdError, traits::NameServiceSigningClient};
use tap::TapFallible;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub id: NameId,
}
pub async fn delete(args: Args, client: SigningClient) -> Result<(), NyxdError> {
info!("Deleting registered name alias with id {}", args.id);
let res = client
.delete_name_by_id(args.id, None)
.await
.tap_err(|err| error!("Failed to delete name: {err:#?}"))?;
info!("Deleted: {res:?}");
Ok(())
}
@@ -1,19 +0,0 @@
use clap::{Args, Subcommand};
pub mod delete;
pub mod register;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsName {
#[clap(subcommand)]
pub command: MixnetOperatorsNameCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsNameCommands {
/// Register a name alias for a nym address
Register(register::Args),
/// Delete name alias for a nym address
Delete(delete::Args),
}

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