Compare commits

..

3 Commits

Author SHA1 Message Date
farbanas fbc8fa24c4 add yarn install 2023-02-27 13:10:09 +01:00
farbanas bafefbf8a6 trying to fix workflow on our custom mac runner 2023-02-27 13:04:58 +01:00
farbanas 09c0a74107 chore: changed mac runner to our new custom one 2023-02-27 11:31:58 +01:00
443 changed files with 5443 additions and 14460 deletions
@@ -79,9 +79,6 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.110.0 wasm-opt
- name: Build release contracts
run: make wasm
@@ -99,14 +96,9 @@ jobs:
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/credential $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
+1 -4
View File
@@ -6,7 +6,7 @@ on:
jobs:
build:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
if: ${{ startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release' }}
runs-on: [self-hosted, custom-runner-linux]
steps:
- uses: actions/checkout@v2
@@ -19,9 +19,6 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.110.0 wasm-opt
- name: Build release contracts
run: make wasm
@@ -0,0 +1,57 @@
name: CI for Network Explorer API
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
if: ${{ startsWith(github.ref, 'refs/tags/nym-explorer-api-') && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
- name: Check the release tag starts with `nym-explorer-api-`
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-explorer-api-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all explorer-api
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path explorer-api/Cargo.toml --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/explorer-api
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/explorer-api
+50
View File
@@ -0,0 +1,50 @@
name: Publish Nym CLI binaries
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym-cli:
if: ${{ startsWith(github.ref, 'refs/tags/nym-cli-') && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-cli-`
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-cli-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build binary
run: make build-nym-cli
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: nym-cli-${{ matrix.platform }}
path: |
target/release/nym-cli*
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/nym-cli
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [macos-latest]
platform: [custom-runner-mac-m1]
runs-on: ${{ matrix.platform }}
steps:
@@ -24,10 +24,15 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
- name: Setup yarn
run: npm install -g yarn
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install the Apple developer certificate for code signing
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
@@ -39,7 +44,7 @@ jobs:
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$APPLE_CERTIFICATE" | base64 --decode --output $CERTIFICATE_PATH
echo -n "$APPLE_CERTIFICATE" | base64 --decode --output=$CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
if: ${{ startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release' }}
strategy:
fail-fast: false
matrix:
+1 -1
View File
@@ -16,7 +16,7 @@ env:
jobs:
publish-nym:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
if: ${{ startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release' }}
strategy:
fail-fast: false
matrix:
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
if: ${{ startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release' }}
strategy:
fail-fast: false
matrix:
@@ -9,7 +9,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
if: ${{ startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release' }}
strategy:
fail-fast: false
matrix:
+2
View File
@@ -39,5 +39,7 @@ validator-api-config.toml
dist
storybook-static
envs/qwerty.env
Cargo.lock
nym-connect/Cargo.lock
.parcel-cache
**/.DS_Store
-54
View File
@@ -4,60 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.13] (2023-03-15)
- NE - instead of throwing a "Mixnode/Gateway not found" error for blacklisted nodes due to bad performance, show their history but tag them as "Having poor performance" ([#2979])
- NE - Upgrade Sandbox and make below changes: ([#2332])
- Explorer - Updates ([#3168])
- Fix contracts and nym-api audit findings ([#3026])
- Website v2 - deploy infrastructure for strapi and CI ([#2213])
- add blockstream green to sp list ([#3180])
- mock-nym-api: fix .storybook lint error ([#3178])
- Validating new interval config parameters to prevent division by zero ([#3153])
[#2979]: https://github.com/nymtech/nym/issues/2979
[#2332]: https://github.com/nymtech/nym/issues/2332
[#3168]: https://github.com/nymtech/nym/issues/3168
[#3026]: https://github.com/nymtech/nym/issues/3026
[#2213]: https://github.com/nymtech/nym/issues/2213
[#3180]: https://github.com/nymtech/nym/pull/3180
[#3178]: https://github.com/nymtech/nym/pull/3178
[#3153]: https://github.com/nymtech/nym/pull/3153
## [v1.1.12] (2023-03-07)
- Fix generated docs for mixnet and vesting contract on docs.rs ([#3093])
- Introduce a way of injecting topology into the client ([#3044])
- Update mixnet TypeScript client methods #1 ([#2783])
- Update tooltips for routing and average score ([#3133])
- update selected service provider description style ([#3128])
[#3093]: https://github.com/nymtech/nym/issues/3093
[#3044]: https://github.com/nymtech/nym/issues/3044
[#2783]: https://github.com/nymtech/nym/issues/2783
[#3133]: https://github.com/nymtech/nym/pull/3133
[#3128]: https://github.com/nymtech/nym/pull/3128
## [v1.1.11] (2023-02-28)
- Fix empty dealer set loop ([#3105])
- The nym-api db.sqlite is broken when trying to run against it it in `enabled-credentials-mode true` there is an ordering issue with migrations when using the credential binary to purchase bandwidth ([#3100])
- Feature/latency based gateway selection ([#3081])
- Fix the credential binary to handle transactions to sleep when in non-inProgress epochs ([#3057])
- Publish mixnet contract to crates.io ([#1919])
- Publish vesting contract to crates.io ([#1920])
- Feature/update checker to use master ([#3097])
- Feature/improve binary checks ([#3094])
[#3105]: https://github.com/nymtech/nym/issues/3105
[#3100]: https://github.com/nymtech/nym/issues/3100
[#3081]: https://github.com/nymtech/nym/pull/3081
[#3057]: https://github.com/nymtech/nym/issues/3057
[#1919]: https://github.com/nymtech/nym/issues/1919
[#1920]: https://github.com/nymtech/nym/issues/1920
[#3097]: https://github.com/nymtech/nym/pull/3097
[#3094]: https://github.com/nymtech/nym/pull/3094
## [v1.1.10] (2023-02-21)
- Verloc listener causing mixnode unexpected shutdown ([#3038])
Generated
+859 -980
View File
File diff suppressed because it is too large Load Diff
+2 -12
View File
@@ -105,19 +105,9 @@ edition = "2021"
license = "Apache-2.0"
[workspace.dependencies]
async-trait = "0.1.64"
async-trait = "0.1.63"
cfg-if = "1.0.0"
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"
dotenv = "0.15.0"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
+5 -16
View File
@@ -1,22 +1,13 @@
test: clippy-all cargo-test wasm fmt
test-no-mobile: clippy-all-no-mobile cargo-test-no-mobile wasm fmt-no-mobile
test-all: test cargo-test-expensive
test-all-no-mobile: test-no-mobile cargo-test-expensive
no-clippy: build cargo-test wasm fmt
no-clippy-no-mobile: build-no-mobile cargo-test-no-mobile wasm fmt-no-mobile
happy: fmt clippy-happy test
happy-no-mobile: fmt-no-mobile clippy-happy-no-mobile test-no-mobile
clippy-all: clippy-all-no-mobile clippy-all-connect-mobile
clippy-all-no-mobile: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client
clippy-happy: clippy-happy-no-mobile clippy-happy-connect-mobile
clippy-happy-no-mobile: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
cargo-test: cargo-test-no-mobile test-connect-mobile
cargo-test-no-mobile: test-main test-contracts test-wallet test-connect
clippy-all: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-connect-mobile clippy-all-wasm-client
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect clippy-happy-connect-mobile
cargo-test: test-main test-contracts test-wallet test-connect test-connect-mobile
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
build: build-no-mobile build-connect-mobile
build-no-mobile: build-contracts build-wallet build-main build-main-examples build-connect build-wasm-client
fmt: fmt-no-mobile fmt-connect-mobile
fmt-no-mobile: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client
build: build-contracts build-wallet build-main build-main-examples build-connect build-connect-mobile build-wasm-client
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-connect-mobile fmt-wasm-client
clippy-happy-main:
cargo clippy
@@ -135,8 +126,6 @@ fmt-wasm-client:
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
mixnet-opt: wasm
cd contracts/mixnet && make opt
+1 -1
View File
@@ -16,7 +16,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/actions/workflow/status/nymtech/nym/build.yml?branch=develop&style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
### Building
+2 -11
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.1.13"
version = "1.1.10"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
@@ -8,7 +8,7 @@ rust-version = "1.66"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
async-trait = { version = "0.1.58" }
dirs = "4.0"
dashmap = "5.4.0"
futures = "0.3"
@@ -20,7 +20,6 @@ serde_json = "1.0.89"
tap = "1.0.1"
thiserror = "1.0.34"
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"
@@ -37,10 +36,6 @@ nym-topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
nym-task = { path = "../../common/task" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.validator-client]
path = "../../common/client-libs/validator-client"
features = ["nyxd-client"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
version = "0.1.11"
features = ["time"]
@@ -49,9 +44,6 @@ features = ["time"]
version = "1.24.1"
features = ["time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
version = "0.6.2"
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
@@ -73,7 +65,6 @@ features = ["futures"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../../common/wasm-utils"
features = ["websocket"]
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
version = "0.3.17"
@@ -15,7 +15,6 @@ use crate::client::replies::reply_controller::{ReplyControllerReceiver, ReplyCon
use crate::client::replies::reply_storage::{
CombinedReplyStorage, PersistentReplyStorage, ReplyStorageBackend, SentReplyKeys,
};
use crate::client::topology_control::nym_api_provider::NymApiTopologyProvider;
use crate::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
@@ -38,12 +37,10 @@ use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use std::sync::Arc;
use std::time::Duration;
use tap::TapFallible;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::CosmWasmClient;
@@ -93,7 +90,6 @@ impl ClientOutput {
pub struct ClientState {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub reply_controller_sender: ReplyControllerSender,
pub topology_accessor: TopologyAccessor,
}
pub enum ClientInputStatus {
@@ -158,7 +154,6 @@ pub struct BaseClientBuilder<'a, B, C: Clone> {
nym_api_endpoints: Vec<Url>,
reply_storage_backend: B,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
bandwidth_controller: Option<BandwidthController<C>>,
key_manager: KeyManager,
}
@@ -182,7 +177,6 @@ where
bandwidth_controller,
reply_storage_backend,
key_manager,
custom_topology_provider: None,
}
}
@@ -201,17 +195,11 @@ where
disabled_credentials: credentials_toggle.is_disabled(),
nym_api_endpoints,
reply_storage_backend,
custom_topology_provider: None,
bandwidth_controller,
key_manager,
}
}
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
self.custom_topology_provider = Some(provider);
self
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
@@ -316,7 +304,7 @@ where
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
return Err(ClientCoreError::GatewayAddressUnknown);
return Err(ClientCoreError::GatwayAddressUnknown);
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
@@ -353,38 +341,25 @@ where
Ok(gateway_client)
}
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
Box::new(NymApiTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
))
})
}
// 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>,
nym_api_urls: Vec<Url>,
refresh_rate: Duration,
topology_accessor: TopologyAccessor,
shutdown: TaskClient,
) -> Result<(), ClientCoreError> {
let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
let mut topology_refresher = TopologyRefresher::new(
topology_refresher_config,
topology_accessor,
topology_provider,
let topology_refresher_config = TopologyRefresherConfig::new(
nym_api_urls,
refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.try_refresh().await;
topology_refresher.refresh().await;
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
log::error!(
@@ -493,12 +468,8 @@ where
)
.await?;
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
self.nym_api_endpoints,
);
Self::start_topology_refresher(
topology_provider,
self.nym_api_endpoints.clone(),
self.debug_config.topology_refresh_rate,
shared_topology_accessor.clone(),
task_manager.subscribe(),
@@ -559,7 +530,7 @@ where
self.debug_config,
self.key_manager.ack_key(),
self_address,
shared_topology_accessor.clone(),
shared_topology_accessor,
sphinx_message_sender,
task_manager.subscribe(),
);
@@ -583,7 +554,6 @@ where
client_state: ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
topology_accessor: shared_topology_accessor,
},
task_manager,
})
@@ -333,7 +333,7 @@ where
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// The average delay could change depending on if backpressure in the downstream channel
// (mix_tx) was detected.
//self.adjust_current_average_message_sending_delay();
self.adjust_current_average_message_sending_delay();
let avg_delay = self.current_average_message_sending_delay();
// Start by checking if we have any incoming messages about closed connections
@@ -155,21 +155,18 @@ impl Backend {
// (assuming no key rotation has happened)
// but the way it's currently coded, everyone will purge old data
let since_last_flush = OffsetDateTime::now_utc() - last_flush;
let days = since_last_flush.whole_days();
let hours = since_last_flush.whole_hours() % 24;
if days > 0 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.");
if since_last_flush.whole_days() > 0 {
info!("it's been over {} days and {} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
manager.delete_all_reply_surb_data().await?;
}
if days > 1 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.");
if since_last_flush.whole_days() > 1 {
info!("it's been over {} days and {} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
manager.delete_all_reply_keys().await?;
}
if days > 2 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
if since_last_flush.whole_days() > 2 {
info!("it's been over {} days and {} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
manager.delete_all_tags().await?;
}
@@ -0,0 +1,336 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
use nym_topology::{nym_topology_from_detailed, NymTopology, NymTopologyError};
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{RwLock, RwLockReadGuard};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
#[derive(Debug)]
pub struct TopologyAccessorInner(Option<NymTopology>);
impl AsRef<Option<NymTopology>> for TopologyAccessorInner {
fn as_ref(&self) -> &Option<NymTopology> {
&self.0
}
}
impl TopologyAccessorInner {
fn new() -> Self {
TopologyAccessorInner(None)
}
fn update(&mut self, new: Option<NymTopology>) {
self.0 = new;
}
}
pub struct TopologyReadPermit<'a> {
permit: RwLockReadGuard<'a, TopologyAccessorInner>,
}
impl<'a> Deref for TopologyReadPermit<'a> {
type Target = TopologyAccessorInner;
fn deref(&self) -> &Self::Target {
&self.permit
}
}
impl<'a> TopologyReadPermit<'a> {
/// Using provided topology read permit, tries to get an immutable reference to the underlying
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
pub(super) fn try_get_valid_topology_ref(
&'a self,
ack_recipient: &Recipient,
packet_recipient: Option<&Recipient>,
) -> Result<&'a NymTopology, NymTopologyError> {
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
let topology = self
.permit
.as_ref()
.as_ref()
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
// 2. does it have any mixnode at all?
// 3. does it have any gateways at all?
// 4. does it have a mixnode on each layer?
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
// 5. does it contain OUR gateway (so that we could create an ack packet)?
if !topology.gateway_exists(ack_recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: ack_recipient.gateway().to_base58_string(),
});
}
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
if let Some(recipient) = packet_recipient {
if !topology.gateway_exists(recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: recipient.gateway().to_base58_string(),
});
}
}
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, TopologyAccessorInner>> for TopologyReadPermit<'a> {
fn from(read_permit: RwLockReadGuard<'a, TopologyAccessorInner>) -> Self {
TopologyReadPermit {
permit: read_permit,
}
}
}
#[derive(Clone, Debug)]
pub struct TopologyAccessor {
// `RwLock` *seems to* be the better approach for this as write access is only requested every
// few seconds, while reads are needed every single packet generated.
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
// approach than a `Mutex`
inner: Arc<RwLock<TopologyAccessorInner>>,
}
impl TopologyAccessor {
pub fn new() -> Self {
TopologyAccessor {
inner: Arc::new(RwLock::new(TopologyAccessorInner::new())),
}
}
pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> {
self.inner.read().await.into()
}
async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
self.inner.write().await.update(new_topology);
}
// only used by the client at startup to get a slightly more reasonable error message
// (currently displays as unused because health checker is disabled due to required changes)
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
match &self.inner.read().await.0 {
None => Err(NymTopologyError::EmptyNetworkTopology),
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
}
}
}
impl Default for TopologyAccessor {
fn default() -> Self {
TopologyAccessor::new()
}
}
pub struct TopologyRefresherConfig {
nym_api_urls: Vec<Url>,
refresh_rate: Duration,
client_version: String,
}
impl TopologyRefresherConfig {
pub fn new(nym_api_urls: Vec<Url>, refresh_rate: Duration, client_version: String) -> Self {
TopologyRefresherConfig {
nym_api_urls,
refresh_rate,
client_version,
}
}
}
pub struct TopologyRefresher {
validator_client: validator_client::client::NymApiClient,
client_version: String,
nym_api_urls: Vec<Url>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
currently_used_api: usize,
was_latest_valid: bool,
}
impl TopologyRefresher {
pub fn new(mut cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self {
cfg.nym_api_urls.shuffle(&mut thread_rng());
TopologyRefresher {
validator_client: validator_client::client::NymApiClient::new(
cfg.nym_api_urls[0].clone(),
),
client_version: cfg.client_version,
nym_api_urls: cfg.nym_api_urls,
topology_accessor,
refresh_rate: cfg.refresh_rate,
currently_used_api: 0,
was_latest_valid: true,
}
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
fn check_layer_distribution(&self, active_topology: &NymTopology) -> bool {
let mixes = active_topology.mixes();
let mixnodes_count = active_topology.num_mixnodes();
if active_topology.gateways().is_empty() {
return false;
}
// trivial check to see if have at least a single node on each layer (regardless of active set size)
if mixes.get(&1).is_none() || mixes.get(&2).is_none() || mixes.get(&3).is_none() {
return false;
}
let upper_bound = (mixnodes_count as f32 * 0.66) as usize;
let lower_bound = (mixnodes_count as f32 * 0.15) as usize;
let layer1 = mixes.get(&1).unwrap().len();
let layer2 = mixes.get(&2).unwrap().len();
let layer3 = mixes.get(&3).unwrap().len();
if layer1 < lower_bound || layer1 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer2 < lower_bound || layer2 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer3 < lower_bound || layer3 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
true
}
async fn get_current_compatible_topology(&self) -> Option<NymTopology> {
// TODO: optimization for the future:
// only refresh mixnodes on timer and refresh gateways only when
// we have to send to a new, unknown, gateway
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
None
} else {
Some(topology)
}
}
pub async fn refresh(&mut self) {
trace!("Refreshing the topology");
let new_topology = self.get_current_compatible_topology().await;
if new_topology.is_none() {
self.use_next_nym_api();
}
if new_topology.is_none() && self.was_latest_valid {
// if we failed to grab this topology, but the one before it was alright, let's assume
// validator had a tiny hiccup and use the old data
warn!("we're going to keep on using the old topology for this iteration");
self.was_latest_valid = false;
return;
} else if new_topology.is_some() {
self.was_latest_valid = true;
}
self.topology_accessor
.update_global_topology(new_topology)
.await;
}
pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> {
self.topology_accessor.ensure_is_routable().await
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.next() => {
self.refresh().await;
},
_ = shutdown.recv() => {
log::trace!("TopologyRefresher: Received shutdown");
},
}
}
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
}
@@ -1,154 +0,0 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
use nym_topology::{NymTopology, NymTopologyError};
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Notify, RwLock, RwLockReadGuard};
#[derive(Debug)]
pub struct TopologyAccessorInner {
controlled_manually: AtomicBool,
released_manual_control: Notify,
// `RwLock` *seems to* be the better approach for this as write access is only requested every
// few seconds, while reads are needed every single packet generated.
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
// approach than a `Mutex`
topology: RwLock<Option<NymTopology>>,
}
impl TopologyAccessorInner {
fn new() -> Self {
TopologyAccessorInner {
controlled_manually: AtomicBool::new(false),
released_manual_control: Notify::new(),
topology: RwLock::new(None),
}
}
async fn update(&self, new: Option<NymTopology>) {
*self.topology.write().await = new;
}
}
pub struct TopologyReadPermit<'a> {
permit: RwLockReadGuard<'a, Option<NymTopology>>,
}
impl<'a> Deref for TopologyReadPermit<'a> {
type Target = Option<NymTopology>;
fn deref(&self) -> &Self::Target {
&self.permit
}
}
impl<'a> TopologyReadPermit<'a> {
/// Using provided topology read permit, tries to get an immutable reference to the underlying
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
pub(crate) fn try_get_valid_topology_ref(
&'a self,
ack_recipient: &Recipient,
packet_recipient: Option<&Recipient>,
) -> Result<&'a NymTopology, NymTopologyError> {
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
let topology = self
.permit
.as_ref()
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
// 2. does it have any mixnode at all?
// 3. does it have any gateways at all?
// 4. does it have a mixnode on each layer?
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
// 5. does it contain OUR gateway (so that we could create an ack packet)?
if !topology.gateway_exists(ack_recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: ack_recipient.gateway().to_base58_string(),
});
}
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
if let Some(recipient) = packet_recipient {
if !topology.gateway_exists(recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: recipient.gateway().to_base58_string(),
});
}
}
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, Option<NymTopology>>> for TopologyReadPermit<'a> {
fn from(read_permit: RwLockReadGuard<'a, Option<NymTopology>>) -> Self {
TopologyReadPermit {
permit: read_permit,
}
}
}
#[derive(Clone, Debug)]
pub struct TopologyAccessor {
inner: Arc<TopologyAccessorInner>,
}
impl TopologyAccessor {
pub fn new() -> Self {
TopologyAccessor {
inner: Arc::new(TopologyAccessorInner::new()),
}
}
pub fn controlled_manually(&self) -> bool {
self.inner.controlled_manually.load(Ordering::SeqCst)
}
pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> {
self.inner.topology.read().await.into()
}
pub(crate) async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
self.inner.update(new_topology).await;
}
pub(crate) async fn wait_for_released_manual_control(&self) {
self.inner.released_manual_control.notified().await
}
pub async fn current_topology(&self) -> Option<NymTopology> {
self.inner.topology.read().await.clone()
}
pub async fn manually_change_topology(&self, new_topology: NymTopology) {
self.inner.controlled_manually.store(true, Ordering::SeqCst);
self.inner.update(Some(new_topology)).await;
}
pub fn release_manual_control(&self) {
self.inner
.controlled_manually
.store(false, Ordering::SeqCst);
self.inner.released_manual_control.notify_waiters();
}
// only used by the client at startup to get a slightly more reasonable error message
// (currently displays as unused because health checker is disabled due to required changes)
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
match self.inner.topology.read().await.deref() {
None => Err(NymTopologyError::EmptyNetworkTopology),
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
}
}
}
impl Default for TopologyAccessor {
fn default() -> Self {
TopologyAccessor::new()
}
}
@@ -1,115 +0,0 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
use futures::StreamExt;
use log::*;
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::NymTopologyError;
use std::time::Duration;
mod accessor;
pub(crate) mod nym_api_provider;
// TODO: move it to config later
const MAX_FAILURE_COUNT: usize = 10;
pub struct TopologyRefresherConfig {
refresh_rate: Duration,
}
impl TopologyRefresherConfig {
pub fn new(refresh_rate: Duration) -> Self {
TopologyRefresherConfig { refresh_rate }
}
}
pub struct TopologyRefresher {
topology_provider: Box<dyn TopologyProvider>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
consecutive_failure_count: usize,
}
impl TopologyRefresher {
pub fn new(
cfg: TopologyRefresherConfig,
topology_accessor: TopologyAccessor,
topology_provider: Box<dyn TopologyProvider>,
) -> Self {
TopologyRefresher {
topology_provider,
topology_accessor,
refresh_rate: cfg.refresh_rate,
consecutive_failure_count: 0,
}
}
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider>) {
self.topology_provider = provider;
}
pub async fn try_refresh(&mut self) {
trace!("Refreshing the topology");
if self.topology_accessor.controlled_manually() {
info!("topology is being controlled manually - we're going to wait until the control is released...");
self.topology_accessor
.wait_for_released_manual_control()
.await;
}
let new_topology = self.topology_provider.get_new_topology().await;
if new_topology.is_none() {
warn!("failed to obtain new network topology");
}
if new_topology.is_none() && self.consecutive_failure_count < MAX_FAILURE_COUNT {
// if we failed to grab this topology, but the one before it was alright, let's assume
// validator had a tiny hiccup and use the old data
warn!("we're going to keep on using the old topology for this iteration");
self.consecutive_failure_count += 1;
return;
} else if new_topology.is_some() {
self.consecutive_failure_count = 0;
}
self.topology_accessor
.update_global_topology(new_topology)
.await;
}
pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> {
self.topology_accessor.ensure_is_routable().await
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.next() => {
self.try_refresh().await;
},
_ = shutdown.recv() => {
log::trace!("TopologyRefresher: Received shutdown");
},
}
}
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
}
@@ -1,106 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use log::{error, warn};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::{nym_topology_from_detailed, NymTopology, NymTopologyError};
use rand::prelude::SliceRandom;
use rand::thread_rng;
use url::Url;
pub(crate) struct NymApiTopologyProvider {
validator_client: validator_client::client::NymApiClient,
nym_api_urls: Vec<Url>,
client_version: String,
currently_used_api: usize,
}
impl NymApiTopologyProvider {
pub(crate) fn new(mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
NymApiTopologyProvider {
validator_client: validator_client::client::NymApiClient::new(nym_api_urls[0].clone()),
nym_api_urls,
client_version,
currently_used_api: 0,
}
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
fn check_layer_distribution(
&self,
active_topology: &NymTopology,
) -> Result<(), NymTopologyError> {
let lower_threshold = 0.15;
let upper_threshold = 0.66;
active_topology.ensure_even_layer_distribution(lower_threshold, upper_threshold)
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if let Err(err) = self.check_layer_distribution(&topology) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}");
self.use_next_nym_api();
None
} else {
Some(topology)
}
}
}
// hehe, wasm
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_current_compatible_topology().await
}
}
#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_current_compatible_topology().await
}
}
+2 -24
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_config::{NymConfig, OptionalSet, DB_FILE_NAME};
use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
@@ -579,7 +579,7 @@ impl<T: NymConfig> Client<T> {
}
fn default_database_path(id: &str) -> PathBuf {
T::default_data_directory(id).join(CRED_DB_FILE_NAME)
T::default_data_directory(id).join(DB_FILE_NAME)
}
}
@@ -697,17 +697,6 @@ pub enum ExtendedPacketSize {
Extended8,
Extended16,
Extended32,
Extended10,
Extended15,
Extended20,
Extended25,
Extended50,
Extended100,
Extended150,
Extended200,
Extended250,
Extended500,
}
impl Default for DebugConfig {
@@ -745,17 +734,6 @@ impl From<ExtendedPacketSize> for PacketSize {
ExtendedPacketSize::Extended8 => PacketSize::ExtendedPacket8,
ExtendedPacketSize::Extended16 => PacketSize::ExtendedPacket16,
ExtendedPacketSize::Extended32 => PacketSize::ExtendedPacket32,
ExtendedPacketSize::Extended10 => PacketSize::ExtendedPacket10,
ExtendedPacketSize::Extended15 => PacketSize::ExtendedPacket15,
ExtendedPacketSize::Extended20 => PacketSize::ExtendedPacket20,
ExtendedPacketSize::Extended25 => PacketSize::ExtendedPacket25,
ExtendedPacketSize::Extended50 => PacketSize::ExtendedPacket50,
ExtendedPacketSize::Extended100 => PacketSize::ExtendedPacket100,
ExtendedPacketSize::Extended150 => PacketSize::ExtendedPacket150,
ExtendedPacketSize::Extended200 => PacketSize::ExtendedPacket200,
ExtendedPacketSize::Extended250 => PacketSize::ExtendedPacket250,
ExtendedPacketSize::Extended500 => PacketSize::ExtendedPacket500,
}
}
}
+1 -27
View File
@@ -3,7 +3,6 @@
use gateway_client::error::GatewayClientError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError;
use validator_client::ValidatorClientError;
@@ -54,32 +53,7 @@ pub enum ClientCoreError {
GatewayOwnerUnknown,
#[error("The address of the gateway is unknown - did you run init?")]
GatewayAddressUnknown,
#[error("The gateway is malformed: {source}")]
MalformedGateway {
#[from]
source: GatewayConversionError,
},
#[error("failed to establish connection to gateway: {source}")]
GatewayConnectionFailure {
#[from]
source: tungstenite::Error,
},
#[cfg(target_arch = "wasm32")]
#[error("failed to establish gateway connection (wasm)")]
GatewayJsConnectionFailure,
#[error("Gateway connection was abruptly closed")]
GatewayConnectionAbruptlyClosed,
#[error("Timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("No ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
GatwayAddressUnknown,
#[error("failed to register receiver for reconstructed mixnet messages")]
FailedToRegisterReceiver,
+24 -195
View File
@@ -6,223 +6,52 @@ use crate::{
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError,
};
use futures::{SinkExt, StreamExt};
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::SigningNyxdClient;
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use log::{debug, info, trace, warn};
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, thread_rng, Rng};
use rand::{seq::SliceRandom, thread_rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use tungstenite::Message;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::SigningNyxdClient;
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::SigningNyxdClient;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Instant;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
#[cfg(target_arch = "wasm32")]
type WsConn = JSWebsocket;
const MEASUREMENTS: usize = 3;
#[cfg(not(target_arch = "wasm32"))]
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
const PING_TIMEOUT: Duration = Duration::from_millis(1000);
struct GatewayWithLatency {
gateway: gateway::Node,
latency: Duration,
}
impl GatewayWithLatency {
fn new(gateway: gateway::Node, latency: Duration) -> Self {
GatewayWithLatency { gateway, latency }
}
}
async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: Vec<Url>,
) -> Result<Vec<gateway::Node>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
) -> Result<gateway::Node, ClientCoreError> {
let nym_api = validator_servers
.choose(&mut thread_rng())
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let client = validator_client::client::NymApiClient::new(nym_api.clone());
let validator_client = validator_client::client::NymApiClient::new(nym_api.clone());
log::trace!("Fetching list of gateways from: {}", nym_api);
let gateways = client.get_cached_gateways().await?;
let gateways = validator_client.get_cached_gateways().await?;
let valid_gateways = gateways
.into_iter()
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<gateway::Node>>();
// we were always filtering by version so I'm not removing that 'feature'
let filtered_gateways = valid_gateways.filter_by_version(env!("CARGO_PKG_VERSION"));
Ok(filtered_gateways)
}
#[cfg(not(target_arch = "wasm32"))]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
Ok(Err(conn_failure)) => Err(conn_failure.into()),
Ok(Ok((stream, _))) => Ok(stream),
}
}
#[cfg(target_arch = "wasm32")]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
}
async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
let addr = gateway.clients_address();
trace!(
"establishing connection to {} ({addr})...",
gateway.identity_key,
);
let mut stream = connect(&addr).await?;
let mut results = Vec::new();
for _ in 0..MEASUREMENTS {
let measurement_future = async {
let ping_content = vec![1, 2, 3];
let start = Instant::now();
stream.send(Message::Ping(ping_content.clone())).await?;
match stream.next().await {
Some(Ok(Message::Pong(content))) => {
if content == ping_content {
let elapsed = Instant::now().duration_since(start);
trace!("current ping time: {elapsed:?}");
results.push(elapsed);
} else {
warn!("received a pong message with different content? wtf.")
}
}
Some(Ok(_)) => warn!("received a message that's not a pong!"),
Some(Err(err)) => return Err(err.into()),
None => return Err(ClientCoreError::GatewayConnectionAbruptlyClosed),
}
Ok::<(), ClientCoreError>(())
};
// thanks to wasm we can't use tokio::time::timeout : (
#[cfg(not(target_arch = "wasm32"))]
let timeout = tokio::time::sleep(PING_TIMEOUT);
#[cfg(not(target_arch = "wasm32"))]
tokio::pin!(timeout);
#[cfg(target_arch = "wasm32")]
let mut timeout = wasm_timer::Delay::new(PING_TIMEOUT);
tokio::select! {
_ = &mut timeout => {
warn!("timed out while trying to perform measurement...")
}
res = measurement_future => res?,
}
}
let count = results.len() as u64;
if count == 0 {
return Err(ClientCoreError::NoGatewayMeasurements {
identity: gateway.identity_key.to_base58_string(),
});
}
let sum: Duration = results.into_iter().sum();
let avg = Duration::from_nanos(sum.as_nanos() as u64 / count);
Ok(GatewayWithLatency::new(gateway, avg))
}
async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
info!("choosing gateway by latency...");
let mut gateways_with_latency = Vec::new();
for gateway in gateways {
let id = *gateway.identity();
trace!("measuring latency to {id}...");
let with_latency = match measure_latency(gateway).await {
Ok(res) => res,
Err(err) => {
warn!("failed to measure {id}: {err}");
continue;
}
};
debug!(
"{id} ({}): {:?}",
with_latency.gateway.location, with_latency.latency
);
gateways_with_latency.push(with_latency)
}
let chosen = gateways_with_latency
.choose_weighted(rng, |item| 1. / item.latency.as_secs_f32())
.expect("invalid selection weight!");
info!(
"chose gateway {} (located at {}) with average latency of {:?}",
chosen.gateway.identity_key, chosen.gateway.location, chosen.latency
);
Ok(chosen.gateway.clone())
}
fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
gateways
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
}
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
// if we have chosen particular gateway - use it, otherwise choose a random one.
// (remember that in active topology all gateways have at least 100 reputation so should
// be working correctly)
if let Some(gateway_id) = chosen_gateway_id {
filtered_gateways
.iter()
.find(|gateway| gateway.identity_key == gateway_id)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string()))
.cloned()
} else {
uniformly_random_gateway(&mut rng, gateways)
filtered_gateways
.choose(&mut rand::thread_rng())
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
}
}
+4 -10
View File
@@ -77,11 +77,9 @@ pub async fn register_with_gateway(
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?;
let gateway = helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id).await?;
log::debug!("Querying gateway gives: {}", gateway);
let our_identity = key_manager.identity_keypair();
@@ -104,7 +102,6 @@ pub async fn setup_gateway_from_config<C, T>(
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config<T>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
where
C: NymConfig + ClientCoreConfigTrait,
@@ -120,12 +117,9 @@ where
}
// Else, we preceed by querying the nym-api
let gateway = helpers::query_gateway_details(
config.get_nym_api_endpoints(),
user_chosen_gateway_id,
by_latency,
)
.await?;
let gateway =
helpers::query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id)
.await?;
log::debug!("Querying gateway gives: {}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and
+3 -3
View File
@@ -15,10 +15,10 @@ thiserror = "1.0"
url = "2.2"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
nym-coconut-interface = { path = "../../common/coconut-interface" }
coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
nym-bin-common = { path = "../../common/bin-common"}
nym-network-defaults = { path = "../../common/network-defaults" }
+5 -5
View File
@@ -7,11 +7,11 @@ use nym_bin_common::completions::ArgShell;
use rand::rngs::OsRng;
use std::str::FromStr;
use nym_coconut_interface::{Base58, Parameters};
use nym_credential_storage::storage::Storage;
use nym_credential_storage::PersistentStorage;
use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use nym_credentials::coconut::utils::obtain_aggregate_signature;
use coconut_interface::{Base58, Parameters};
use credential_storage::storage::Storage;
use credential_storage::PersistentStorage;
use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use credentials::coconut::utils::obtain_aggregate_signature;
use nym_crypto::asymmetric::{encryption, identity};
use nym_network_defaults::VOUCHER_INFO;
use validator_client::nyxd::traits::DkgQueryClient;
+2 -2
View File
@@ -4,8 +4,8 @@
use std::time::SystemTimeError;
use thiserror::Error;
use nym_credential_storage::error::StorageError;
use nym_credentials::error::Error as CredentialError;
use credential_storage::error::StorageError;
use credentials::error::Error as CredentialError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use validator_client::nyxd::error::NyxdError;
+5 -11
View File
@@ -11,7 +11,7 @@ use commands::*;
use error::Result;
use log::*;
use nym_bin_common::completions::fig_generate;
use nym_config::{CRED_DB_FILE_NAME, DATA_DIR};
use nym_config::{DATA_DIR, DB_FILE_NAME};
use nym_network_defaults::{setup_env, NymNetworkDetails};
use std::process::exit;
use std::time::{Duration, SystemTime};
@@ -51,11 +51,8 @@ async fn block_until_coconut_is_available<C: Clone + CosmWasmClient + Send + Syn
break;
} else {
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
let secs_until_final = epoch
.final_timestamp_secs()
.saturating_sub(current_timestamp_secs)
+ 1;
// Use 20 additional seconds to avoid the exact moment of going into the final epoch state
let secs_until_final = epoch.final_timestamp_secs() + 20 - current_timestamp_secs;
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
std::thread::sleep(Duration::from_secs(secs_until_final));
}
@@ -73,11 +70,8 @@ async fn main() -> Result<()> {
match args.command {
Command::Run(r) => {
let db_path = r
.client_home_directory
.join(DATA_DIR)
.join(CRED_DB_FILE_NAME);
let shared_storage = nym_credential_storage::initialise_storage(db_path).await;
let db_path = r.client_home_directory.join(DATA_DIR).join(DB_FILE_NAME);
let shared_storage = credential_storage::initialise_storage(db_path).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
let network_details = NymNetworkDetails::new_from_env();
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use credentials::coconut::bandwidth::BandwidthVoucher;
use std::fs::{create_dir_all, read_dir, File};
use std::io::{Read, Write};
use std::path::PathBuf;
+2 -2
View File
@@ -1,8 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::Parameters;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use coconut_interface::Parameters;
use credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.13"
version = "1.1.10"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -36,10 +36,10 @@ tokio-tungstenite = "0.14" # websocket
## internal
nym-bin-common = { path = "../../common/bin-common" }
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
nym-coconut-interface = { path = "../../common/coconut-interface" }
coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
+1 -2
View File
@@ -74,7 +74,7 @@ impl SocketClient {
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(
nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await,
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
client,
)
}
@@ -101,7 +101,6 @@ impl SocketClient {
let ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
..
} = client_state;
let websocket_handler = websocket::HandlerBuilder::new(
-6
View File
@@ -25,11 +25,6 @@ pub(crate) struct Init {
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
@@ -148,7 +143,6 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
register_gateway,
user_chosen_gateway_id,
config.get_base(),
args.latency_based_selection,
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
+9 -9
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.13"
version = "1.1.10"
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"
@@ -29,26 +29,26 @@ url = "2.2"
# internal
nym-bin-common = { path = "../../common/bin-common" }
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
nym-coconut-interface = { path = "../../common/coconut-interface" }
coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
nym-credential-storage = { path = "../../common/credential-storage", optional = true }
credential-storage = { path = "../../common/credential-storage", optional = true }
mobile-storage = { path = "../../common/mobile-storage", optional = true }
nym-credentials = { path = "../../common/credentials" }
credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nym-network-defaults = { path = "../../common/network-defaults" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
nym-pemstore = { path = "../../common/pemstore" }
nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
nym-service-providers-common = { path = "../../service-providers/common" }
nym-socks5-requests = { path = "../../common/socks5/requests" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
service-providers-common = { path = "../../service-providers/common" }
socks5-requests = { path = "../../common/socks5/requests" }
nym-task = { path = "../../common/task" }
nym-topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] }
[features]
default = ["nym-credential-storage"]
default = ["credential-storage"]
eth = []
mobile = ["mobile-storage", "gateway-client/mobile"]
+2 -2
View File
@@ -7,10 +7,10 @@ pub use client_core::config::MISSING_VALUE;
use client_core::config::{ClientCoreConfigTrait, DebugConfig};
use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
use nym_config::{NymConfig, OptionalSet};
use nym_service_providers_common::interface::ProviderInterfaceVersion;
use nym_socks5_requests::Socks5ProtocolVersion;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use service_providers_common::interface::ProviderInterfaceVersion;
use socks5_requests::Socks5ProtocolVersion;
use std::fmt::Debug;
use std::path::PathBuf;
use std::str::FromStr;
+2 -2
View File
@@ -92,7 +92,7 @@ impl NymClient {
#[cfg(not(feature = "mobile"))]
let storage =
nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await;
credential_storage::initialise_storage(config.get_base().get_database_path()).await;
#[cfg(feature = "mobile")]
let storage = mobile_storage::PersistentStorage {};
@@ -123,7 +123,7 @@ impl NymClient {
let ClientState {
shared_lane_queue_lengths,
..
reply_controller_sender: _,
} = client_status;
let authenticator = Authenticator::new(auth_methods, allowed_users);
-6
View File
@@ -37,11 +37,6 @@ pub(crate) struct Init {
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
@@ -154,7 +149,6 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
register_gateway,
user_chosen_gateway_id,
config.get_base(),
args.latency_based_selection,
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::socks::types::SocksProxyError;
use client_core::error::ClientCoreError;
use nym_socks5_requests::{ConnectionError, ConnectionId};
use socks5_requests::{ConnectionError, ConnectionId};
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
+8 -8
View File
@@ -8,19 +8,19 @@ use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use log::*;
use nym_service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use nym_socks5_proxy_helpers::connection_controller::{
ConnectionReceiver, ControllerCommand, ControllerSender,
};
use nym_socks5_proxy_helpers::proxy_runner::ProxyRunner;
use nym_socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_task::connections::{LaneQueueLengths, TransmissionLane};
use nym_task::TaskClient;
use pin_project::pin_project;
use proxy_helpers::connection_controller::{
ConnectionReceiver, ControllerCommand, ControllerSender,
};
use proxy_helpers::proxy_runner::ProxyRunner;
use rand::RngCore;
use service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
+3 -3
View File
@@ -4,11 +4,11 @@ use log::*;
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
use nym_service_providers_common::interface::{ControlResponse, ResponseContent};
use nym_socks5_proxy_helpers::connection_controller::ControllerSender;
use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::TaskClient;
use proxy_helpers::connection_controller::ControllerSender;
use service_providers_common::interface::{ControlResponse, ResponseContent};
use socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use crate::error::Socks5ClientError;
+1 -1
View File
@@ -8,10 +8,10 @@ use client_core::client::{
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
};
use log::*;
use nym_socks5_proxy_helpers::connection_controller::Controller;
use nym_sphinx::addressing::clients::Recipient;
use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths};
use nym_task::TaskClient;
use proxy_helpers::connection_controller::Controller;
use std::net::SocketAddr;
use tap::TapFallible;
use tokio::net::TcpListener;
+1 -1
View File
@@ -1,4 +1,4 @@
use nym_socks5_requests::Socks5RequestError;
use socks5_requests::Socks5RequestError;
use std::string::FromUtf8Error;
use thiserror::Error;
+79 -67
View File
@@ -1,71 +1,83 @@
{
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": ["airbnb-base", "airbnb-typescript/base", "prettier"],
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["**/*.test.[jt]s", "**/*.spec.[jt]s"]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
]
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": ["plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"prettier"],
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["**/*.test.ts", "**/*.spec.ts"]
}
"error",
{
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
],
"ignorePatterns": ["tsconfig.json", "*.d.ts", "dist/**/*", "dist", "node_modules"]
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
]
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
]
}
+1 -2
View File
@@ -4,5 +4,4 @@ coverage
dist
docs
examples/accounts
node_modules
.env
node_modules
+1 -3
View File
@@ -1,5 +1,3 @@
coverage
node_modules
tests
src
type
tests
+25 -24
View File
@@ -1,39 +1,40 @@
# Nym Validator Client (Typescript)
Nym Validator Client
====================
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
Include the Nym Validator in your project:
Running examples
-----------------
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
Running tests
-------------
```
yarn add @nymproject/nym-validator-client
npm test
```
Connect to validator and make queries
You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
```
import Validator from '@nymproject/nym-validator-client'
const main = async () => {
const client = await Validator.connectForQuery(rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom)
client.getBalance(address)
}
cargo watch -s "cd clients/validator && npm test"
```
Connect to validator for performing actions
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
```
import Validator from '@nymproject/nym-validator-client'
Generating Documentation
------------------------
const main = async () => {
You can generate docs by running `npm run docs`. Generated output will appear in the `docs` directory.
const client = await Validator.connect(mnemonic, rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom)
Packaging
------------------------
const res = await client.send(address, [{ amount: '10000000', denom: 'unym' }]);
If you're a Nym platform developer who's made changes to the client and wants to re-publish the package to NPM, here's how you do it:
}
```
1. Bump the version number (use SemVer)
1. `npm run build`
1. `npm login` (if you haven't already)
1. `npm publish`
+18 -34
View File
@@ -4,23 +4,15 @@
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
"repository": "https://github.com/nymtech/nym",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"types": "dist/index.d.ts",
"scripts": {
"build": "rollup -c ./rollup.config.mjs",
"build:types": "rollup-type-bundler --dist ./dist/nym-validator-client",
"build:prod": "sh ./scripts/build-prod.sh",
"test": "ts-mocha -p ./tsconfig.test.json ./src/tests/**/*.test.ts",
"testmock": "ts-mocha -p ./tsconfig.test.json ./src/tests/mock/*.test.ts",
"build": "tsc",
"test": "ts-mocha tests/**/*.test.ts",
"coverage": "nyc npm test",
"clean": "rm -rf ./dist",
"lint": "eslint",
"lint:fix": "eslint --fix",
"lint:tsc": "tsc --noEmit",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"docs": "typedoc --out docs src/index.ts"
},
"files": [
"./dist/*"
],
"keywords": [],
"author": "Nym Technologies SA (https://nymtech.net)",
"contributors": [
@@ -29,14 +21,6 @@
],
"license": "Apache-2.0",
"devDependencies": {
"@favware/rollup-type-bundler": "^2.0.0",
"@nymproject/types": "^1.0.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"rollup": "^3.17.2",
"rollup-plugin-dts": "^5.2.0",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"eslint": "^7.18.0",
@@ -47,21 +31,21 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-prettier": "^4.0.0",
"expect": "^28.1.3",
"mocha": "^10.0.0",
"prettier": "^2.5.1",
"ts-mocha": "^10.0.0",
"typedoc": "^0.22.13",
"typescript": "^4.6.2",
"cosmjs-types": "^0.4.1",
"dotenv": "^16.0.3",
"expect": "^28.1.3",
"moq.ts": "^7.3.4",
"@cosmjs/cosmwasm-stargate": "^0.29.5",
"@cosmjs/crypto": "^0.29.5",
"@cosmjs/math": "^0.29.5",
"@cosmjs/proto-signing": "^0.29.5",
"@cosmjs/stargate": "^0.29.5",
"@cosmjs/tendermint-rpc": "^0.29.5",
"axios": "^1.3.3"
"ts-mocha": "^10.0.0",
"typescript": "^4.6.2"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.28.0",
"@cosmjs/crypto": "^0.28.0",
"@cosmjs/math": "^0.28.0",
"@cosmjs/proto-signing": "^0.28.0",
"@cosmjs/stargate": "^0.28.0",
"@cosmjs/tendermint-rpc": "^0.28.0",
"axios": "^0.26.1",
"cosmjs-types": "^0.4.1"
}
}
-15
View File
@@ -1,15 +0,0 @@
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import json from '@rollup/plugin-json';
import commonjs from '@rollup/plugin-commonjs';
export default [
{
input: './src/index.ts',
output: {
dir: 'dist/nym-validator-client',
format: 'cjs',
},
plugins: [resolve(), typescript(), commonjs(), json()],
},
];
-31
View File
@@ -1,31 +0,0 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
rm -rf ./dist || true
rm -rf ../../dist || true
# Bundle application
yarn build
# Bundle types
yarn build:types
# Build package.json for bundle
node ./scripts/buildPackageJson.mjs
# Copy README
cp README.md dist/nym-validator-client
# move the output outside of the yarn/npm workspaces
mv ./dist ../../
echo "Output can be found in:"
realpath ../../dist
@@ -1,20 +0,0 @@
import * as fs from 'fs';
// parse the package.json from the SDK, so we can keep fields like the name and version
const json = JSON.parse(fs.readFileSync('./package.json').toString());
// defaults (NB: these are in the output file locations)
const main = 'index.js';
const types = 'index.d.ts';
// make a package.json for the bundle
const packageJson = {
name: json.name,
version: json.version,
license: json.license,
author: json.author,
main,
types,
};
fs.writeFileSync('./dist/nym-validator-client/package.json', JSON.stringify(packageJson, null, 2));
+9 -1
View File
@@ -1,6 +1,5 @@
import { Decimal } from '@cosmjs/math';
import { Coin } from '@cosmjs/stargate';
import { CoinMap } from './types/shared';
// NARROW NO-BREAK SPACE (U+202F)
const thinSpace = '\u202F';
@@ -35,6 +34,15 @@ export function nativeToPrintable(nativeValue: string): string {
return Decimal.fromAtomics(nativeValue, 6).toString();
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
}
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
if (!coinMap) return coin;
+75 -133
View File
@@ -1,3 +1,6 @@
import { Bip39, Random } from '@cosmjs/crypto';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { coin as cosmosCoin, Coin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate';
import {
ExecuteResult,
InstantiateOptions,
@@ -5,37 +8,45 @@ import {
MigrateResult,
UploadResult,
} from '@cosmjs/cosmwasm-stargate';
import { Bip39, Random } from '@cosmjs/crypto';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { Coin, coin as cosmosCoin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate';
import SigningClient, { ISigningClient } from './signing-client';
import {
ContractStateParams,
Delegation,
Gateway,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNode,
MixNodeBond,
MixNodeCostParams,
MixNodeDetails,
MixNodeRewarding,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
RewardingParams,
StakeSaturationResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
PagedMixnodeResponse,
} from './types';
import {
CoinMap,
displayAmountToNative,
MappedCoin,
nativeCoinToDisplay,
nativeToPrintable,
printableBalance,
printableCoin,
} from './currency';
import QueryClient from './query-client';
import SigningClient, { ISigningClient } from './signing-client';
import { ContractState } from './types/shared';
import { nymGasPrice } from './stargate-helper';
export { coins, coin } from '@cosmjs/stargate';
export { Coin };
export {
displayAmountToNative,
nativeCoinToDisplay,
printableCoin,
printableBalance,
nativeToPrintable,
MappedCoin,
CoinMap,
};
export { nymGasPrice };
export interface INymClient {
readonly mixnetContract: string;
@@ -136,7 +147,7 @@ export default class ValidatorClient implements INymClient {
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
async getBalance(address: string): Promise<Coin> {
getBalance(address: string): Promise<Coin> {
return this.client.getBalance(address, this.denom);
}
@@ -148,39 +159,15 @@ export default class ValidatorClient implements INymClient {
return this.client.getCachedMixnodes();
}
async getStakeSaturation(mixId: number): Promise<StakeSaturationResponse> {
return this.client.getStakeSaturation(this.mixnetContract, mixId);
}
async getActiveMixnodes(): Promise<MixNodeDetails[]> {
async getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.client.getActiveMixnodes();
}
async getUnbondedMixNodeInformation(mixId: number): Promise<UnbondedMixnodeResponse> {
return this.client.getUnbondedMixNodeInformation(this.mixnetContract, mixId);
}
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.client.getRewardedMixnodes();
}
async getMixnodeRewardingDetails(mixId: number): Promise<MixNodeRewarding> {
return this.client.getMixnodeRewardingDetails(this.mixnetContract, mixId);
}
async getOwnedMixnode(address: string): Promise<MixOwnershipResponse> {
return this.client.getOwnedMixnode(this.mixnetContract, address);
}
async ownsGateway(address: string): Promise<GatewayOwnershipResponse> {
return this.client.ownsGateway(this.mixnetContract, address);
}
async getLayerDistribution(): Promise<LayerDistribution> {
return this.client.getLayerDistribution(this.mixnetContract);
}
public async getMixnetContractSettings(): Promise<ContractState> {
public async getMixnetContractSettings(): Promise<ContractStateParams> {
return this.client.getStateParams(this.mixnetContract);
}
@@ -188,74 +175,29 @@ export default class ValidatorClient implements INymClient {
return this.client.getContractVersion(this.mixnetContract);
}
public async getVestingContractVersion(): Promise<MixnetContractVersion> {
return this.client.getContractVersion(this.vestingContract);
public async getRewardPool(): Promise<string> {
return this.client.getRewardPool(this.mixnetContract);
}
public async getSpendableCoins(vestingAccountAddress: string): Promise<MixnetContractVersion> {
return this.client.getSpendableCoins(this.vestingContract, vestingAccountAddress);
public async getCirculatingSupply(): Promise<string> {
return this.client.getCirculatingSupply(this.mixnetContract);
}
public async getRewardParams(): Promise<RewardingParams> {
return this.client.getRewardParams(this.mixnetContract);
public async getSybilResistancePercent(): Promise<number> {
return this.client.getSybilResistancePercent(this.mixnetContract);
}
async getUnbondedMixNodes(): Promise<UnbondedMixnodeResponse[]> {
let mixNodes: UnbondedMixnodeResponse[] = [];
const limit = 50;
let startAfter;
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
public async getIntervalRewardPercent(): Promise<number> {
return this.client.getIntervalRewardPercent(this.mixnetContract);
}
public async getMixNodeBonds(): Promise<MixNodeBond[]> {
public async getAllNyxdMixnodes(): Promise<MixNodeBond[]> {
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,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getMixNodesDetailed(): Promise<MixNodeDetails[]> {
let mixNodes: MixNodeDetails[] = [];
const limit = 50;
let startAfter;
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
this.mixnetContract,
limit,
startAfter,
);
const pagedResponse: PagedMixnodeResponse = await this.client.getMixNodesPaged(this.mixnetContract, limit);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
@@ -268,24 +210,37 @@ export default class ValidatorClient implements INymClient {
}
public async getAllNyxdGateways(): Promise<GatewayBond[]> {
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract);
return pagedResponse.nodes;
let gateways: GatewayBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract, limit);
gateways = gateways.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return gateways;
}
/**
* Gets list of all delegations towards particular mixnode.
*
* @param mix_id identity of the node to which the delegation was sent
* @param mixIdentity identity of the node to which the delegation was sent
*/
public async getAllNyxdSingleMixnodeDelegations(mix_id: number): Promise<Delegation[]> {
public async getAllNyxdSingleMixnodeDelegations(mixIdentity: string): Promise<Delegation[]> {
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,
mix_id,
mixIdentity,
limit,
startAfter,
);
@@ -304,7 +259,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,
@@ -323,13 +278,13 @@ export default class ValidatorClient implements INymClient {
return delegations;
}
public async getAllNyxdDelegations(): Promise<Delegation[]> {
public async getAllNyxdNetworkDelegations(): Promise<Delegation[]> {
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(
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllNetworkDelegationsPaged(
this.mixnetContract,
limit,
startAfter,
@@ -345,10 +300,6 @@ export default class ValidatorClient implements INymClient {
return delegations;
}
public async getDelegationDetails(mix_id: number, delegator: string): Promise<Delegation> {
return this.client.getDelegationDetails(this.mixnetContract, mix_id, delegator);
}
/**
* Generate a minimum gateway bond required to create a fresh mixnode.
*
@@ -357,7 +308,7 @@ export default class ValidatorClient implements INymClient {
public async minimumMixnodePledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.params.minimum_mixnode_pledge, this.prefix);
return cosmosCoin(stateParams.minimum_mixnode_pledge, this.prefix);
}
/**
@@ -368,7 +319,7 @@ export default class ValidatorClient implements INymClient {
public async minimumGatewayPledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.params.minimum_gateway_pledge, this.prefix);
return cosmosCoin(stateParams.minimum_gateway_pledge, this.prefix);
}
public async send(
@@ -442,21 +393,12 @@ export default class ValidatorClient implements INymClient {
public async bondMixNode(
mixNode: MixNode,
ownerSignature: string,
costParams: MixNodeCostParams,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).bondMixNode(
this.mixnetContract,
mixNode,
costParams,
ownerSignature,
pledge,
fee,
memo,
);
return (this.client as ISigningClient).bondMixNode(this.mixnetContract, mixNode, ownerSignature, pledge, fee, memo);
}
public async unbondMixNode(fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult> {
@@ -481,29 +423,29 @@ export default class ValidatorClient implements INymClient {
}
public async delegateToMixNode(
mixId: number,
mixIdentity: string,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixId, amount, fee, memo);
return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixIdentity, amount, fee, memo);
}
public async undelegateFromMixNode(
mixId: number,
mixIdentity: string,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixId, fee, memo);
return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixIdentity, fee, memo);
}
public async updateMixnodeConfig(
mixId: number,
mixIdentity: string,
fee: StdFee | 'auto' | number,
profitPercentage: number,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixId, profitPercentage, fee);
return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixIdentity, profitPercentage, fee);
}
public async updateContractStateParams(
+4 -3
View File
@@ -2,8 +2,9 @@
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import axios from 'axios';
import { GatewayBond, MixNodeBond, MixNodeDetails } from '@nymproject/types';
import { GatewayBond, MixNodeBond } from './types';
export const NYM_API_VERSION = '/v1';
export const NYM_API_GATEWAYS_PATH = `${NYM_API_VERSION}/gateways`;
@@ -16,7 +17,7 @@ export interface INymApiQuery {
getCachedGateways(): Promise<GatewayBond[]>;
getActiveMixnodes(): Promise<MixNodeDetails[]>;
getActiveMixnodes(): Promise<MixNodeBond[]>;
getRewardedMixnodes(): Promise<MixNodeBond[]>;
}
@@ -50,7 +51,7 @@ export default class NymApiQuerier implements INymApiQuery {
throw new Error('None of the provided validator APIs seem to be alive');
}
async getActiveMixnodes(): Promise<MixNodeDetails[]> {
async getActiveMixnodes(): Promise<MixNodeBond[]> {
const url = new URL(this.nymApiUrl);
url.pathname += NYM_API_ACTIVE_MIXNODES_PATH;
+56 -72
View File
@@ -2,24 +2,28 @@
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import { JsonObject } from '@cosmjs/cosmwasm-stargate';
// eslint-disable-next-line import/no-cycle
import { INyxdQuery } from './query-client';
import { Delegation, RewardingParams, StakeSaturationResponse } from '@nymproject/types';
import {
UnbondedMixnodeResponse,
ContractStateParams,
Delegation,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
LayerDistribution,
} from '@nymproject/types';
import { ContractState, SmartContractQuery } from './types/shared';
PagedMixnodeResponse,
RewardingStatus,
} from './types';
interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export default class NyxdQuerier implements INyxdQuery {
client: SmartContractQuery;
@@ -34,44 +38,15 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_node_bonds: {
get_mix_nodes: {
limit,
start_after: startAfter,
},
});
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes_detailed: {
limit,
start_after: startAfter,
},
});
}
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_stake_saturation: { mix_id: mixId },
});
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<any> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_rewarding_details: { mix_id: mixId },
});
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_gateways: {
@@ -81,51 +56,35 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_owned_mixnode: {
owns_mixnode: {
address,
},
});
}
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_unbonded_mix_nodes: { limit, start_after: startAfter },
});
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_unbonded_mix_node_information: { mix_id: mixId },
});
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_owned_gateway: {
owns_gateway: {
address,
},
});
}
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_state: {},
state_params: {},
});
}
getAllDelegationsPaged(
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_all_delegations: {
get_all_network_delegations: {
start_after: startAfter,
limit,
},
@@ -134,13 +93,13 @@ export default class NyxdQuerier implements INyxdQuery {
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mix_id: number,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_delegations: {
mix_id: mix_id,
mix_identity: mixIdentity,
start_after: startAfter,
limit,
},
@@ -162,10 +121,10 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegation_details: {
mix_id: mix_id,
mix_identity: mixIdentity,
delegator,
},
});
@@ -173,19 +132,44 @@ export default class NyxdQuerier implements INyxdQuery {
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_layer_distribution: {},
layer_distribution: {},
});
}
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_params: {},
get_reward_pool: {},
});
}
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.client.queryContractSmart(vestingContractAddress, {
vesting_account_address: vestingAccountAddress,
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_circulating_supply: {},
});
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_interval_reward_percent: {},
});
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_sybil_resistance_percent: {},
});
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_status: {
mix_identity: mixIdentity,
rewarding_interval_nonce: rewardingIntervalNonce,
},
});
}
}
+93 -84
View File
@@ -1,56 +1,75 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { CosmWasmClient, JsonObject } from '@cosmjs/cosmwasm-stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import {
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
// eslint-disable-next-line import/no-cycle
import NyxdQuerier from './nyxd-querier';
import {
ContractStateParams,
Delegation,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNodeDetails,
MixNodeBond,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
StakeSaturationResponse,
UnbondedMixnodeResponse,
MixNodeBond,
MixNodeRewarding,
} from '@nymproject/types';
import NymApiQuerier, { INymApiQuery } from './nym-api-querier';
import { ContractState, ICosmWasmQuery } from './types/shared';
import { RewardingParams } from '@nymproject/types';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
PagedMixnodeResponse,
RewardingStatus,
} from './types';
import NymApiQuerier, { INymApiQuery as INymApiQuery } from './nym-api-querier';
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export interface INyxdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse>;
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse>;
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractState>;
getAllDelegationsPaged(
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mix_id: number,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
@@ -60,15 +79,21 @@ export interface INyxdQuery {
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation>;
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation>;
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams>;
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse>;
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse>;
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding>;
getRewardPool(mixnetContractAddress: string): Promise<string>;
getCirculatingSupply(mixnetContractAddress: string): Promise<string>;
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number>;
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>;
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus>;
}
export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery {}
export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery { }
export default class QueryClient extends CosmWasmClient implements IQueryClient {
private nyxdQuerier: NyxdQuerier;
@@ -90,73 +115,41 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter);
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter);
}
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse> {
return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId);
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding> {
return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId);
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address);
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
}
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter);
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId);
}
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
}
getAllDelegationsPaged(
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nyxdQuerier.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter);
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mix_id: number,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter);
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
}
getDelegatorDelegationsPaged(
@@ -168,16 +161,36 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator);
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.nyxdQuerier.getRewardParams(mixnetContractAddress);
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
@@ -188,15 +201,11 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nymApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeDetails[]> {
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.nymApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.nymApiQuerier.getRewardedMixnodes();
}
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress);
}
}
+47 -73
View File
@@ -25,22 +25,15 @@ import {
MixnetContractVersion,
MixNode,
MixNodeBond,
MixNodeCostParams,
MixNodeDetails,
MixNodeRewarding,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
RewardingParams,
UnbondedMixnodeResponse,
} from '@nymproject/types';
PagedMixnodeResponse,
RewardingStatus,
} from './types';
import NymApiQuerier from './nym-api-querier';
import { ContractState } from './types/shared';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
@@ -150,7 +143,6 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
costParams: MixNodeCostParams,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
@@ -172,7 +164,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
delegateToMixNode(
mixnetContractAddress: string,
mixId: number,
mixIdentity: string,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
@@ -180,14 +172,14 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
undelegateFromMixNode(
mixnetContractAddress: string,
mixId: number,
mixIdentity: string,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
updateMixnodeConfig(
mixnetContractAddress: string,
mixId: number,
mixIdentity: string,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult>;
@@ -243,76 +235,44 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
// query related:
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.getContractVersion(mixnetContractAddress);
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter);
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter);
}
getStakeSaturation(mixnetContractAddress: string, mixId: number) {
return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId);
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId);
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding> {
return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId);
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address);
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
}
getAllDelegationsPaged(
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter);
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mix_id: number,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter);
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
}
getDelegatorDelegationsPaged(
@@ -324,16 +284,36 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator);
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.nyxdQuerier.getRewardParams(mixnetContractAddress);
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
@@ -344,7 +324,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nymApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeDetails[]> {
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.nymApiQuerier.getActiveMixnodes();
}
@@ -352,16 +332,11 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nymApiQuerier.getRewardedMixnodes();
}
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress);
}
// signing related:
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
costParams: MixNodeCostParams,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
@@ -373,7 +348,6 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
{
bond_mixnode: {
mix_node: mixNode,
cost_params: costParams,
owner_signature: ownerSignature,
},
},
@@ -440,7 +414,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
delegateToMixNode(
mixnetContractAddress: string,
mixId: number,
mixIdentity: string,
amount: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Delegation from Typescript',
@@ -450,7 +424,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
mixnetContractAddress,
{
delegate_to_mixnode: {
mix_id: mixId,
mix_identity: mixIdentity,
},
},
fee,
@@ -461,7 +435,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
undelegateFromMixNode(
mixnetContractAddress: string,
mixId: number,
mixIdentity: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Undelegation from Typescript',
): Promise<ExecuteResult> {
@@ -470,7 +444,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
mixnetContractAddress,
{
undelegate_from_mixnode: {
mix_id: mixId,
mix_identity: mixIdentity,
},
},
fee,
@@ -480,14 +454,14 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
updateMixnodeConfig(
mixnetContractAddress: string,
mixId: number,
mixIdentity: string,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_id: mixId } },
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_identity: mixIdentity } },
fee,
);
}
@@ -1,178 +0,0 @@
import expect from 'expect';
export const amountDemon = {
amount: expect.any(String),
denom: expect.any(String)
}
export const delegation = {
owner: expect.any(String),
mix_id: expect.any(Number),
cumulative_reward_ratio: expect.any(String),
amount: amountDemon,
height: expect.any(Number || BigInt),
proxy: expect.any(String || null)
}
export const detailedDelegation = {
delegation: delegation,
mixnode_still_bonded: expect.any(Boolean)
}
export const gateway = {
pledge_amount: amountDemon,
owner: expect.any(String),
block_height: expect.any(Number || BigInt),
gateway: {
host: expect.any(String),
mix_port: expect.any(Number),
clients_port: expect.any(Number),
location: expect.any(String),
sphinx_key: expect.any(String),
identity_key: expect.any(String),
version: expect.any(String),
},
proxy: expect.any(String || null)
}
export const pagedGateway = {
nodes: gateway,
per_page: expect.any(Number),
start_next_after: expect.any(Number)
}
export const ownGateway = {
address: expect.any(String),
gateway: gateway
}
export const rewardingdetails = {
cost_params: {
profit_margin_percent: expect.any(String),
interval_operating_cost: {
denom: expect.any(String),
amount: expect.any(String)
}
},
operator: expect.any(String),
delegates: expect.any(String),
total_unit_reward: expect.any(String),
unit_delegation: expect.any(String),
last_rewarded_epoch: expect.any(Number),
unique_delegations: expect.any(Number)
}
export const mix_node = {
host: expect.any(String),
mix_port: expect.any(Number),
verloc_port: expect.any(Number),
http_api_port: expect.any(Number),
sphinx_key: expect.any(String),
identity_key: expect.any(String),
version: expect.any(String)
}
export const mixnodebond = {
mix_id: expect.any(Number),
owner: expect.any(String),
original_pledge: {
denom: expect.any(String),
amount: expect.any(String)
},
layer: expect.any(String),
mix_node: mix_node,
proxy: expect.any(String) || null,
bonding_height: expect.any(Number || BigInt),
is_unbonding: expect.any(Boolean)
}
export const mixnode = {
bond_information: mixnodebond,
rewarding_details: rewardingdetails
}
export const ownedNode = {
address: expect.any(String),
mixnode_details: {
bond_information: mixnodebond,
rewarding_details: rewardingdetails
}
}
export const saturation = {
mix_id: expect.any(Number),
current_saturation: expect.any(String),
uncapped_saturation: expect.any(String)
}
export const contractVersion = {
build_timestamp: expect.any(String),
build_version: expect.any(String),
commit_sha: expect.any(String),
commit_timestamp: expect.any(String),
commit_branch: expect.any(String),
rustc_version: expect.any(String)
};
export const stateParams = {
minimum_gateway_pledge: amountDemon,
minimum_mixnode_pledge: expect.any(String),
mixnode_rewarded_set_size: expect.any(Number),
mixnode_active_set_size: expect.any(Number)
}
export const contract = {
owner: expect.any(Number),
rewarding_validator_address: expect.any(Number),
vesting_contract_address: expect.any(Number),
rewarding_denom: expect.any(String),
params: stateParams
}
export const rewardingnode = {
mix_id: expect.any(Number),
rewarding_details: rewardingdetails
}
export const unbondednode = {
mix_id: expect.any(Number),
unbonded_info: {
identity_key: expect.any(String),
owner: expect.any(String),
proxy: expect.any(String) || null,
unbonding_height: expect.any(Number)
}
}
export const allunbondednodes = [
expect.any(Number), {
identity_key: expect.any(String),
owner: expect.any(String),
proxy: expect.any(String) || null,
unbonding_height: expect.any(Number)
}
]
export const layerDistribution = {
layer1: expect.any(Number),
layer2: expect.any(Number),
layer3: expect.any(Number)
}
export const intervalRewardParams = {
reward_pool: expect.any(Number),
staking_supply: expect.any(Number),
staking_supply_scale_factor: expect.any(Number),
epoch_reward_budget: expect.any(Number),
stake_saturation_point: expect.any(Number),
sybil_resistance: expect.any(Number),
active_set_work_factor: expect.any(Number),
interval_pool_emission: expect.any(Number)
}
export const rewardingParams = {
interval: intervalRewardParams,
rewarded_set_size: expect.any(Number),
active_set_size: expect.any(Number)
}
@@ -1,19 +0,0 @@
import { Mock, Times } from 'moq.ts';
import expect from 'expect';
import { INyxdQuery } from '../../query-client';
export class TestHelper {
buildMethod = async <T>(methodName: string, args: any[], expectedResult: any): Promise<T> => {
const client = new Mock<INyxdQuery>()
.setup((nym) => nym[methodName](...args))
.returns(Promise.resolve(expectedResult));
const obj = client.object();
const actualDetails = await obj[methodName](...args);
client.verify((nym) => nym[methodName](...args), Times.Exactly(1));
expect(Object.keys([actualDetails])).toEqual(Object.keys(expectedResult));
expect(actualDetails).toBeDefined();
return actualDetails;
};
}
@@ -1,40 +0,0 @@
import expect from 'expect';
import { Delegation } from '@nymproject/types';
import { PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse } from '../../types/shared-types';
import { TestHelper } from './client';
import { mixnet, mixnodeowneraddress, mix_id } from './testData';
describe('Delegation mock tests', () => {
const testHelper = new TestHelper();
it('get Delegation Details', () => {
const execute = testHelper.buildMethod('getDelegationDetails', [mixnet, mix_id, mixnodeowneraddress], <Delegation>{
owner: mixnodeowneraddress,
mix_id: 0,
cumulative_reward_ratio: '',
amount: {
denom: 'nym',
amount: '10',
},
height: 1314134144132n,
proxy: 'null',
});
expect(execute).toBeTruthy();
});
it('get All Delegations Paged', () => {
const execute = testHelper.buildMethod('getAllDelegationsPaged', [mixnet], <PagedAllDelegationsResponse>{
delegations: [],
});
expect(execute).toBeTruthy();
});
it('get Delegator Delegations Paged', () => {
const execute = testHelper.buildMethod('getDelegatorDelegationsPaged', [mixnet, mixnodeowneraddress], <
PagedDelegatorDelegationsResponse
>{
delegations: [],
});
expect(execute).toBeTruthy();
});
});
@@ -1,24 +0,0 @@
import expect from 'expect';
import { GatewayOwnershipResponse, PagedGatewayResponse } from '../../types/shared-types';
import { TestHelper } from './client';
import { gatewayowneraddress, mixnet } from './testData';
describe('Gateway mock tests', () => {
const testHelper = new TestHelper();
it('get Gateways Paged', () => {
const execute = testHelper.buildMethod('getGatewaysPaged', [mixnet], <PagedGatewayResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('owns Gateway', () => {
const execute = testHelper.buildMethod('ownsGateway', [mixnet, gatewayowneraddress], <GatewayOwnershipResponse>{
address: gatewayowneraddress,
gateway: {},
});
expect(execute).toBeTruthy();
});
});
@@ -1,65 +0,0 @@
import expect from 'expect';
import { LayerDistribution, MixnetContractVersion, StakeSaturationResponse } from '@nymproject/types';
import { TestHelper } from './client';
import { mixnet, mix_id } from './testData';
import { RewardingParams } from '@nymproject/types';
import { ContractState } from '../../types/shared';
describe('Mixnet mock tests', () => {
const testHelper = new TestHelper();
it('get Layer Distribution', () => {
const execute = testHelper.buildMethod('getLayerDistribution', [mixnet], <LayerDistribution>{
layer1: 2,
layer2: 2,
layer3: 5,
});
expect(execute).toBeTruthy();
});
it('get Reward Params', () => {
const execute = testHelper.buildMethod('getRewardParams', [mixnet], <RewardingParams>{
interval: {},
rewarded_set_size: 0,
active_set_size: 0,
});
expect(execute).toBeTruthy();
});
it('get Stake Saturation', () => {
const execute = testHelper.buildMethod('getStakeSaturation', [mixnet, mix_id], <StakeSaturationResponse>{
mix_id: 0,
current_saturation: '',
uncapped_saturation: '',
});
expect(execute).toBeTruthy();
});
it('get State Params', () => {
const execute = testHelper.buildMethod('getStateParams', [mixnet], <ContractState>{
owner: '',
rewarding_validator_address: '',
vesting_contract_address: '',
rewarding_denom: 'unym',
params: {
minimum_mixnode_pledge: '',
minimum_gateway_pledge: '',
mixnode_rewarded_set_size: 240,
mixnode_active_set_size: 240,
},
});
expect(execute).toBeTruthy();
});
it('get Contract Version', () => {
const execute = testHelper.buildMethod('getContractVersion', [mixnet], <MixnetContractVersion>{
build_timestamp: 'test',
commit_branch: 'test',
build_version: 'test',
rustc_version: 'test',
commit_sha: 'test',
commit_timestamp: 'test',
});
expect(execute).toBeTruthy();
});
});
@@ -1,71 +0,0 @@
import expect from 'expect';
import {
MixNodeRewarding,
MixOwnershipResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
import { TestHelper } from './client';
import { mixnet, mixnodeowneraddress, mix_id } from './testData';
describe('Mixnode mock tests', () => {
const testHelper = new TestHelper();
it('get Mixnode Bonds', () => {
const execute = testHelper.buildMethod('getMixNodeBonds', [mixnet], <PagedMixNodeBondResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnode Delegations Paged', () => {
const execute = testHelper.buildMethod('getMixNodeDelegationsPaged', [mixnet, mix_id], <
PagedMixDelegationsResponse
>{
delegations: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnodes Detailed', () => {
const execute = testHelper.buildMethod('getMixNodesDetailed', [mixnet], <PagedMixNodeDetailsResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnode Rewarding Details', () => {
const execute = testHelper.buildMethod('getMixnodeRewardingDetails', [mixnet, mix_id], <MixNodeRewarding>{
cost_params: {},
operator: '',
delegates: '',
total_unit_reward: '',
unit_delegation: '',
last_rewarded_epoch: 1,
unique_delegations: 1,
});
expect(execute).toBeTruthy();
});
it('get Owned Mixnode', () => {
const execute = testHelper.buildMethod('getOwnedMixnode', [mixnet, mixnodeowneraddress], <MixOwnershipResponse>{
address: '',
mixnode: {},
});
expect(execute).toBeTruthy();
});
it('get Unbonded Mixnode Information', () => {
const execute = testHelper.buildMethod(
'getUnbondedMixNodeInformation',
[mixnet, mix_id],
<UnbondedMixnodeResponse>{},
);
expect(execute).toBeTruthy();
});
});
@@ -1,8 +0,0 @@
export const mixnet = 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g';
export const gatewayowneraddress = 'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts';
// export const mixId = 436207616;
export const mix_id = 26;
export const nodeIdentityKey = 'ATmVJknZarDF6Yj53M7h8NS9LLCUvWuToXpk3pDvYUH1';
export const mixnodeowneraddress = 'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47';
export const delegatorAddress = 'n1lemst75va9700tsrxq58adzujrh6h9s5x60h9h';
export const rewardingIntervalNonce = 1;
@@ -1,180 +0,0 @@
import expect from 'expect';
import ValidatorClient from '../../index';
import {
allunbondednodes,
contract,
delegation,
gateway,
layerDistribution,
mixnode,
mixnodebond,
ownedNode,
ownGateway,
rewardingnode,
saturation,
unbondednode,
} from '../expectedResponses';
import { delegatorAddress, gatewayowneraddress } from '../mock/testData';
const dotenv = require('dotenv');
dotenv.config();
describe('Mixnet queries', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connectForQuery(
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
//
// CONTRACT
//
it('can query for an account balance', async () => {
const balance = await client.getBalance('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
});
it('can query for stake saturation', async () => {
const stakeSaturation = await client.getStakeSaturation(7);
expect(Object.keys(stakeSaturation)).toEqual(Object.keys(saturation));
expect(stakeSaturation).toBeTruthy();
expect(stakeSaturation?.current_saturation).toBeTruthy();
});
it('can query for contract version', async () => {
const contractV = await client.getMixnetContractVersion();
expect(contractV).toBeTruthy();
});
it('can query for mixnet contract settings', async () => {
const settings = await client.getMixnetContractSettings();
expect(Object.keys(settings)).toEqual(Object.keys(contract));
expect(settings).toBeTruthy();
});
it('can query for reward pool', async () => {
const rewardPool = await client.getRewardParams();
// TODO add velidation here
expect(rewardPool).toBeTruthy();
});
it('can query for layer distribution', async () => {
const layer = await client.getLayerDistribution();
expect(Object.keys(layer)).toEqual(Object.keys(layerDistribution));
expect(layer).toBeTruthy();
});
//
// MIXNODES
//
it('can query for unbonded mixnodes', async () => {
const unbondedNodes = await client.getUnbondedMixNodes();
for (let i = 0; i < unbondedNodes.length; i++) {
expect(Object.keys(unbondedNodes[0])).toEqual(Object.keys(allunbondednodes));
expect(unbondedNodes).toBeTruthy();
}
});
it('can query for unbonded mixnode information', async () => {
const unbondedMixnodeInfo = await client.getUnbondedMixNodeInformation(1);
expect(Object.keys(unbondedMixnodeInfo)).toEqual(Object.keys(unbondednode));
expect(unbondedMixnodeInfo).toBeTruthy();
});
it('can query for mixnode rewarding details', async () => {
const rewardingDetails = await client.getMixnodeRewardingDetails(1);
expect(Object.keys(rewardingDetails)).toEqual(Object.keys(rewardingnode));
expect(rewardingDetails).toBeTruthy();
});
it('can query for owned mixnode', async () => {
const ownedMixnode = await client.getOwnedMixnode('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77');
expect(Object.keys(ownedMixnode)).toEqual(Object.keys(ownedNode));
expect(ownedMixnode).toBeTruthy();
});
it('can query for all mixnode bonds', async () => {
const mixnodeBonds = await client.getMixNodeBonds();
expect(Object.keys(mixnodeBonds[0])).toEqual(Object.keys(mixnodebond));
expect(mixnodeBonds).toBeTruthy();
expect(Array.isArray(mixnodeBonds)).toBeTruthy();
});
it('can query for all mixnode details', async () => {
const mixnodeDetails = await client.getMixNodesDetailed();
expect(Object.keys(mixnodeDetails[0])).toEqual(Object.keys(mixnode));
expect(mixnodeDetails).toBeTruthy();
expect(Array.isArray(mixnodeDetails)).toBeTruthy();
});
it('can query for all active mixnodes', async () => {
const activeNodes = await client.getActiveMixnodes();
expect(Object.keys(activeNodes[0])).toEqual(Object.keys(mixnode));
expect(activeNodes).toBeTruthy();
expect(Array.isArray(activeNodes)).toBeTruthy();
});
it('can query for rewarded mixnodes', async () => {
const rewardNodes = await client.getRewardedMixnodes();
expect(Object.keys(rewardNodes[0])).toEqual(Object.keys(mixnode));
expect(rewardNodes).toBeTruthy();
});
//
// DELEGATIONS
//
it('can query for account delegations', async () => {
const delegations = await client.getAllNyxdDelegatorDelegations('n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47');
expect(Object.keys(delegations[0])).toEqual(Object.keys(delegation));
expect(delegations).toBeTruthy();
expect(Array.isArray(delegations)).toBeTruthy();
});
it('can query for all delegations', async () => {
const allDelegations = await client.getAllNyxdDelegations();
expect(Object.keys(allDelegations[0])).toEqual(Object.keys(delegation));
expect(allDelegations).toBeTruthy();
expect(Array.isArray(allDelegations)).toBeTruthy();
});
it('can query for all delegations on a node', async () => {
const mixnodeDelegations = await client.getAllNyxdSingleMixnodeDelegations(1);
expect(Object.keys(mixnodeDelegations[0])).toEqual(Object.keys(delegation));
expect(mixnodeDelegations).toBeTruthy();
});
it('can query for detailed delegations', async () => {
const detailedDelegation = await client.getDelegationDetails(7, delegatorAddress);
expect(Object.keys(detailedDelegation)).toEqual(Object.keys(detailedDelegation));
expect(detailedDelegation).toBeTruthy();
});
//
// GATEWAYS
//
it('can query for all gateways', async () => {
const gateways = await client.getAllNyxdGateways();
expect(Object.keys(gateways[0])).toEqual(Object.keys(gateway));
expect(gateways).toBeTruthy();
expect(Array.isArray(gateways)).toBeTruthy();
}).timeout(10000);
it('can query for owned gateway', async () => {
const gateway = await client.ownsGateway(gatewayowneraddress);
expect(Object.keys(gateway)).toEqual(Object.keys(ownGateway));
expect(gateway).toBeTruthy();
}).timeout(10000);
});
@@ -1,28 +0,0 @@
import expect from 'expect';
import ValidatorClient from '../../index';
const dotenv = require('dotenv');
dotenv.config();
describe('Vesting queries', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connectForQuery(
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can query for contract version', async () => {
const contract = await client.getVestingContractVersion();
expect(contract).toBeTruthy();
});
it('can get the balance on the account', () => {});
});
@@ -1,107 +0,0 @@
import expect from 'expect';
import ValidatorClient from '../../';
const dotenv = require('dotenv');
dotenv.config();
// TODO: implement for QA with .env for mnemonics
describe('Mixnet actions', () => {
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 send tokens', async () => {
const res = await client.send(client.address, [{ amount: '10000000', denom: 'unym' }]);
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('can delegate tokens', async () => {
const [_, secondMixnode] = await client.getActiveMixnodes();
if (secondMixnode) {
const res = await client.delegateToMixNode(
secondMixnode.bond_information.mix_id,
{
amount: '15000000',
denom: 'unym',
},
{ gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] },
);
expect(res.transactionHash).toBeDefined();
}
}).timeout(10000);
// Need to provide a mix id that can be undelegated from
it.skip('can undelegate from a mixnode', async () => {
const mixId = 8;
const res = await client.undelegateFromMixNode(mixId);
expect(res.transactionHash).toBeDefined();
});
it.skip('Can unbond a mixnode', async () => {
const res = await client.unbondMixNode();
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('Can bond a mixnode', async () => {
const res = await client.bondMixNode(
{
identity_key: '3P6pAcF2p3pYMqWdpHqhbavu3ifyaBs5Qw5UmmCGwimx',
sphinx_key: 'GQMQKwUThaggatA6oZteSWTsCQoUfmLtamJ7o9YkP9aE',
host: '109.74.195.67',
mix_port: 1789,
verloc_port: 1790,
http_api_port: 8000,
version: '1.1.4',
},
'3rXWCQBUj5JQB3UBUkZcXhCk9Zh3cjduMF8aFHPTG7KTkkhZzDJTNmE2p2Ph1g6iQW5vRGTpQzjXF33WDwvhzHk6',
{ profit_margin_percent: '0.1', interval_operating_cost: { amount: '40', denom: 'nym' } },
{ amount: '100_000_000', denom: 'unym' },
{ gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] },
);
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('can unbond a gateway', async () => {
const res = await client.unbondGateway();
expect(res.transactionHash).toBeDefined();
});
it.skip('can bond a gateway', async () => {
const res = await client.bondGateway(
{
identity_key: '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL',
sphinx_key: 'G65Fwc2JNAotuHQFqmDKhQNQL5rn3r9pupUdmxMygNUZ',
host: '151.236.220.82',
version: '1.1.4',
mix_port: 1789,
clients_port: 9000,
location: 'Cuba',
},
'3ipSJksWHehZm1YfuH5Ahtg7b22NnrP9hEs6iEDXfUS5uiUhpWmCjGR3b3NDHuxeGjpZYJNYJ52D8WCPK5ZR7Szj',
{ amount: '100_000_000', denom: 'unym' },
);
expect(res.transactionHash).toBeDefined();
});
it.skip('can update contract state params', async () => {
const res = await client.updateContractStateParams({
minimum_mixnode_pledge: '',
minimum_gateway_pledge: '',
mixnode_rewarded_set_size: 2,
mixnode_active_set_size: 2,
});
expect(res.transactionHash).toBeDefined();
});
});
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist"
},
"exclude": [
"tests",
"dist",
"node_modules"
]
}
+157
View File
@@ -0,0 +1,157 @@
import { Coin } from '@cosmjs/stargate';
// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
export type PagedMixnodeResponse = {
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
export type PagedGatewayResponse = {
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string;
gateway?: GatewayBond;
};
export type ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
export type MixNode = {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
};
export type GatewayBond = {
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
export type Gateway = {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
export type SendRequest = {
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
-11
View File
@@ -1,11 +0,0 @@
declare namespace NodeJS {
interface ProcessEnv {
rpcAddress: string;
validatorAddress: string;
prefix: string;
mixnetContractAddress: string;
vestingContractAddress: string;
denom: string;
mnemonic: string;
}
}
-259
View File
@@ -1,259 +0,0 @@
import { JsonObject } from '@cosmjs/cosmwasm-stargate';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
import {
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import {
MixNodeRewarding,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
StakeSaturationResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
export type PagedMixnodeResponse = {
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
export type PagedGatewayResponse = {
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string;
gateway?: GatewayBond;
};
export type ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
export type MixNode = {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
};
export type GatewayBond = {
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
export type Gateway = {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
export type SendRequest = {
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
export interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export interface INymdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse>;
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractState>;
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation>;
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse>;
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse>;
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding>;
}
export interface IVestingQuerier {
getVestingContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
}
export interface ContractState {
owner: string;
rewarding_validator_address: string;
vesting_contract_address: string;
rewarding_denom: string;
params: ContractStateParams;
}
@@ -0,0 +1,11 @@
import ValidatorClient from '../../validator/index';
import expect from 'expect';
describe('Query: balances', () => {
it('can query for an account balance', async () => {
const client = await ValidatorClient.connectForQuery(
'https://rpc.nymtech.net/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
}).timeout(5000);
})
+14
View File
@@ -0,0 +1,14 @@
import ValidatorClient from '../../dist';
import expect from 'expect';
// TODO: implement for QA with .env for mnemonics
// describe('Sign: send', () => {
// it('can send tokens', async () => {
// const client = await ValidatorClient.connect(
// '<ADD MNEMONIC HERE>',
// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
// await client.send('<ADD ADDRESS HERE>')
// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
// }).timeout(5000);
// })
+22 -21
View File
@@ -1,22 +1,23 @@
{
"compilerOptions": {
"outDir": "./dist/nym-validator-client",
"module": "ES2020",
"target": "es2021",
"allowJs": false,
"strict": true,
"lib": ["es2021", "dom", "dom.iterable", "esnext"],
"moduleResolution": "node",
"skipDefaultLibCheck": true,
"esModuleInterop": true,
"declaration": true,
"skipLibCheck": true,
"noImplicitAny": true,
"typeRoots": ["./src/types/global.d.ts"]
},
"typedocOptions": {
"entryPoints": ["./src/index.ts"],
"out": "docs"
},
"exclude": ["dist", "./src/tests/**/*", "node_module"]
}
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist",
"skipLibCheck": true
},
"typedocOptions": {
"entryPoints": [
"src/index.ts"
],
"out": "docs"
},
"exclude": [
"dist",
"examples",
"tests",
"node_modules"
]
}
-6
View File
@@ -1,6 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS"
}
}
+2 -2
View File
@@ -31,8 +31,8 @@ wasm-bindgen-futures = "0.4"
# internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
nym-sphinx = { path = "../../common/nymsphinx" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
+1 -1
View File
@@ -1,5 +1,5 @@
[package]
name = "nym-bandwidth-claim-contract"
name = "bandwidth-claim-contract"
version = "0.1.0"
edition = "2021"
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-bin-common"
version = "0.3.0"
version = "0.1.0"
description = "Common code for nym binaries"
edition = { workspace = true }
authors = { workspace = true }
@@ -17,7 +17,7 @@ semver = "0.11"
serde = { workspace = true, features = ["derive"], optional = true }
[build-dependencies]
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc", "cargo"] }
vergen = { version = "7", default-features = false, features = ["build", "git", "rustc", "cargo"] }
[features]
default = []
+1 -6
View File
@@ -4,10 +4,5 @@
use vergen::{vergen, Config};
fn main() {
let mut config = Config::default();
if std::env::var("DOCS_RS").is_ok() {
// If we don't have access to git information, such as in a docs.rs build, don't error
*config.git_mut().skip_if_error_mut() = true;
}
vergen(config).expect("failed to extract build metadata");
vergen(Config::default()).expect("failed to extract build metadata")
}
View File
+4 -4
View File
@@ -14,12 +14,12 @@ log = { workspace = true }
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
async-trait = { workspace = true }
async-trait = { version = "0.1.51" }
tokio = { version = "1.24.1", features = ["macros"] }
# internal
nym-coconut-interface = { path = "../../coconut-interface" }
nym-credentials = { path = "../../credentials" }
coconut-interface = { path = "../../coconut-interface" }
credentials = { path = "../../credentials" }
nym-crypto = { path = "../../crypto" }
gateway-requests = { path = "../../../gateway/gateway-requests" }
nym-network-defaults = { path = "../../network-defaults" }
@@ -47,7 +47,7 @@ features = ["net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-credential-storage]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage]
path = "../../credential-storage"
# wasm-only dependencies
@@ -7,7 +7,7 @@ use crate::error::GatewayClientError;
use crate::wasm_mockups::Storage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use nym_credential_storage::storage::Storage;
use credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "mobile")]
@@ -22,7 +22,7 @@ use crate::wasm_mockups::StorageError;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use nym_credential_storage::error::StorageError;
use credential_storage::error::StorageError;
#[cfg(target_arch = "wasm32")]
use crate::wasm_mockups::{Client, CosmWasmClient};
@@ -30,8 +30,8 @@ use std::str::FromStr;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::{nyxd::CosmWasmClient, Client};
use {
nym_coconut_interface::Base58,
nym_credentials::coconut::{
coconut_interface::Base58,
credentials::coconut::{
bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key,
},
};
@@ -42,7 +42,7 @@ use crate::wasm_mockups::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use nym_credential_storage::PersistentStorage;
use credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "mobile")]
@@ -69,17 +69,17 @@ where
pub async fn prepare_coconut_credential(
&self,
) -> Result<(nym_coconut_interface::Credential, i64), GatewayClientError> {
) -> Result<(coconut_interface::Credential, i64), GatewayClientError> {
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();
let serial_number =
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?;
coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?;
let binding_number =
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
let signature =
nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
let epoch_id = u64::from_str(&bandwidth_credential.epoch_id)
.map_err(|_| StorageError::InconsistentData)?;
@@ -9,13 +9,13 @@ pub use crate::packet_router::{
};
use crate::socket_state::{PartiallyDelegated, SocketState};
use crate::{cleanup_socket_message, try_decrypt_binary_message};
use coconut_interface::Credential;
use futures::{SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION};
use log::*;
use nym_coconut_interface::Credential;
use nym_crypto::asymmetric::identity;
use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
use nym_sphinx::forwarding::packet::MixPacket;
@@ -33,7 +33,7 @@ use validator_client::nyxd::CosmWasmClient;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use nym_credential_storage::PersistentStorage;
use credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "mobile")]
@@ -3,13 +3,13 @@
#[cfg(target_arch = "wasm32")]
use crate::wasm_mockups::StorageError;
#[cfg(not(feature = "mobile"))]
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::error::StorageError;
use gateway_requests::registration::handshake::error::HandshakeError;
#[cfg(feature = "mobile")]
#[cfg(not(target_arch = "wasm32"))]
use mobile_storage::StorageError;
#[cfg(not(feature = "mobile"))]
#[cfg(not(target_arch = "wasm32"))]
use nym_credential_storage::error::StorageError;
use std::io;
use thiserror::Error;
use tungstenite::Error as WsError;
@@ -31,7 +31,7 @@ pub enum GatewayClientError {
CredentialStorageError(#[from] StorageError),
#[error("Coconut error - {0}")]
CoconutError(#[from] nym_coconut_interface::CoconutError),
CoconutError(#[from] coconut_interface::CoconutError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
@@ -48,7 +48,7 @@ pub enum GatewayClientError {
NoBandwidthControllerAvailable,
#[error("Credential error - {0}")]
CredentialError(#[from] nym_credentials::error::Error),
CredentialError(#[from] credentials::error::Error),
#[error("Connection was abruptly closed")]
ConnectionAbruptlyClosed,
@@ -74,7 +74,7 @@ impl PacketRouter {
received_messages.push(received_packet);
} else {
// this can happen if other clients are not padding their messages
//warn!("Received message of unexpected size. Probably from an outdated client... len: {}", received_packet.len());
warn!("Received message of unexpected size. Probably from an outdated client... len: {}", received_packet.len());
received_messages.push(received_packet);
}
}
@@ -8,7 +8,6 @@ edition = "2021"
[dependencies]
futures = "0.3"
tracing = "0.1.37"
log = { workspace = true }
tokio = { version = "1.24.1", features = ["time", "net", "rt"] }
tokio-util = { version = "0.7.4", features = ["codec"] }
@@ -3,12 +3,11 @@
use futures::channel::mpsc;
use futures::StreamExt;
use tracing::*;
use log::*;
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 nym_sphinx::params::packet_sizes::PacketSize;
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
@@ -198,7 +197,6 @@ impl Client {
}
impl SendWithoutResponse for Client {
#[instrument(level="info", skip(self, packet), "Sending packet to mixnet", fields(packet_size))]
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
@@ -206,15 +204,13 @@ impl SendWithoutResponse for Client {
packet_mode: PacketMode,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let packet_size = PacketSize::get_type(packet.len()).unwrap();
Span::current().record("packet_size", field::debug(packet_size));
let framed_packet =
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) {
if err.is_full() {
info!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
// it's not a 'big' error, but we did not manage to send the packet
// if the queue is full, we can't really do anything but to drop the packet
Err(io::Error::new(
@@ -4,8 +4,8 @@
use crate::client::{Client, Config, SendWithoutResponse};
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_sphinx::forwarding::packet::MixPacket;
use tracing::*;
use std::time::Duration;
pub type MixForwardingSender = mpsc::UnboundedSender<MixPacket>;
@@ -53,10 +53,10 @@ impl PacketForwarder {
tokio::select! {
biased;
_ = self.shutdown.recv() => {
trace!("PacketForwarder: Received shutdown");
log::trace!("PacketForwarder: Received shutdown");
}
Some(mix_packet) = self.packet_receiver.next() => {
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
let next_hop = mix_packet.next_hop();
let packet_mode = mix_packet.packet_mode();
+10 -12
View File
@@ -11,13 +11,13 @@ rust-version = "1.56"
base64 = "0.13"
colored = "2.0"
nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
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-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
nym-vesting-contract = { path = "../../../contracts/vesting" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -28,26 +28,25 @@ url = { version = "2.2", features = ["serde"] }
tokio = { version = "1.24.1", features = ["sync", "time"] }
futures = "0.3"
nym-coconut-interface = { path = "../../coconut-interface" }
coconut-interface = { path = "../../coconut-interface" }
nym-network-defaults = { path = "../../network-defaults" }
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
nym-execute = { path = "../../execute" }
# required for nyxd-client
# at some point it might be possible to make it wasm-compatible
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
async-trait = { workspace = true, optional = true }
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cw3 = { workspace = true, optional = true }
cw4 = { workspace = true, optional = true }
cw3 = { version = "0.13.4", optional = true }
cw4 = { version = "0.13.4", optional = true }
prost = { version = "0.10", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { workspace = true, optional = true }
zeroize = { version = "1.5.7", optional = true }
cosmwasm-std = { version = "1.0.0", optional = true }
nym-execute = { path = "../../execute" }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -65,7 +64,6 @@ nyxd-client = [
"sha2",
"itertools",
"cosmwasm-std",
"zeroize"
]
generate-ts = []
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{nym_api, ValidatorClientError};
use coconut_dkg_common::types::NodeIndex;
use coconut_interface::VerificationKey;
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
@@ -9,26 +11,26 @@ use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
use nym_coconut_dkg_common::types::NodeIndex;
use nym_coconut_interface::VerificationKey;
pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef};
#[cfg(feature = "nyxd-client")]
use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient};
#[cfg(feature = "nyxd-client")]
use crate::nyxd::{self, CosmWasmClient, NyxdClient, QueryNyxdClient, SigningNyxdClient};
#[cfg(feature = "nyxd-client")]
use cw3::ProposalResponse;
#[cfg(feature = "nyxd-client")]
use nym_api_requests::models::MixNodeBondAnnotated;
#[cfg(feature = "nyxd-client")]
use nym_coconut_dkg_common::{
use coconut_dkg_common::{
dealer::ContractDealing,
types::{DealerDetails, EpochId},
verification_key::ContractVKShare,
};
#[cfg(feature = "nyxd-client")]
use nym_coconut_interface::Base58;
use coconut_interface::Base58;
#[cfg(feature = "nyxd-client")]
use cw3::ProposalResponse;
#[cfg(feature = "nyxd-client")]
use nym_api_requests::models::MixNodeBondAnnotated;
#[cfg(feature = "nyxd-client")]
use nym_mixnet_contract_common::{
families::{Family, FamilyHead},
@@ -130,9 +132,10 @@ impl Config {
#[cfg(feature = "nyxd-client")]
#[derive(Clone)]
pub struct Client<C: Clone> {
// // TODO: we really shouldn't be storing a mnemonic here, but removing it would be
// // non-trivial amount of work and it's out of scope of the current branch
// mnemonic: Option<bip39::Mnemonic>,
// TODO: we really shouldn't be storing a mnemonic here, but removing it would be
// non-trivial amount of work and it's out of scope of the current branch
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
@@ -156,11 +159,12 @@ impl Client<SigningNyxdClient> {
let nyxd_client = NyxdClient::connect_with_mnemonic(
config.nyxd_config.clone(),
config.nyxd_url.as_str(),
mnemonic,
mnemonic.clone(),
None,
)?;
Ok(Client {
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
@@ -174,7 +178,12 @@ impl Client<SigningNyxdClient> {
}
pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
self.nyxd.change_endpoint(new_endpoint.as_ref())?;
self.nyxd = NyxdClient::connect_with_mnemonic(
self.nyxd.current_config().clone(),
new_endpoint.as_ref(),
self.mnemonic.clone().unwrap(),
None,
)?;
Ok(())
}
@@ -191,6 +200,7 @@ impl Client<QueryNyxdClient> {
NyxdClient::connect(config.nyxd_config.clone(), config.nyxd_url.as_str())?;
Ok(Client {
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
@@ -749,12 +759,6 @@ where
Ok(self.nym_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
@@ -144,21 +144,6 @@ impl Client {
.await
}
pub async fn get_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::MIXNODES,
routes::DETAILED_UNFILTERED,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
@@ -8,7 +8,6 @@ pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const DETAILED: &str = "detailed";
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
pub const COCONUT_ROUTES: &str = "coconut";
@@ -1,13 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::{Gas, GasPrice};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmwasm_std::Coin as CosmWasmCoin;
use cosmwasm_std::{Fraction, Uint128};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Div;
pub use cosmrs::Coin as CosmosCoin;
pub use cosmwasm_std::Coin as CosmWasmCoin;
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct MismatchedDenoms;
@@ -22,40 +19,6 @@ pub struct Coin {
pub denom: String,
}
impl Div<GasPrice> for Coin {
type Output = Gas;
fn div(self, rhs: GasPrice) -> Self::Output {
&self / rhs
}
}
impl<'a> Div<GasPrice> for &'a Coin {
type Output = Gas;
fn div(self, rhs: GasPrice) -> Self::Output {
if self.denom != rhs.denom {
panic!(
"attempted to use two different denoms for gas calculation ({} and {})",
self.denom, rhs.denom
);
}
// tsk, tsk. somebody tried to divide by zero here!
let Some(gas_price_inv) = rhs.amount.inv() else {
panic!("attempted to divide by zero!")
};
let implicit_gas_limit = gas_price_inv * Uint128::new(self.amount);
if implicit_gas_limit.u128() >= u64::MAX as u128 {
u64::MAX
} else {
implicit_gas_limit.u128() as u64
}
.into()
}
}
impl Coin {
pub fn new<S: Into<String>>(amount: u128, denom: S) -> Self {
Coin {
@@ -165,67 +128,3 @@ impl CoinConverter for CosmWasmCoin {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn division_by_zero_gas_price() {
let gas_price: GasPrice = "0unym".parse().unwrap();
let amount = Coin::new(123, "unym");
let _res = amount / gas_price;
}
#[test]
#[should_panic]
fn division_by_gas_price_of_different_denom() {
let gas_price: GasPrice = "0.025unyx".parse().unwrap();
let amount = Coin::new(123, "unym");
let _res = amount / gas_price;
}
#[test]
fn gas_price_division() {
let amount = Coin::new(3938, "unym");
let gas_price = "0.025unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(157520, res.value());
let amount = Coin::new(1234567890, "unym");
let gas_price = "0.025unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(49382715600, res.value());
let amount = Coin::new(1, "unym");
let gas_price = "0.025unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(40, res.value());
let amount = Coin::new(150_000_000, "unym");
let gas_price = "0.001234unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(121555915721, res.value());
let amount = Coin::new(150_000_000, "unym");
let gas_price = "1unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(150_000_000, res.value());
let amount = Coin::new(150_000_000, "unym");
let gas_price = "1234.56unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(121500, res.value());
}
#[test]
fn gas_price_division_identity() {
let amount = Coin::new(1234567890, "unym");
let gas_price: GasPrice = "0.025unym".parse().unwrap();
let res1 = (&amount) / gas_price.clone();
let res2 = &gas_price * res1;
assert_eq!(amount, Coin::from(res2));
}
}
@@ -6,8 +6,8 @@ use cosmrs::tendermint::abci;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
pub use nym_coconut_bandwidth_contract_common::event_attributes::*;
pub use nym_coconut_dkg_common::event_attributes::*;
pub use coconut_bandwidth_contract_common::event_attributes::*;
pub use coconut_dkg_common::event_attributes::*;
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
// as theirs logs
@@ -756,19 +756,6 @@ impl Client {
})
}
pub fn change_endpoint<U>(&mut self, new_endpoint: U) -> Result<(), NyxdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let new_rpc_client = HttpClient::new(new_endpoint)?;
self.rpc_client = new_rpc_client;
Ok(())
}
pub fn into_signer(self) -> DirectSecp256k1HdWallet {
self.signer
}
pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) {
self.broadcast_polling_rate = broadcast_polling_rate
}
@@ -140,7 +140,7 @@ pub enum NyxdError {
CosmwasmStdError(#[from] cosmwasm_std::StdError),
#[error("Coconut interface error: {0}")]
CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError),
CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError),
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
UnexpectedBech32Prefix { got: String, expected: String },
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::Coin;
use crate::nyxd::Gas;
use crate::nyxd::{Coin, GasPrice};
use cosmrs::{tx, AccountId};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
@@ -64,12 +64,6 @@ impl Display for Fee {
}
impl Fee {
pub fn manual_with_gas_price(fee: Coin, gas_price: GasPrice) -> Self {
let gas_limit = &fee / gas_price;
Fee::Manual(tx::Fee::from_amount_and_gas(fee.into(), gas_limit))
}
pub fn new_payer_granter_auto(
gas_adjustment: Option<GasAdjustment>,
payer: Option<AccountId>,
@@ -197,7 +197,7 @@ impl NyxdClient<SigningNyxdClient> {
{
let prefix = &config.chain_details.bech32_account_prefix;
let denom = &config.chain_details.mix_denom.base;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?;
let client_address = wallet
.try_derive_accounts()?
.into_iter()
@@ -212,17 +212,6 @@ impl NyxdClient<SigningNyxdClient> {
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
})
}
pub fn change_endpoint<U>(&mut self, new_endpoint: U) -> Result<(), NyxdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
self.client.change_endpoint(new_endpoint)
}
pub fn into_signer(self) -> DirectSecp256k1HdWallet {
self.client.into_signer()
}
}
impl<C> NyxdClient<C>

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