Compare commits

..

57 Commits

Author SHA1 Message Date
Tommy Verrall e20ed854df check outputs 2023-08-29 11:46:14 +02:00
Tommy Verrall c6509c3a95 fix string 2023-08-29 11:35:31 +02:00
Tommy Verrall 8a9d242d03 check to see if this fixes strapi 2023-08-29 11:32:10 +02:00
Jon Häggblad 769a26fdeb Merge pull request #3815 from nymtech/jon/revert-clippy-change
Revert clippy fix for arc_with_non_send_sync in wasm client
2023-08-28 09:53:16 +02:00
Jon Häggblad 2e7ddcb195 Allow arc_with_non_send_sync in wasm client 2023-08-26 21:51:16 +02:00
Jon Häggblad 84d893198b Revert "Replace Arc with Rc on clippy's suggestion"
This reverts commit b050ae72de.
2023-08-26 21:09:33 +02:00
Fran Arbanas 2f6617daac Feature/issue credentials (#3691)
* Move the functionality to issue credentials from the credential binary and connect it with nym-cli

* finished CLI part, trying to fit SDK part

* finished Rust SDK

* fix: cleanup

* linting

* linting

* linting

* remove one layer of coconut in nym-cli

* linting

* Fixes based on PR comments

* formatting

* fixes based on PR comments

* formatting

* fixing clippy errors

* fixed post-rebasing issues and converted the lib into shared dep for other binaries

* removed credentials client in favour of moving the functionality to nym-cli

* removed redundant 'issue_credential' example (it did the same thing as 'bandwdith')

* removed credentials client from build server

* made the coconut cli also accept nym-api configs

* fixed support for socks5 and NR

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2023-08-25 17:13:52 +02:00
Tommy Verrall 9e483da802 Merge pull request #3814 from nymtech/jon/include-gateways-in-georouting
In geoaware routing, choose mixnodes close to exit gateway
2023-08-25 16:56:07 +02:00
Jon Häggblad d530e492ec Fix compilation 2023-08-25 16:13:41 +02:00
Jon Häggblad 6c67cff235 Allow clippy for now 2023-08-25 16:02:13 +02:00
Jon Häggblad 66f04d17b7 Select country group by the exit gateway 2023-08-25 15:52:43 +02:00
Jon Häggblad 492155d04a Also filter gateways on location 2023-08-25 15:52:43 +02:00
Jon Häggblad 069226125b Separate cfg wasm function 2023-08-25 15:42:40 +02:00
Jędrzej Stuczyński 1ce536c2fa [Manually reapplied] Merge pull request #3804 from nymtech/feature/explorer-gateway-iplocation
This was originally a commit 'b0a45c03b16c74697d8b46428fd83a25a5168add'.
However, we had to manually reapply it due to accidentally messing up the branch history.
2023-08-25 13:59:38 +01:00
Lorexia 2fbb901deb Update mixnode setup to return IPv4 2023-08-25 13:56:31 +01:00
Tommy Verrall 3c3ec9c831 Merge pull request #3812 from nymtech/jon/nym-connect-filter-gateway-version
nym-connect: filter gateways on compatible version
2023-08-25 11:58:35 +02:00
Jon Häggblad 6a34a99fee Merge pull request #3811 from nymtech/jon/clippy
Fix clippy for latest rustc
2023-08-25 11:44:57 +02:00
Jon Häggblad ec75a06c9d nym-connect: filter gateways on compatible version 2023-08-25 11:27:47 +02:00
Tommy Verrall 2fbe7bb350 Merge pull request #3810 from nymtech/jon/make-explorer-api-url-configurable-in-geo-aware
Add EXPLORER_API configurable url
2023-08-25 10:17:36 +02:00
Jon Häggblad ee67f3d0f0 rustfmt 2023-08-25 09:10:10 +02:00
Jon Häggblad 188650ea05 Stay on 1.71.0 for nym-wallet until the clippy crash is resolved 2023-08-25 09:09:05 +02:00
Jon Häggblad decae2b54d Clippy pub use shadowing warning 2023-08-25 09:01:27 +02:00
Jon Häggblad 8104761914 Update nym-connect Cargo.lock 2023-08-25 07:37:45 +02:00
Jon Häggblad 8503def37f Remove nym-connect mobile from Makefile 2023-08-25 07:37:29 +02:00
Jon Häggblad 48023eab41 Remove shadowing import 2023-08-25 07:33:49 +02:00
Jon Häggblad b050ae72de Replace Arc with Rc on clippy's suggestion 2023-08-25 07:33:32 +02:00
Jon Häggblad 377c9be790 clippy --fix 2023-08-24 17:58:51 +02:00
Jon Häggblad 5d7fd66cfc rustfmt 2023-08-24 17:55:19 +02:00
Jon Häggblad 5128aef193 Upgrade ts-rs to 7.0.0 2023-08-24 17:55:19 +02:00
Jon Häggblad 6a327b0bd6 No timeout for wasm 2023-08-24 16:01:47 +02:00
Jon Häggblad 8a38c61065 Fix wallet build 2023-08-24 15:43:48 +02:00
Jon Häggblad f0a888d59c Add EXPLORER_API configurable url 2023-08-24 15:34:49 +02:00
mfahampshire f1b9cf4d68 update minibolt pp 2023-08-23 20:56:43 +02:00
Tommy Verrall 392e7b5268 Merge pull request #3806 from nymtech/fix/wallet-gateway-form
fix(wallet): gateway bond form wrong helper text
2023-08-23 18:01:40 +02:00
pierre 776a9d508a fix wrong helper text 2023-08-23 17:44:53 +02:00
pierre 2f624d4f10 doc(nc-android): add notes for AAB build 2023-08-23 17:20:51 +02:00
Jędrzej Stuczyński 3c9faff4ec [hotfix]: don't assign invalid fields when crossing the JS boundary (#3805)
* [hotfix]: don't assign invalid fields when crossing the JS boundary

* eslint
2023-08-23 16:12:01 +01:00
pierre 026d52a218 print location in uppercase 2023-08-23 16:46:07 +02:00
pierre f8b70097d3 use geoip location for gateways 2023-08-23 16:17:07 +02:00
Tommy Verrall 0d3df4b58d Merge pull request #3802 from nymtech/bugfix/use-correct-tendermint-dialect
Bugfix/use correct tendermint dialect
2023-08-23 12:46:41 +02:00
Jędrzej Stuczyński 5241047f45 implemented compatibility mode for reqwest client 2023-08-23 11:05:31 +01:00
Jędrzej Stuczyński 2651784e8b fully delegating 'TendermintRpcClient' trait 2023-08-23 10:42:09 +01:00
Jędrzej Stuczyński 2cd2b1ccd4 always using http client in v034 compat mode 2023-08-23 10:08:33 +01:00
Jon Häggblad af4e8241e7 Fix rocksdb compilation (#3801)
* Fix rocksdb compilation

* Add ephemera to CI build

* Add nym-nr-query to CI build

* Fix clippy in ephemera rocksdb
2023-08-23 10:07:45 +02:00
Jon Häggblad 92350daca8 network-requester: disable poisson process by default (#3783)
* network-requester: disable poisson process by default

* network-requester: instead add new top-level config field

* Remove quoation marks in template
2023-08-22 14:17:03 +02:00
Jędrzej Stuczyński b429c64168 [demo] libp2p example with nym-sdk (#3763)
* imported libp2p to monorepo

* fixed vanilla ping example

* added libp2p client to workspace

* naively replaced dockerised client with the sdk

* moved libp2p code to sdk examples

* reduced number of dependencies required for libp2p example

* updated readmes

* added protobuf compiler to build dependencies

* added protoc dependency to readme for chat and ping examples

---------

Co-authored-by: mfahampshire <maxhampshire@pm.me>
2023-08-22 13:12:13 +02:00
Tommy Verrall f88622ac08 Delete upload-nyxd-ci.yml
not needed anymore removing
2023-08-22 12:21:44 +02:00
Jędrzej Stuczyński 0a5a2c6747 removed needless_pass_by_ref_mut in mixnet contract tests (#3798) 2023-08-22 11:56:23 +02:00
Tommy Verrall 824bd636f9 Merge pull request #3795 from nymtech/release/v1.1.28
Release/v1.1.28
2023-08-22 11:17:29 +02:00
Tommy Verrall cf6411ac08 Merge pull request #3794 from nymtech/qa/fix-windows-builder
Update nym-connect-publish-windows10.yml
2023-08-22 10:59:23 +02:00
Tommy Verrall 6428133122 Merge pull request #3793 from nymtech/qa/fix-api-tests
change ci to run against sandbox for tests
2023-08-22 10:55:24 +02:00
Tommy Verrall 3c69f9c2f9 Update nym-connect-publish-windows10.yml 2023-08-22 10:35:46 +02:00
Tommy Verrall c1e4b87744 change ci to run against sandbox for tests 2023-08-22 10:31:37 +02:00
Pierre Dommerc dffe171b7f feat(explorer-api): add gateway geoip location (#3785)
* feat(explorer-api): add gateway geoip location

* fix lint
2023-08-21 15:50:18 +02:00
mfahampshire 6d3b198f00 change minibolt description 2023-08-21 14:04:21 +02:00
mfahampshire d4920b82f0 include 2faktor's PR material 2023-08-21 14:04:08 +02:00
mfahampshire 8dfe8f4678 version bump in toml files fr docs projects 2023-08-21 09:54:09 +02:00
199 changed files with 5913 additions and 2884 deletions
@@ -57,7 +57,7 @@ jobs:
echo $OUTPUT_DIR
- 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
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 protobuf-compiler
continue-on-error: true
- name: Install Rust stable
@@ -98,7 +98,6 @@ jobs:
cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/nym-credential-client $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
+5 -1
View File
@@ -6,6 +6,7 @@ on:
- 'clients/**'
- 'common/**'
- 'explorer-api/**'
- 'ephemera/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
@@ -15,6 +16,7 @@ on:
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/nym-nr-query/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
pull_request:
@@ -22,6 +24,7 @@ on:
- 'clients/**'
- 'common/**'
- 'explorer-api/**'
- 'ephemera/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
@@ -31,6 +34,7 @@ on:
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/nym-nr-query/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
@@ -42,7 +46,7 @@ jobs:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
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 protobuf-compiler
continue-on-error: true
- name: Check out repository code
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
if: matrix.os == 'ubuntu-20.04'
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
if: matrix.os == 'ubuntu-20.04'
@@ -100,6 +100,7 @@ jobs:
nym-connect/desktop/target/release/bundle/msi/*.msi.zip*
- id: release-info
name: Prepare release info
shell: bash
run: |
ref=${{ github.ref_name }}
semver="${ref##nym-connect-}" && semver="${semver##v}"
+20 -1
View File
@@ -25,7 +25,7 @@ jobs:
outputs:
release_id: ${{ steps.create-release.outputs.id }}
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }}
gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }}
@@ -40,6 +40,7 @@ jobs:
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
netstat_version: ${{ steps.binary-versions.outputs.netstat_version }}
platform: ${{ steps.get-platform-version.outputs.platform }}"
steps:
- uses: actions/checkout@v3
@@ -96,6 +97,11 @@ jobs:
target/release/nym-network-statistics
target/release/nym-cli
- id: echo-outputs
name: echo output for assets
run: |
echo "data from release: $ ${{ steps.create-release.outputs }}"
- id: release-info
name: Prepare release info
run: |
@@ -124,6 +130,12 @@ jobs:
v=$(rg '^version = "(.*)"' -or '$1' tools/nym-cli/Cargo.toml) && echo "cli_version=$v" >> "$GITHUB_OUTPUT"
v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-statistics/Cargo.toml) && echo "netstat_version=$v" >> "$GITHUB_OUTPUT"
- id: get-platform-version
name: get platform version
run: |
echo "::set-output name=platform::$(uname -r)"
push-release-data-client:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/push-release-data.yml
@@ -139,6 +151,7 @@ jobs:
file_hash: ${{ needs.publish-nym.outputs.client_hash }}
name: Client
category: binaries
platform: "${{ needs.publish-nym.outputs.platform }}"
secrets: inherit
push-release-data-mixnode:
@@ -156,6 +169,7 @@ jobs:
file_hash: ${{ needs.publish-nym.outputs.mixnode_hash }}
name: Mixnode
category: binaries
platform: "${{ needs.publish-nym.outputs.platform }}"
secrets: inherit
push-release-data-gateway:
@@ -173,6 +187,7 @@ jobs:
file_hash: ${{ needs.publish-nym.outputs.gateway_hash }}
name: Gateway
category: binaries
platform: "${{ needs.publish-nym.outputs.platform }}"
secrets: inherit
push-release-data-socks5:
@@ -190,6 +205,7 @@ jobs:
file_hash: ${{ needs.publish-nym.outputs.socks5_hash }}
name: Socks5 Client
category: binaries
platform: "${{ needs.publish-nym.outputs.platform }}"
secrets: inherit
push-release-data-network-requester:
@@ -207,6 +223,7 @@ jobs:
file_hash: ${{ needs.publish-nym.outputs.netreq_hash }}
name: Network Requester
category: binaries
platform: "${{ needs.publish-nym.outputs.platform }}"
secrets: inherit
push-release-data-cli:
@@ -224,6 +241,7 @@ jobs:
file_hash: ${{ needs.publish-nym.outputs.cli_hash }}
name: Cli
category: binaries
platform: "${{ needs.publish-nym.outputs.platform }}"
secrets: inherit
push-release-data-network-stat:
@@ -241,4 +259,5 @@ jobs:
file_hash: ${{ needs.publish-nym.outputs.netstat_hash }}
name: Network Statistics
category: binaries
platform: "${{ needs.publish-nym.outputs.platform }}"
secrets: inherit
+1 -1
View File
@@ -32,5 +32,5 @@ jobs:
run: yarn
- name: Run tests
run: yarn test:qa
run: yarn test:sandbox
working-directory: nym-api/tests
-79
View File
@@ -1,79 +0,0 @@
name: Upload nyxd to CI
on:
workflow_dispatch:
jobs:
publish-nyxd:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Prepare build output directory
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
rm -rf ci-builds || true
mkdir -p $OUTPUT_DIR
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools git
continue-on-error: true
- name: Update env variables to include go
run: |
sudo rm -rf /usr/local/go
curl https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf -
cat <<'EOF' >>$HOME/.profile
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export GO111MODULE=on
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
EOF
source $HOME/.profile
- name: Verify Go is installed
run: go version
- name: Clone nyxd repo
run: |
git clone https://github.com/tommyv1987/nyxd
cd nyxd
git checkout release/v0.30.2
- name: Run nyxd
run: |
pwd
cd nyxd && make build
sleep 10
ls /home/runner/work/nym/nym/nyxd/build
- name: Prepare build output
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
cp /home/runner/work/nym/nym/nyxd/build/nyxd $OUTPUT_DIR
WASMVM_SO=$(ldd /home/runner/work/nym/nym/nyxd/build/nyxd | grep "libwasm*" | awk '{ print $3 }')
ls $WASMVM_SO
sleep 3
cp $(echo $WASMVM_SO) $OUTPUT_DIR
- name: Deploy nyxd to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "ci-builds/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
EXCLUDE: "/dist/, /node_modules/"
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: 1.71.0
override: true
components: rustfmt, clippy
Generated
+933 -438
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -17,7 +17,6 @@ opt-level = 3
resolver = "2"
members = [
"clients/credential",
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
@@ -43,6 +42,7 @@ members = [
"common/cosmwasm-smart-contracts/vesting-contract",
"common/credential-storage",
"common/credentials",
"common/credential-utils",
"common/crypto",
"common/dkg",
"common/execute",
@@ -150,6 +150,7 @@ tap = "1.0.1"
tendermint-rpc = "0.32" # same version as used by cosmrs
thiserror = "1.0.38"
tokio = "1.24.1"
ts-rs = "7.0.0"
url = "2.4"
zeroize = "1.6.0"
-3
View File
@@ -77,9 +77,6 @@ $(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unkn
$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
$(eval $(call add_cargo_workspace,wallet,nym-wallet,))
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
ifdef NYM_MOBILE
$(eval $(call add_cargo_workspace,connect-mobile,nym-connect/mobile/src-tauri))
endif
# -----------------------------------------------------------------------------
# Convenience targets for crates that are already part of the main workspace
-32
View File
@@ -1,32 +0,0 @@
<!--
Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
SPDX-License-Identifier: Apache-2.0
-->
## Credential binary
The credential binary is used to acquire coconut bandwidth credentials in exchange for nym tokens. Those credentials are stored in the client's `data` directory, so that they can be used as the client sees fit.
### Warning
The credential binary is still experimental software. The infrastructure for using it is not yet deployed to mainnet and it's still in the process of being deployed to sandbox.
### Building
From the project's root directory, run:
```
cargo build -p nym-credential-client
```
which generates the `nym-credential-client` binary in `target/debug/nym-credential-client`.
### Running
For example, you can get a credential worth 3 nym (3000000 unym) in a socks5 client that was already initialized like so:
```
./target/debug/nym-credential-client --config-env-file envs/sandbox.env --client-home-directory ~/.nym/socks5-clients/cred_client --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic $MNEMONIC --recovery-dir /tmp/recovery --amount 3000000
```
More information regarding how to run the binary can be found by running it with the `--help` argument.
-55
View File
@@ -1,55 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Result;
use bip39::Mnemonic;
use nym_network_defaults::{NymNetworkDetails, VOUCHER_INFO};
use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient;
use nym_validator_client::nyxd::{self, DirectSigningHttpRpcNyxdClient};
use nym_validator_client::nyxd::{Coin, Fee, NyxdClient};
use std::str::FromStr;
use url::Url;
pub(crate) struct Client {
nyxd_client: DirectSigningHttpRpcNyxdClient,
mix_denom_base: String,
}
impl Client {
pub fn new(nyxd_url: &str, mnemonic: &str) -> Self {
let nyxd_url = Url::from_str(nyxd_url).unwrap();
let mnemonic = Mnemonic::from_str(mnemonic).unwrap();
let network_details = NymNetworkDetails::new_from_env();
let config = nyxd::Config::try_from_nym_network_details(&network_details)
.expect("failed to construct valid validator client config with the provided network");
let nyxd_client =
NyxdClient::connect_with_mnemonic(config, nyxd_url.as_ref(), mnemonic, None).unwrap();
Client {
nyxd_client,
mix_denom_base: network_details.chain_details.mix_denom.base,
}
}
pub async fn deposit(
&self,
amount: u64,
verification_key: String,
encryption_key: String,
fee: Option<Fee>,
) -> Result<String> {
let amount = Coin::new(amount as u128, self.mix_denom_base.clone());
Ok(self
.nyxd_client
.deposit(
amount,
String::from(VOUCHER_INFO),
verification_key,
encryption_key,
fee,
)
.await?
.transaction_hash
.to_string())
}
}
-82
View File
@@ -1,82 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{ArgGroup, Args, Subcommand};
use log::*;
use nym_bandwidth_controller::acquire::state::State;
use nym_bin_common::completions::ArgShell;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use crate::error::Result;
use crate::recovery_storage::RecoveryStorage;
#[derive(Subcommand)]
pub(crate) enum Command {
/// Run the binary to obtain a credential
Run(Run),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
#[derive(Args)]
#[clap(group(
ArgGroup::new("recov")
.required(true)
.args(&["amount", "recovery_mode"]),
))]
pub(crate) struct Run {
/// Home directory of the client that is supposed to use the credential.
#[clap(long)]
pub(crate) client_home_directory: std::path::PathBuf,
/// A mnemonic for the account that buys the credential
#[clap(long)]
pub(crate) mnemonic: String,
/// The amount of utokens the credential will hold. If recovery mode is enabled, this value
/// is not needed
#[clap(long, default_value = "0")]
pub(crate) amount: u64,
/// Path to a directory used to store recovery files for unconsumed deposits
#[clap(long)]
pub(crate) recovery_dir: std::path::PathBuf,
/// Recovery mode, when enabled, tries to recover any deposit data dumped in recovery_dir
#[clap(long)]
pub(crate) recovery_mode: bool,
}
pub(crate) async fn recover_credentials<C: DkgQueryClient + Send + Sync>(
client: &C,
recovery_storage: &RecoveryStorage,
shared_storage: &PersistentStorage,
) -> Result<()> {
for voucher in recovery_storage.unconsumed_vouchers()? {
let state = State::new(voucher);
if let Err(e) =
nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await
{
error!(
"Could not recover deposit {} due to {:?}, try again later",
state.voucher.tx_hash(),
e
)
} else {
info!(
"Converted deposit {} to a credential, removing recovery data for it",
state.voucher.tx_hash()
);
if let Err(e) = recovery_storage.remove_voucher(state.voucher.tx_hash().to_string()) {
warn!("Could not remove recovery data - {:?}", e);
}
}
}
Ok(())
}
-36
View File
@@ -1,36 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::SystemTimeError;
use thiserror::Error;
use nym_credential_storage::error::StorageError;
use nym_credentials::error::Error as CredentialError;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::ValidatorClientError;
pub type Result<T> = std::result::Result<T, CredentialClientError>;
#[derive(Error, Debug)]
pub enum CredentialClientError {
#[error("IO error: {0}")]
IOError(#[from] std::io::Error),
#[error("Bandwidth controller error: {0}")]
BandwidthControllerError(#[from] nym_bandwidth_controller::error::BandwidthControllerError),
#[error("Nyxd error: {0}")]
Nyxd(#[from] NyxdError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("Credential error: {0}")]
Credential(#[from] CredentialError),
#[error("Could not use shared storage")]
SharedStorageError(#[from] StorageError),
#[error("Could not get system time")]
SysTimeError(#[from] SystemTimeError),
}
-131
View File
@@ -1,131 +0,0 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod commands;
mod error;
mod recovery_storage;
use commands::*;
use error::Result;
use log::*;
use nym_bin_common::completions::fig_generate;
use nym_config::DEFAULT_DATA_DIR;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use std::process::exit;
use std::time::{Duration, SystemTime};
use clap::{CommandFactory, Parser};
use nym_bin_common::logging::setup_logging;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::nyxd::{Coin, Config};
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute
#[derive(Parser)]
#[clap(author = "Nymtech", version, about)]
struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
pub(crate) command: Command,
}
async fn block_until_coconut_is_available<C: DkgQueryClient + Send + Sync>(
client: &C,
) -> Result<()> {
loop {
let epoch = client.get_current_epoch().await?;
let current_timestamp_secs = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs();
if epoch.state.is_final() {
if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() {
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
exit(0);
}
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;
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));
}
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
setup_logging();
setup_env(args.config_env_file.as_ref());
let bin_name = "nym-credential-client";
match args.command {
Command::Run(r) => {
// we assume the structure of <home-dir>/data
let data_dir = r.client_home_directory.join(DEFAULT_DATA_DIR);
let paths = CommonClientPaths::new_default(data_dir);
let db_path = paths.credentials_database;
let shared_storage =
nym_credential_storage::initialise_persistent_storage(db_path).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
let network_details = NymNetworkDetails::new_from_env();
let config = Config::try_from_nym_network_details(&network_details).expect(
"failed to construct valid validator client config with the provided network",
);
let amount = Coin::new(
r.amount as u128,
network_details.chain_details.mix_denom.base,
);
let endpoint = network_details.endpoints[0].nyxd_url.as_str();
let client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
config,
endpoint,
r.mnemonic.parse().unwrap(),
)?;
block_until_coconut_is_available(&client).await?;
info!("Starting depositing funds, don't kill the process");
if !r.recovery_mode {
let state = nym_bandwidth_controller::acquire::deposit(&client, amount).await?;
if nym_bandwidth_controller::acquire::get_credential(
&state,
&client,
&shared_storage,
)
.await
.is_err()
{
warn!("Failed to obtain credential. Dumping recovery data.",);
match recovery_storage.insert_voucher(&state.voucher) {
Ok(file_path) => {
warn!("Dumped recovery data to {:?}. Try using recovery mode to convert it to a credential", file_path);
}
Err(e) => {
error!("Could not dump recovery data to file system due to {:?}, the deposit will be lost!", e)
}
}
}
} else {
recover_credentials(&client, &recovery_storage, &shared_storage).await?;
}
}
Command::Completions(c) => c.generate(&mut Cli::command(), bin_name),
Command::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
}
Ok(())
}
@@ -1,56 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use std::fs::{create_dir_all, read_dir, File};
use std::io::{Read, Write};
use std::path::PathBuf;
pub struct RecoveryStorage {
recovery_dir: PathBuf,
}
impl RecoveryStorage {
pub fn new(recovery_dir: PathBuf) -> std::io::Result<Self> {
create_dir_all(&recovery_dir)?;
Ok(Self { recovery_dir })
}
pub fn unconsumed_vouchers(&self) -> std::io::Result<impl Iterator<Item = BandwidthVoucher>> {
Ok(read_dir(&self.recovery_dir)?
.filter_map(|entry| entry.ok())
.filter_map(|entry| {
let path = entry.path();
if path.is_file() {
Some(path)
} else {
None
}
})
.filter_map(|path| File::open(path).ok())
.filter_map(|mut f| {
let mut buff = Vec::new();
if f.read_to_end(&mut buff).is_ok() {
Some(buff)
} else {
None
}
})
.filter_map(|buff| BandwidthVoucher::try_from_bytes(&buff).ok()))
}
pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> std::io::Result<PathBuf> {
let file_name = voucher.tx_hash().to_string();
let file_path = self.recovery_dir.join(file_name);
let mut file = File::create(&file_path)?;
let buff = voucher.to_bytes();
file.write_all(&buff)?;
Ok(file_path)
}
pub fn remove_voucher(&self, file_name: String) -> std::io::Result<()> {
let file_path = self.recovery_dir.join(file_name);
std::fs::remove_file(file_path)
}
}
@@ -105,10 +105,10 @@ impl ClientRequest {
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
std::iter::once(ClientRequestTag::Send as u8)
.chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant
.chain(conn_id_bytes.into_iter())
.chain(data_len_bytes.into_iter())
.chain(data.into_iter())
.chain(recipient.to_bytes()) // will not be length prefixed because the length is constant
.chain(conn_id_bytes)
.chain(data_len_bytes)
.chain(data)
.collect()
}
@@ -180,11 +180,11 @@ impl ClientRequest {
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
std::iter::once(ClientRequestTag::SendAnonymous as u8)
.chain(reply_surbs.to_be_bytes().into_iter())
.chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant
.chain(conn_id_bytes.into_iter())
.chain(data_len_bytes.into_iter())
.chain(data.into_iter())
.chain(reply_surbs.to_be_bytes())
.chain(recipient.to_bytes()) // will not be length prefixed because the length is constant
.chain(conn_id_bytes)
.chain(data_len_bytes)
.chain(data)
.collect()
}
@@ -258,10 +258,10 @@ impl ClientRequest {
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
std::iter::once(ClientRequestTag::Reply as u8)
.chain(sender_tag.to_bytes().into_iter())
.chain(conn_id_bytes.into_iter())
.chain(message_len_bytes.into_iter())
.chain(message.into_iter())
.chain(sender_tag.to_bytes())
.chain(conn_id_bytes)
.chain(message_len_bytes)
.chain(message)
.collect()
}
@@ -332,7 +332,7 @@ impl ClientRequest {
fn serialize_closed_connection(connection_id: u64) -> Vec<u8> {
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(ClientRequestTag::ClosedConnection as u8)
.chain(conn_id_bytes.into_iter())
.chain(conn_id_bytes)
.collect()
}
@@ -359,7 +359,7 @@ impl ClientRequest {
fn serialize_get_lane_queue_lengths(connection_id: u64) -> Vec<u8> {
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(ClientRequestTag::GetLaneQueueLength as u8)
.chain(conn_id_bytes.into_iter())
.chain(conn_id_bytes)
.collect()
}
@@ -67,15 +67,15 @@ impl ServerResponse {
if let Some(sender_tag) = reconstructed_message.sender_tag {
std::iter::once(ServerResponseTag::Received as u8)
.chain(std::iter::once(true as u8))
.chain(sender_tag.to_bytes().into_iter())
.chain(sender_tag.to_bytes())
.chain(message_len_bytes.iter().cloned())
.chain(reconstructed_message.message.into_iter())
.chain(reconstructed_message.message)
.collect()
} else {
std::iter::once(ServerResponseTag::Received as u8)
.chain(std::iter::once(false as u8))
.chain(message_len_bytes.iter().cloned())
.chain(reconstructed_message.message.into_iter())
.chain(reconstructed_message.message)
.collect()
}
}
@@ -149,7 +149,7 @@ impl ServerResponse {
// SELF_ADDRESS_RESPONSE_TAG || self_address
fn serialize_self_address(address: Recipient) -> Vec<u8> {
std::iter::once(ServerResponseTag::SelfAddress as u8)
.chain(address.to_bytes().into_iter())
.chain(address.to_bytes())
.collect()
}
@@ -211,8 +211,8 @@ impl ServerResponse {
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
std::iter::once(ServerResponseTag::Error as u8)
.chain(std::iter::once(error.kind as u8))
.chain(message_len_bytes.into_iter())
.chain(error.message.into_bytes().into_iter())
.chain(message_len_bytes)
.chain(error.message.into_bytes())
.collect()
}
+12 -4
View File
@@ -17,7 +17,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{
};
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_client_core::config::{GatewayEndpointConfig, TopologyStructure};
use nym_client_core::config::{GatewayEndpointConfig, GroupBy, TopologyStructure};
use nym_client_core::error::ClientCoreError;
use nym_config::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
@@ -101,9 +101,17 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16);
let no_per_hop_delays = args.medium_toggle;
let topology_structure = if args.medium_toggle || args.geo_routing.is_some() {
// TODO: rethink the default group. I just picked one for now.
TopologyStructure::GeoAware(args.geo_routing.unwrap_or(CountryGroup::Europe))
let topology_structure = if args.medium_toggle {
// Use the location of the network-requester
let address = config
.core
.socks5
.provider_mix_address
.parse()
.expect("failed to parse provider mix address");
TopologyStructure::GeoAware(GroupBy::NymAddress(address))
} else if let Some(code) = args.geo_routing {
TopologyStructure::GeoAware(GroupBy::CountryGroup(code))
} else {
TopologyStructure::default()
};
+2
View File
@@ -2503,6 +2503,7 @@ version = "1.1.15"
dependencies = [
"async-trait",
"base64 0.21.2",
"cfg-if 1.0.0",
"dashmap",
"dirs 4.0.0",
"futures",
@@ -2676,6 +2677,7 @@ version = "0.1.0"
dependencies = [
"bls12_381 0.5.0",
"cosmrs",
"log",
"nym-api-requests",
"nym-coconut-interface",
"nym-crypto",
+2 -2
View File
@@ -114,7 +114,7 @@ impl WasmTopologyExt for Arc<ClientState> {
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
return Err(WasmClientError::UnavailableNetworkTopology.into());
};
match current_topology.find_mix_by_identity(&mixnode_identity) {
@@ -135,7 +135,7 @@ impl WasmTopologyExt for Arc<ClientState> {
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
return Err(WasmClientError::UnavailableNetworkTopology.into());
};
let Some(mix) = current_topology.find_mix_by_identity(&mixnode_identity) else {
@@ -52,7 +52,7 @@ pub fn encode_payload_with_headers(
Ok(metadata) => {
let metadata = metadata.as_bytes().to_vec();
let size = (metadata.len() as u64).to_be_bytes().to_vec();
Ok(vec![size, metadata, payload].concat())
Ok([size, metadata, payload].concat())
}
Err(e) => Err(JsValue::from(JsError::new(
format!("Could not encode message: {}", e).as_str(),
+4
View File
@@ -1,6 +1,10 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// After reading https://github.com/rust-lang/rust-clippy/issues/11382
// I suspect we *maybe* have hit a false positive, but I'm not sure.
#![allow(clippy::arc_with_non_send_sync)]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
@@ -58,7 +58,7 @@ impl<'a> EphemeralTestReceiver<'a> {
let Some(received_packet) = packet else {
// can't do anything more...
console_error!("packet receiver has stopped processing results!");
return true
return true;
};
match received_packet {
Received::Message(msg) => {
+1
View File
@@ -10,6 +10,7 @@ rust-version = "1.66"
[dependencies]
async-trait = { workspace = true }
base64 = "0.21.2"
cfg-if = "1.0.0"
dashmap = "5.4.0"
dirs = "4.0"
futures = { workspace = true }
@@ -45,9 +45,6 @@ use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender,
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::NymApiClient;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::sync::Arc;
use tap::TapFallible;
use url::Url;
@@ -292,7 +289,6 @@ where
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
api_client: NymApiClient,
shutdown: TaskClient,
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
where
@@ -315,26 +311,20 @@ where
let gateway_address = gateway_config.gateway_listener.clone();
let gateway_id = gateway_config.gateway_id;
let gateway_sphinx = gateway_config.gateway_sphinx;
// TODO: in theory, at this point, this should be infallible
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let gateway_sphinx_key = encryption::PublicKey::from_base58_string(gateway_sphinx)
.map_err(ClientCoreError::UnableToCreateSphinxKeyFromGatewayId)?;
GatewayClient::new(
gateway_address,
managed_keys.identity_keypair(),
managed_keys.encryption_keypair(),
gateway_identity,
gateway_sphinx_key,
Some(managed_keys.must_get_gateway_shared_key()),
mixnet_message_sender,
ack_sender,
config.debug.gateway_connection.gateway_response_timeout,
bandwidth_controller,
api_client,
shutdown,
)
};
@@ -353,15 +343,6 @@ where
Ok(gateway_client)
}
fn random_api_client(&self) -> nym_validator_client::NymApiClient {
let endpoints = self.config.get_nym_api_endpoints();
let nym_api = endpoints
.choose(&mut thread_rng())
.expect("The list of validator apis is empty");
nym_validator_client::NymApiClient::new(nym_api.clone())
}
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
provider_from_config: config::TopologyStructure,
@@ -373,11 +354,13 @@ where
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
)),
config::TopologyStructure::GeoAware(group) => Box::new(GeoAwareTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
group,
)),
config::TopologyStructure::GeoAware(group_by) => {
Box::new(GeoAwareTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
group_by,
))
}
})
}
@@ -500,7 +483,6 @@ where
{
info!("Starting nym client");
let random_api_client = self.random_api_client();
// derive (or load) client keys and gateway configuration
let init_res = Self::initialise_keys_and_gateway(
self.setup_method,
@@ -555,7 +537,6 @@ where
bandwidth_controller,
mixnet_messages_sender,
ack_sender,
random_api_client,
task_manager.subscribe(),
)
.await?;
@@ -83,16 +83,6 @@ impl<'a> TopologyReadPermit<'a> {
Ok(topology)
}
pub fn try_get_raw_topology_ref(&'a self) -> 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)?;
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, Option<NymTopology>>> for TopologyReadPermit<'a> {
@@ -1,23 +1,48 @@
use std::{collections::HashMap, fmt};
use log::{debug, error, info};
use nym_explorer_api_requests::PrettyDetailedMixNodeBond;
use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
nym_topology_from_detailed,
provider_trait::{async_trait, TopologyProvider},
NymTopology,
};
use nym_validator_client::client::MixId;
use nym_validator_client::client::{IdentityKey, MixId};
use rand::{prelude::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
use tap::TapOptional;
use url::Url;
use crate::config::GroupBy;
const MIN_NODES_PER_LAYER: usize = 1;
const EXPLORER_API_MIXNODES_URL: &str = "https://explorer.nymtech.net/api/v1/mix-nodes";
#[cfg(target_arch = "wasm32")]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder().build().ok()
}
#[cfg(not(target_arch = "wasm32"))]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.ok()
}
// TODO: create a explorer-api-client
async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeBond>> {
reqwest::get(EXPLORER_API_MIXNODES_URL)
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
let explorer_api_url = Url::parse(&explorer_api_url)
.ok()?
.join("v1/mix-nodes")
.ok()?;
debug!("Fetching: {}", explorer_api_url);
reqwest_client()?
.get(explorer_api_url)
.send()
.await
.ok()?
.json::<Vec<PrettyDetailedMixNodeBond>>()
@@ -25,6 +50,25 @@ async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeB
.ok()
}
// TODO: create a explorer-api-client
async fn fetch_gateways_from_explorer_api() -> Option<Vec<PrettyDetailedGatewayBond>> {
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
let explorer_api_url = Url::parse(&explorer_api_url)
.ok()?
.join("v1/gateways")
.ok()?;
debug!("Fetching: {}", explorer_api_url);
reqwest_client()?
.get(explorer_api_url)
.send()
.await
.ok()?
.json::<Vec<PrettyDetailedGatewayBond>>()
.await
.ok()
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum CountryGroup {
Europe,
@@ -191,6 +235,23 @@ fn group_mixnodes_by_country_code(
})
}
fn group_gateways_by_country_code(
gateways: Vec<PrettyDetailedGatewayBond>,
) -> HashMap<CountryGroup, Vec<IdentityKey>> {
gateways.into_iter().fold(
HashMap::<CountryGroup, Vec<IdentityKey>>::new(),
|mut acc, g| {
if let Some(ref location) = g.location {
let country_code = location.two_letter_iso_country_code.clone();
let group_code = CountryGroup::new(country_code.as_str());
let gateways = acc.entry(group_code).or_insert_with(Vec::new);
gateways.push(g.gateway.identity_key)
}
acc
},
)
}
fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
let mixnode_distribution = mixnodes
.iter()
@@ -200,6 +261,15 @@ fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
debug!("Mixnode distribution - {}", mixnode_distribution);
}
fn log_gateway_distribution(gateways: &HashMap<CountryGroup, Vec<IdentityKey>>) {
let gateway_distribution = gateways
.iter()
.map(|(k, v)| format!("{}: {}", k, v.len()))
.collect::<Vec<_>>()
.join(", ");
debug!("Gateway distribution - {}", gateway_distribution);
}
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
let mixes = topology.mixes();
if mixes.keys().len() < 3 {
@@ -222,7 +292,7 @@ fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
pub struct GeoAwareTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
filter_on: CountryGroup,
filter_on: GroupBy,
client_version: String,
}
@@ -230,7 +300,7 @@ impl GeoAwareTopologyProvider {
pub fn new(
mut nym_api_urls: Vec<Url>,
client_version: String,
filter_on: CountryGroup,
filter_on: GroupBy,
) -> GeoAwareTopologyProvider {
log::info!(
"Creating geo-aware topology provider with filter on {:?}",
@@ -272,6 +342,38 @@ impl GeoAwareTopologyProvider {
return None;
};
debug!("Fetching gateways from explorer-api...");
let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
// Determine what we should filter around
let filter_on = match self.filter_on {
GroupBy::CountryGroup(group) => group,
GroupBy::NymAddress(recipient) => {
// Convert recipient into a country group by extracting out the gateway part and
// using that as the country code.
let gateway = recipient.gateway().to_base58_string();
// Lookup the location of this gateway by using the location data from the
// explorer-api
let gateway_location = gateways_from_explorer_api
.iter()
.find(|g| g.gateway.identity_key == gateway)
.and_then(|g| g.location.clone())
.map(|location| location.two_letter_iso_country_code)
.tap_none(|| error!("No location found for the gateway: {}", gateway))?;
debug!(
"Filtering on nym-address: {}, with location: {}",
recipient, gateway_location
);
CountryGroup::new(&gateway_location)
}
};
debug!("Filter group: {}", filter_on);
// Partition mixnodes_from_explorer_api according to the value of
// two_letter_iso_country_code.
// NOTE: we construct the full distribution here, but only use the one we're interested in.
@@ -280,8 +382,16 @@ impl GeoAwareTopologyProvider {
let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api);
log_mixnode_distribution(&mixnode_distribution);
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&self.filter_on) else {
error!("no mixnodes found for: {}", self.filter_on);
let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api);
log_gateway_distribution(&gateway_distribution);
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&filter_on) else {
error!("no mixnodes found for: {}", filter_on);
return None;
};
let Some(filtered_gateway_ids) = gateway_distribution.get(&filter_on) else {
error!("no gateways found for: {}", filter_on);
return None;
};
@@ -290,6 +400,11 @@ impl GeoAwareTopologyProvider {
.filter(|m| filtered_mixnode_ids.contains(&m.mix_id()))
.collect::<Vec<_>>();
let gateways = gateways
.into_iter()
.filter(|g| filtered_gateway_ids.contains(g.identity()))
.collect::<Vec<_>>();
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
@@ -9,9 +9,9 @@ use nym_topology::provider_trait::TopologyProvider;
use nym_topology::NymTopologyError;
use std::time::Duration;
pub mod accessor;
mod accessor;
pub mod geo_aware_provider;
pub mod nym_api_provider;
pub(crate) mod nym_api_provider;
// TODO: move it to config later
const MAX_FAILURE_COUNT: usize = 10;
@@ -9,7 +9,7 @@ use rand::prelude::SliceRandom;
use rand::thread_rng;
use url::Url;
pub struct NymApiTopologyProvider {
pub(crate) struct NymApiTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
nym_api_urls: Vec<Url>,
@@ -18,7 +18,7 @@ pub struct NymApiTopologyProvider {
}
impl NymApiTopologyProvider {
pub fn new(mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
pub(crate) fn new(mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
NymApiTopologyProvider {
@@ -77,34 +77,13 @@ impl NymApiTopologyProvider {
Ok(gateways) => gateways,
};
let all_mixes = match self.validator_client.get_all_mixnodes().await {
Err(err) => {
error!("failed to get all mixes - {err}");
return None;
}
Ok(epoch) => epoch,
};
let all_gateways = match self.validator_client.get_all_gateways().await {
Err(err) => {
error!("failed to get all gateways - {err}");
return None;
}
Ok(epoch) => epoch,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.with_all_mixes(all_mixes.clone())
.with_all_gateways(all_gateways.clone())
.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();
let empty_topology = NymTopology::empty()
.with_all_mixes(all_mixes)
.with_all_gateways(all_gateways);
Some(empty_topology)
None
} else {
Some(topology)
}
+14 -13
View File
@@ -2,8 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails;
use nym_crypto::asymmetric::{encryption, identity};
use nym_sphinx::params::{PacketSize, PacketType};
use nym_crypto::asymmetric::identity;
use nym_sphinx::{
addressing::clients::Recipient,
params::{PacketSize, PacketType},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -216,8 +219,6 @@ pub struct GatewayEndpointConfig {
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
pub gateway_sphinx: String,
/// Address of the gateway owner to which the client should send messages.
pub gateway_owner: String,
@@ -230,13 +231,11 @@ impl GatewayEndpointConfig {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(constructor))]
pub fn new(
gateway_id: String,
gateway_sphinx: String,
gateway_owner: String,
gateway_listener: String,
) -> GatewayEndpointConfig {
GatewayEndpointConfig {
gateway_id,
gateway_sphinx,
gateway_owner,
gateway_listener,
}
@@ -249,11 +248,6 @@ impl GatewayEndpointConfig {
identity::PublicKey::from_base58_string(&self.gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
}
pub fn try_get_gateway_sphinx_key(&self) -> Result<encryption::PublicKey, ClientCoreError> {
encryption::PublicKey::from_base58_string(&self.gateway_sphinx)
.map_err(ClientCoreError::UnableToCreateSphinxKeyFromGatewayId)
}
}
impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
@@ -261,7 +255,6 @@ impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
let gateway_listener = node.clients_address();
GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_sphinx: node.sphinx_key.to_base58_string(),
gateway_owner: node.owner,
gateway_listener,
}
@@ -490,11 +483,19 @@ pub struct Topology {
pub topology_structure: TopologyStructure,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructure {
#[default]
NymApi,
GeoAware(CountryGroup),
GeoAware(GroupBy),
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupBy {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl Default for Topology {
@@ -68,7 +68,6 @@ pub struct ConfigV1_1_20<T> {
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV1_1_20 {
pub gateway_id: String,
pub gateway_sphinx: String,
pub gateway_owner: String,
pub gateway_listener: String,
}
@@ -77,7 +76,6 @@ impl From<GatewayEndpointConfigV1_1_20> for GatewayEndpointConfigV1_1_20_2 {
fn from(value: GatewayEndpointConfigV1_1_20) -> Self {
GatewayEndpointConfigV1_1_20_2 {
gateway_id: value.gateway_id,
gateway_sphinx: value.gateway_sphinx,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
}
@@ -73,8 +73,6 @@ pub struct GatewayEndpointConfigV1_1_20_2 {
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
pub gateway_sphinx: String,
/// Address of the gateway owner to which the client should send messages.
pub gateway_owner: String,
@@ -86,7 +84,6 @@ impl From<GatewayEndpointConfigV1_1_20_2> for GatewayEndpointConfig {
fn from(value: GatewayEndpointConfigV1_1_20_2) -> Self {
GatewayEndpointConfig {
gateway_id: value.gateway_id,
gateway_sphinx: value.gateway_sphinx,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
}
-4
View File
@@ -1,7 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_topology::gateway::GatewayConversionError;
@@ -59,9 +58,6 @@ pub enum ClientCoreError {
#[error("The gateway id is invalid - {0}")]
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
#[error("The gateway sphinx is invalid - {0}")]
UnableToCreateSphinxKeyFromGatewayId(KeyRecoveryError),
#[error("The identity of the gateway is unknown - did you run init?")]
GatewayIdUnknown,
+1 -7
View File
@@ -6,10 +6,9 @@ use crate::error::ClientCoreError;
use crate::init::RegistrationResult;
use futures::{SinkExt, StreamExt};
use log::{debug, info, trace, warn};
use nym_crypto::asymmetric::{encryption, identity};
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_topology::{filter::VersionFilterable, gateway};
use nym_validator_client::NymApiClient;
use rand::{seq::SliceRandom, Rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
@@ -202,18 +201,13 @@ pub(super) fn uniformly_random_gateway<R: Rng>(
pub(super) async fn register_with_gateway(
gateway: &GatewayEndpointConfig,
our_identity: Arc<identity::KeyPair>,
our_sphinx: Arc<encryption::KeyPair>,
nym_api_client: NymApiClient,
) -> Result<RegistrationResult, ClientCoreError> {
let timeout = Duration::from_millis(1500);
let mut gateway_client = GatewayClient::new_init(
gateway.gateway_listener.clone(),
gateway.try_get_gateway_identity_key()?,
gateway.try_get_gateway_sphinx_key()?,
our_identity.clone(),
our_sphinx.clone(),
timeout,
nym_api_client,
);
gateway_client
.establish_connection()
+1 -14
View File
@@ -20,9 +20,7 @@ use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
use nym_topology::gateway;
use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient;
use rand::rngs::OsRng;
use rand::seq::SliceRandom;
use serde::Serialize;
use std::fmt::{Debug, Display};
use std::sync::Arc;
@@ -302,7 +300,6 @@ pub async fn setup_gateway_from<K, D>(
details_store: &D,
overwrite_data: bool,
gateways: Option<&[gateway::Node]>,
nym_api_client: NymApiClient,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
@@ -420,12 +417,9 @@ where
// get our identity key
let our_identity = managed_keys.identity_keypair();
let our_sphinx = managed_keys.encryption_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
let registration_result =
helpers::register_with_gateway(&gateway_details, our_identity, our_sphinx, nym_api_client)
.await?;
helpers::register_with_gateway(&gateway_details, our_identity).await?;
let shared_keys = registration_result.shared_keys;
let persisted_details = PersistedGatewayDetails::new(gateway_details, &shared_keys);
@@ -463,19 +457,12 @@ where
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers.unwrap_or_default()).await?;
let nym_api = validator_servers
.unwrap_or_default()
.choose(&mut rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
setup_gateway_from(
setup,
key_store,
details_store,
overwrite_data,
Some(&gateways),
client,
)
.await
}
@@ -35,9 +35,6 @@ version = "0.13"
default-features = false
# non-wasm-only dependencies
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-noise]
path = "../../nymnoise"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
version = "1.24.1"
features = ["macros", "rt", "net", "sync", "time"]
@@ -14,7 +14,7 @@ use nym_bandwidth_controller::BandwidthController;
use nym_coconut_interface::Credential;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::{encryption, identity};
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use nym_gateway_requests::iv::IV;
use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys};
@@ -23,23 +23,16 @@ use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::TaskClient;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::NymApiClient;
use rand::rngs::OsRng;
use std::convert::TryFrom;
use std::sync::Arc;
use std::time::Duration;
use tungstenite::protocol::Message;
#[cfg(not(target_arch = "wasm32"))]
use nym_noise::upgrade_noise_initiator;
#[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::client_async;
use tokio_tungstenite::connect_async;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
@@ -55,15 +48,12 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
bandwidth_remaining: i64,
gateway_address: String,
gateway_identity: identity::PublicKey,
gateway_sphinx: encryption::PublicKey,
local_identity: Arc<identity::KeyPair>,
local_sphinx: Arc<encryption::KeyPair>,
shared_key: Option<Arc<SharedKeys>>,
connection: SocketState,
packet_router: PacketRouter,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController<C, St>>,
nym_api_client: NymApiClient,
// reconnection related variables
/// Specifies whether client should try to reconnect to gateway on connection failure.
@@ -84,16 +74,13 @@ impl<C, St> GatewayClient<C, St> {
pub fn new(
gateway_address: String,
local_identity: Arc<identity::KeyPair>,
local_sphinx: Arc<encryption::KeyPair>,
gateway_identity: identity::PublicKey,
gateway_sphinx: encryption::PublicKey,
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
shared_key: Option<Arc<SharedKeys>>,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController<C, St>>,
nym_api_client: NymApiClient,
shutdown: TaskClient,
) -> Self {
GatewayClient {
@@ -101,16 +88,13 @@ impl<C, St> GatewayClient<C, St> {
disabled_credentials_mode: true,
bandwidth_remaining: 0,
gateway_address,
local_identity,
local_sphinx,
gateway_identity,
gateway_sphinx,
local_identity,
shared_key,
connection: SocketState::NotConnected,
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
response_timeout_duration,
bandwidth_controller,
nym_api_client,
should_reconnect_on_failure: true,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
@@ -178,59 +162,7 @@ impl<C, St> GatewayClient<C, St> {
#[cfg(not(target_arch = "wasm32"))]
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
let socket_addr: SocketAddr = self.gateway_address.parse().unwrap();
let connection_fut = TcpStream::connect(socket_addr);
//arbitrary TO, it's a POC
let noise_conn = match tokio::time::timeout(Duration::from_secs(5), connection_fut).await {
Ok(stream_res) => match stream_res {
Ok(stream) => {
debug!("Managed to establish connection to gateway");
let current_epoch_id = match self.nym_api_client.get_current_epoch_id().await {
Ok(epoch_id) => epoch_id,
Err(err) => {
error!("Failed to retrieve epoch Id for Noise handshake - {err}");
return Err(GatewayClientError::ConnectionNotEstablished);
}
};
let noise_stream = match upgrade_noise_initiator(
stream,
None, //as a client, the gateway cannot know my pub key
&self.local_sphinx.private_key().to_bytes(),
&self.gateway_sphinx.to_bytes(),
current_epoch_id,
)
.await
{
Ok(noise_stream) => noise_stream,
Err(err) => {
error!(
"Failed to perform Noise handshake with {:?} - {err}",
self.gateway_address
);
return Err(GatewayClientError::ConnectionNotEstablished);
}
};
debug!(
"Noise initiator handshake completed for {:?}",
self.gateway_address
);
noise_stream
}
Err(err) => {
debug!("failed to establish connection to gateway (err: {})", err);
return Err(GatewayClientError::NetworkIoError(err));
}
},
Err(_) => {
debug!("failed to connect to {} within 5s", self.gateway_address);
return Err(GatewayClientError::Timeout);
}
};
let ws_address = format!("ws://{}", self.gateway_address);
let ws_stream = match client_async(ws_address, noise_conn).await {
let ws_stream = match connect_async(&self.gateway_address).await {
Ok((ws_stream, _)) => ws_stream,
Err(e) => return Err(GatewayClientError::NetworkError(e)),
};
@@ -834,11 +766,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
pub fn new_init(
gateway_address: String,
gateway_identity: identity::PublicKey,
gateway_sphinx: encryption::PublicKey,
local_identity: Arc<identity::KeyPair>,
local_sphinx: Arc<encryption::KeyPair>,
response_timeout_duration: Duration,
nym_api_client: NymApiClient,
) -> Self {
use futures::channel::mpsc;
@@ -855,15 +784,12 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
gateway_sphinx,
local_identity,
local_sphinx,
shared_key: None,
connection: SocketState::NotConnected,
packet_router,
response_timeout_duration,
bandwidth_controller: None,
nym_api_client,
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
@@ -891,15 +817,12 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
bandwidth_remaining: self.bandwidth_remaining,
gateway_address: self.gateway_address,
gateway_identity: self.gateway_identity,
gateway_sphinx: self.gateway_sphinx,
local_sphinx: self.local_sphinx,
local_identity: self.local_identity,
shared_key: self.shared_key,
connection: self.connection,
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
response_timeout_duration,
bandwidth_controller,
nym_api_client: self.nym_api_client,
should_reconnect_on_failure: self.should_reconnect_on_failure,
reconnection_attempts: self.reconnection_attempts,
reconnection_backoff: self.reconnection_backoff,
@@ -19,9 +19,6 @@ pub enum GatewayClientError {
#[error("There was a network error - {0}")]
NetworkError(#[from] WsError),
#[error("There was a network error - {0}")]
NetworkIoError(#[from] io::Error),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
#[error("There was a network error")]
@@ -14,9 +14,9 @@ use std::sync::Arc;
use tungstenite::Message;
#[cfg(not(target_arch = "wasm32"))]
use nym_noise::NoiseStream;
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures;
@@ -26,7 +26,7 @@ use wasm_utils::websocket::JSWebsocket;
// type alias for not having to type the whole thing every single time (and now it makes it easier
// to use different types based on compilation target)
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<NoiseStream>;
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
#[cfg(target_arch = "wasm32")]
type WsConn = JSWebsocket;
@@ -15,7 +15,3 @@ tokio-util = { version = "0.7.4", features = ["codec"] }
# internal
nym-sphinx = { path = "../../nymsphinx" }
nym-task = { path = "../../task" }
nym-client-core = { path = "../../client-core" }
nym-noise = { path = "../../nymnoise"}
nym-crypto = { path = "../../crypto" }
nym-validator-client = { path = "../validator-client"}
+9 -73
View File
@@ -4,15 +4,11 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_client_core::client::topology_control::accessor::TopologyAccessor;
use nym_crypto::asymmetric::encryption;
use nym_noise::upgrade_noise_initiator_with_topology;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::params::PacketType;
use nym_sphinx::NymPacket;
use nym_validator_client::NymApiClient;
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
@@ -63,9 +59,6 @@ pub trait SendWithoutResponse {
pub struct Client {
conn_new: HashMap<NymNodeRoutingAddress, ConnectionSender>,
config: Config,
topology_access: TopologyAccessor,
api_client: NymApiClient,
local_identity: Arc<encryption::KeyPair>,
}
struct ConnectionSender {
@@ -83,18 +76,10 @@ impl ConnectionSender {
}
impl Client {
pub fn new(
config: Config,
topology_access: TopologyAccessor,
api_client: NymApiClient,
local_identity: Arc<encryption::KeyPair>,
) -> Client {
pub fn new(config: Config) -> Client {
Client {
conn_new: HashMap::new(),
config,
topology_access,
api_client,
local_identity,
}
}
@@ -103,10 +88,6 @@ impl Client {
receiver: mpsc::Receiver<FramedNymPacket>,
connection_timeout: Duration,
current_reconnection: &AtomicU32,
topology_access: TopologyAccessor,
api_client: NymApiClient,
local_public_key: &[u8],
local_private_key: &[u8],
) {
let connection_fut = TcpStream::connect(address);
@@ -116,40 +97,7 @@ impl Client {
debug!("Managed to establish connection to {}", address);
// if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release);
//Get the topology, because we need the keys for the handshake
let topology_ref = match topology_access.current_topology().await {
Some(topology) => topology,
None => {
error!("Cannot perform Noise handshake to {address}, due to topology error");
return;
}
};
let epoch_id = match api_client.get_current_epoch_id().await {
Ok(id) => id,
Err(err) => {
error!("Cannot perform Noise handshake to {address}, due to epoch id error - {err}");
return;
}
};
let noise_stream = match upgrade_noise_initiator_with_topology(
stream,
&topology_ref,
epoch_id,
local_public_key,
local_private_key,
)
.await
{
Ok(noise_stream) => noise_stream,
Err(err) => {
error!("Failed to perform Noise handshake with {address} - {err}");
return;
}
};
debug!("Noise initiator handshake completed for {:?}", address);
Framed::new(noise_stream, NymCodec)
Framed::new(stream, NymCodec)
}
Err(err) => {
debug!(
@@ -227,11 +175,6 @@ impl Client {
// copy the value before moving into another task
let initial_connection_timeout = self.config.initial_connection_timeout;
let topology_access_clone = self.topology_access.clone();
let api_client_clone = self.api_client.clone();
let local_public_key = self.local_identity.public_key().to_bytes();
let local_private_key = self.local_identity.private_key().to_bytes();
tokio::spawn(async move {
// before executing the manager, wait for what was specified, if anything
if let Some(backoff) = backoff {
@@ -244,10 +187,6 @@ impl Client {
receiver,
initial_connection_timeout,
&current_reconnection_attempt,
topology_access_clone,
api_client_clone,
&local_public_key,
&local_private_key,
)
.await
});
@@ -316,16 +255,13 @@ mod tests {
use super::*;
fn dummy_client() -> Client {
Client::new(
Config {
initial_reconnection_backoff: Duration::from_millis(10_000),
maximum_reconnection_backoff: Duration::from_millis(300_000),
initial_connection_timeout: Duration::from_millis(1_500),
maximum_connection_buffer_size: 128,
use_legacy_version: false,
},
TopologyAccessor::new(),
)
Client::new(Config {
initial_reconnection_backoff: Duration::from_millis(10_000),
maximum_reconnection_backoff: Duration::from_millis(300_000),
initial_connection_timeout: Duration::from_millis(1_500),
maximum_connection_buffer_size: 128,
use_legacy_version: false,
})
}
#[test]
@@ -5,11 +5,7 @@ use crate::client::{Client, Config, SendWithoutResponse};
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_client_core::client::topology_control::accessor::TopologyAccessor;
use nym_crypto::asymmetric::encryption;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_validator_client::NymApiClient;
use std::sync::Arc;
use std::time::Duration;
pub type MixForwardingSender = mpsc::UnboundedSender<MixPacket>;
@@ -30,9 +26,6 @@ impl PacketForwarder {
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_version: bool,
topology_access: TopologyAccessor,
api_client: NymApiClient,
local_identity: Arc<encryption::KeyPair>,
shutdown: nym_task::TaskClient,
) -> (PacketForwarder, MixForwardingSender) {
let client_config = Config::new(
@@ -47,12 +40,7 @@ impl PacketForwarder {
(
PacketForwarder {
mixnet_client: Client::new(
client_config,
topology_access,
api_client,
local_identity,
),
mixnet_client: Client::new(client_config),
packet_receiver,
shutdown,
},
@@ -65,7 +65,7 @@ features = ["tokio"]
[dev-dependencies]
bip39 = { workspace = true }
cosmrs = { workspace = true, features = ["bip32"] }
ts-rs = "6.1.2"
ts-rs = { workspace = true }
[[example]]
name = "offline_signing"
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use cosmrs::bank::MsgSend;
use cosmrs::rpc::HttpClient;
use cosmrs::tx::Msg;
use cosmrs::{tx, AccountId, Coin, Denom};
use nym_validator_client::http_client;
use nym_validator_client::nyxd::CosmWasmClient;
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
use nym_validator_client::signing::tx_signer::TxSigner;
@@ -27,7 +27,7 @@ async fn main() {
// possibly remote client that doesn't do ANY signing
// (only broadcasts + queries for sequence numbers)
let broadcaster = HttpClient::new(validator).unwrap();
let broadcaster = http_client(validator).unwrap();
// get signer information
let sequence_response = broadcaster.get_sequence(&signer_address).await.unwrap();
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
setup_env(Some("../../../envs/qa-qwerty.env"));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
setup_env(Some("../../../envs/qa-qwerty.env"));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
@@ -26,6 +26,8 @@ pub use nym_mixnet_contract_common::{
// re-export the type to not break existing imports
pub use crate::coconut::CoconutApiClient;
#[cfg(feature = "http-client")]
use crate::rpc::http_client;
#[cfg(feature = "http-client")]
use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient};
@@ -95,7 +97,7 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
config: Config,
mnemonic: bip39::Mnemonic,
) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?;
let rpc_client = http_client(config.nyxd_url.as_str())?;
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
@@ -126,7 +128,7 @@ impl Client<ReqwestRpcClient, DirectSecp256k1HdWallet> {
#[cfg(feature = "http-client")]
impl Client<HttpRpcClient> {
pub fn new_query(config: Config) -> Result<QueryHttpRpcValidatorClient, ValidatorClientError> {
let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?;
let rpc_client = http_client(config.nyxd_url.as_str())?;
Ok(Self::new_with_rpc_client(config, rpc_client))
}
@@ -170,6 +172,10 @@ impl<C, S> Client<C, S> {
// validator-api wrappers
impl<C, S> Client<C, S> {
pub fn api_url(&self) -> &Url {
self.nym_api.current_url()
}
pub fn change_nym_api(&mut self, new_endpoint: Url) {
self.nym_api.change_url(new_endpoint)
}
@@ -240,6 +246,10 @@ impl NymApiClient {
NymApiClient { nym_api }
}
pub fn api_url(&self) -> &Url {
self.nym_api.current_url()
}
pub fn change_nym_api(&mut self, new_endpoint: Url) {
self.nym_api.change_url(new_endpoint);
}
@@ -260,24 +270,10 @@ impl NymApiClient {
Ok(self.nym_api.get_mixnodes().await?)
}
pub async fn get_all_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.nym_api.get_all_mixnodes().await?)
}
pub async fn get_all_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.nym_api.get_all_gateways().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.nym_api.get_gateways().await?)
}
pub async fn get_current_epoch_id(
&self,
) -> Result<nym_mixnet_contract_common::EpochId, ValidatorClientError> {
Ok(self.nym_api.get_current_epoch().await?.current_epoch_id())
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
@@ -20,6 +20,8 @@ pub use nym_api_requests::*;
#[cfg(feature = "http-client")]
pub use cosmrs::rpc::HttpClient as HttpRpcClient;
#[cfg(feature = "http-client")]
pub use rpc::http_client;
// some type aliasing
@@ -14,7 +14,7 @@ use nym_api_requests::models::{
StakeSaturationResponse, UptimeResponse,
};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixId};
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
use nym_service_provider_directory_common::response::ServicesListResponse;
use reqwest::{Response, StatusCode};
@@ -130,27 +130,11 @@ impl Client {
}
}
pub async fn get_current_epoch(&self) -> Result<Interval, NymAPIError> {
self.query_nym_api(
&[routes::API_VERSION, routes::EPOCH, routes::CURRENT],
NO_PARAMS,
)
.await
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
pub async fn get_all_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ALL],
NO_PARAMS,
)
.await
}
pub async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
&[
@@ -197,14 +181,6 @@ impl Client {
.await
}
pub async fn get_all_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
self.query_nym_api(
&[routes::API_VERSION, routes::GATEWAYS, routes::ALL],
NO_PARAMS,
)
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
@@ -6,10 +6,6 @@ use nym_network_defaults::NYM_API_VERSION;
pub const API_VERSION: &str = NYM_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const ALL: &str = "all";
pub const EPOCH: &str = "epoch";
pub const CURRENT: &str = "current";
pub const DETAILED: &str = "detailed";
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
@@ -3,18 +3,25 @@
use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Config, GasPrice};
use crate::nyxd::{Config, GasPrice, Hash, Height};
use crate::rpc::TendermintRpcClient;
use crate::signing::{
signer::{NoSigner, OfflineSigner},
AccountData,
};
use async_trait::async_trait;
use tendermint_rpc::{Error as TendermintRpcError, SimpleRequest};
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
use cosmrs::tx::{Raw, SignDoc};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use tendermint_rpc::endpoint::*;
use tendermint_rpc::query::Query;
use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest};
#[cfg(feature = "http-client")]
use crate::http_client;
#[cfg(feature = "http-client")]
use cosmrs::rpc::{HttpClient, HttpClientUrl};
use cosmrs::tx::{Raw, SignDoc};
pub mod client_traits;
mod helpers;
@@ -73,7 +80,7 @@ impl<S> MaybeSigningClient<HttpClient, S> {
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
self.client = HttpClient::new(new_endpoint)?;
self.client = http_client(new_endpoint)?;
Ok(())
}
}
@@ -85,6 +92,216 @@ where
C: TendermintRpcClient + Send + Sync,
S: Send + Sync,
{
async fn abci_info(&self) -> Result<abci::response::Info, TendermintRpcError> {
self.client.abci_info().await
}
async fn abci_query<V>(
&self,
path: Option<String>,
data: V,
height: Option<Height>,
prove: bool,
) -> Result<abci_query::AbciQuery, TendermintRpcError>
where
V: Into<Vec<u8>> + Send,
{
self.client.abci_query(path, data, height, prove).await
}
async fn block<H>(&self, height: H) -> Result<block::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.block(height).await
}
async fn block_by_hash(
&self,
hash: Hash,
) -> Result<block_by_hash::Response, TendermintRpcError> {
self.client.block_by_hash(hash).await
}
async fn latest_block(&self) -> Result<block::Response, TendermintRpcError> {
self.client.latest_block().await
}
async fn header<H>(&self, height: H) -> Result<header::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.header(height).await
}
async fn header_by_hash(
&self,
hash: Hash,
) -> Result<header_by_hash::Response, TendermintRpcError> {
self.client.header_by_hash(hash).await
}
async fn block_results<H>(
&self,
height: H,
) -> Result<block_results::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.block_results(height).await
}
async fn latest_block_results(&self) -> Result<block_results::Response, TendermintRpcError> {
self.client.latest_block_results().await
}
async fn block_search(
&self,
query: Query,
page: u32,
per_page: u8,
order: Order,
) -> Result<block_search::Response, TendermintRpcError> {
self.client.block_search(query, page, per_page, order).await
}
async fn blockchain<H>(
&self,
min: H,
max: H,
) -> Result<blockchain::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.blockchain(min, max).await
}
async fn broadcast_tx_async<T>(
&self,
tx: T,
) -> Result<broadcast::tx_async::Response, TendermintRpcError>
where
T: Into<Vec<u8>> + Send,
{
self.client.broadcast_tx_async(tx).await
}
async fn broadcast_tx_sync<T>(
&self,
tx: T,
) -> Result<broadcast::tx_sync::Response, TendermintRpcError>
where
T: Into<Vec<u8>> + Send,
{
self.client.broadcast_tx_sync(tx).await
}
async fn broadcast_tx_commit<T>(
&self,
tx: T,
) -> Result<broadcast::tx_commit::Response, TendermintRpcError>
where
T: Into<Vec<u8>> + Send,
{
self.client.broadcast_tx_commit(tx).await
}
async fn commit<H>(&self, height: H) -> Result<commit::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.commit(height).await
}
async fn consensus_params<H>(
&self,
height: H,
) -> Result<consensus_params::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.consensus_params(height).await
}
async fn consensus_state(&self) -> Result<consensus_state::Response, TendermintRpcError> {
self.client.consensus_state().await
}
async fn validators<H>(
&self,
height: H,
paging: Paging,
) -> Result<validators::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.validators(height, paging).await
}
async fn latest_consensus_params(
&self,
) -> Result<consensus_params::Response, TendermintRpcError> {
self.client.latest_consensus_params().await
}
async fn latest_commit(&self) -> Result<commit::Response, TendermintRpcError> {
self.client.latest_commit().await
}
async fn health(&self) -> Result<(), TendermintRpcError> {
self.client.health().await
}
async fn genesis<AppState>(&self) -> Result<Genesis<AppState>, TendermintRpcError>
where
AppState: Debug + Serialize + DeserializeOwned + Send,
{
self.client.genesis().await
}
async fn net_info(&self) -> Result<net_info::Response, TendermintRpcError> {
self.client.net_info().await
}
async fn status(&self) -> Result<status::Response, TendermintRpcError> {
self.client.status().await
}
async fn broadcast_evidence(
&self,
e: Evidence,
) -> Result<evidence::Response, TendermintRpcError> {
self.client.broadcast_evidence(e).await
}
async fn tx(&self, hash: Hash, prove: bool) -> Result<tx::Response, TendermintRpcError> {
self.client.tx(hash, prove).await
}
async fn tx_search(
&self,
query: Query,
prove: bool,
page: u32,
per_page: u8,
order: Order,
) -> Result<tx_search::Response, TendermintRpcError> {
self.client
.tx_search(query, prove, page, per_page, order)
.await
}
#[cfg(any(
feature = "tendermint-rpc/http-client",
feature = "tendermint-rpc/websocket-client"
))]
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
where
T: Into<core::time::Duration> + Send,
{
self.client.wait_until_healthy(timeout).await
}
async fn perform<R>(&self, request: R) -> Result<R::Output, TendermintRpcError>
where
R: SimpleRequest,
@@ -9,19 +9,25 @@ use crate::nyxd::cosmwasm_client::types::{
use crate::nyxd::cosmwasm_client::MaybeSigningClient;
use crate::nyxd::error::NyxdError;
use crate::nyxd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
use crate::signing::signer::NoSigner;
use crate::signing::signer::OfflineSigner;
use crate::signing::tx_signer::TxSigner;
use crate::signing::AccountData;
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
use async_trait::async_trait;
use cosmrs::cosmwasm;
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
use cosmrs::tx::{Msg, Raw, SignDoc};
use cosmwasm_std::Addr;
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
use serde::Serialize;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use std::time::SystemTime;
use tendermint_rpc::endpoint::block::Response as BlockResponse;
use tendermint_rpc::Error as TendermintRpcError;
use tendermint_rpc::endpoint::*;
use tendermint_rpc::{Error as TendermintRpcError, Order};
use url::Url;
pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
pub use crate::nyxd::fee::Fee;
@@ -45,14 +51,13 @@ pub use tendermint_rpc::{
};
pub use tendermint_rpc::{Request, Response, SimpleRequest};
// #[cfg(feature = "http-client")]
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
#[cfg(feature = "http-client")]
use crate::http_client;
#[cfg(feature = "http-client")]
use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient};
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
#[cfg(feature = "http-client")]
use cosmrs::rpc::{HttpClient, HttpClientUrl};
use url::Url;
use tendermint_rpc::query::Query;
pub mod coin;
pub mod contract_traits;
@@ -97,7 +102,7 @@ impl NyxdClient<HttpClient> {
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let client = HttpClient::new(endpoint)?;
let client = http_client(endpoint)?;
Ok(NyxdClient {
client: MaybeSigningClient::new(client, (&config).into()),
@@ -140,7 +145,7 @@ impl NyxdClient<HttpClient, DirectSecp256k1HdWallet> {
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let client = HttpClient::new(endpoint)?;
let client = http_client(endpoint)?;
let prefix = &config.chain_details.bech32_account_prefix;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
@@ -568,6 +573,216 @@ where
C: TendermintRpcClient + Send + Sync,
S: Send + Sync,
{
async fn abci_info(&self) -> Result<abci::response::Info, TendermintRpcError> {
self.client.abci_info().await
}
async fn abci_query<V>(
&self,
path: Option<String>,
data: V,
height: Option<Height>,
prove: bool,
) -> Result<abci_query::AbciQuery, TendermintRpcError>
where
V: Into<Vec<u8>> + Send,
{
self.client.abci_query(path, data, height, prove).await
}
async fn block<H>(&self, height: H) -> Result<block::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.block(height).await
}
async fn block_by_hash(
&self,
hash: Hash,
) -> Result<block_by_hash::Response, TendermintRpcError> {
self.client.block_by_hash(hash).await
}
async fn latest_block(&self) -> Result<block::Response, TendermintRpcError> {
self.client.latest_block().await
}
async fn header<H>(&self, height: H) -> Result<header::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.header(height).await
}
async fn header_by_hash(
&self,
hash: Hash,
) -> Result<header_by_hash::Response, TendermintRpcError> {
self.client.header_by_hash(hash).await
}
async fn block_results<H>(
&self,
height: H,
) -> Result<block_results::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.block_results(height).await
}
async fn latest_block_results(&self) -> Result<block_results::Response, TendermintRpcError> {
self.client.latest_block_results().await
}
async fn block_search(
&self,
query: Query,
page: u32,
per_page: u8,
order: Order,
) -> Result<block_search::Response, TendermintRpcError> {
self.client.block_search(query, page, per_page, order).await
}
async fn blockchain<H>(
&self,
min: H,
max: H,
) -> Result<blockchain::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.blockchain(min, max).await
}
async fn broadcast_tx_async<T>(
&self,
tx: T,
) -> Result<broadcast::tx_async::Response, TendermintRpcError>
where
T: Into<Vec<u8>> + Send,
{
TendermintRpcClient::broadcast_tx_async(&self.client, tx).await
}
async fn broadcast_tx_sync<T>(
&self,
tx: T,
) -> Result<broadcast::tx_sync::Response, TendermintRpcError>
where
T: Into<Vec<u8>> + Send,
{
TendermintRpcClient::broadcast_tx_sync(&self.client, tx).await
}
async fn broadcast_tx_commit<T>(
&self,
tx: T,
) -> Result<broadcast::tx_commit::Response, TendermintRpcError>
where
T: Into<Vec<u8>> + Send,
{
TendermintRpcClient::broadcast_tx_commit(&self.client, tx).await
}
async fn commit<H>(&self, height: H) -> Result<commit::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.commit(height).await
}
async fn consensus_params<H>(
&self,
height: H,
) -> Result<consensus_params::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.consensus_params(height).await
}
async fn consensus_state(&self) -> Result<consensus_state::Response, TendermintRpcError> {
self.client.consensus_state().await
}
async fn validators<H>(
&self,
height: H,
paging: Paging,
) -> Result<validators::Response, TendermintRpcError>
where
H: Into<Height> + Send,
{
self.client.validators(height, paging).await
}
async fn latest_consensus_params(
&self,
) -> Result<consensus_params::Response, TendermintRpcError> {
self.client.latest_consensus_params().await
}
async fn latest_commit(&self) -> Result<commit::Response, TendermintRpcError> {
self.client.latest_commit().await
}
async fn health(&self) -> Result<(), TendermintRpcError> {
self.client.health().await
}
async fn genesis<AppState>(&self) -> Result<Genesis<AppState>, TendermintRpcError>
where
AppState: Debug + Serialize + DeserializeOwned + Send,
{
self.client.genesis().await
}
async fn net_info(&self) -> Result<net_info::Response, TendermintRpcError> {
self.client.net_info().await
}
async fn status(&self) -> Result<status::Response, TendermintRpcError> {
self.client.status().await
}
async fn broadcast_evidence(
&self,
e: Evidence,
) -> Result<evidence::Response, TendermintRpcError> {
self.client.broadcast_evidence(e).await
}
async fn tx(&self, hash: Hash, prove: bool) -> Result<TxResponse, TendermintRpcError> {
self.client.tx(hash, prove).await
}
async fn tx_search(
&self,
query: Query,
prove: bool,
page: u32,
per_page: u8,
order: Order,
) -> Result<tx_search::Response, TendermintRpcError> {
self.client
.tx_search(query, prove, page, per_page, order)
.await
}
#[cfg(any(
feature = "tendermint-rpc/http-client",
feature = "tendermint-rpc/websocket-client"
))]
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
where
T: Into<core::time::Duration> + Send,
{
self.client.wait_until_healthy(timeout).await
}
async fn perform<R>(&self, request: R) -> Result<R::Output, TendermintRpcError>
where
R: SimpleRequest,
@@ -11,8 +11,27 @@ use tendermint_rpc::{
Error, Order, Paging, SimpleRequest,
};
#[cfg(feature = "http-client")]
use crate::error::TendermintRpcError;
#[cfg(feature = "http-client")]
use crate::HttpRpcClient;
#[cfg(feature = "http-client")]
use tendermint_rpc::client::CompatMode;
#[cfg(feature = "http-client")]
use tendermint_rpc::HttpClientUrl;
pub mod reqwest;
#[cfg(feature = "http-client")]
pub fn http_client<U>(url: U) -> Result<HttpRpcClient, TendermintRpcError>
where
U: TryInto<HttpClientUrl, Error = Error>,
{
HttpRpcClient::builder(url.try_into()?)
.compat_mode(CompatMode::V0_34)
.build()
}
// we have to create a sealed trait since `TendermintClient` needs T: Send (due to how async trait is created)
// which we can't do in wasm
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -3,12 +3,32 @@
use crate::rpc::TendermintRpcClient;
use async_trait::async_trait;
use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
use reqwest::header::HeaderMap;
use reqwest::{header, RequestBuilder};
use tendermint_rpc::{Error, Response, SimpleRequest};
use tendermint_rpc::{
client::CompatMode,
dialect::{self, Dialect},
endpoint::{self, *},
query::Query,
Error, Order, Response, SimpleRequest,
};
use url::Url;
// copied macro from tendermint-rpc crate because that's exactly what we have to do here too
macro_rules! perform_with_compat {
($self:expr, $request:expr) => {{
let request = $request;
match $self.compat {
CompatMode::V0_37 => $self.perform_v0_37(request).await,
CompatMode::V0_34 => $self.perform_v0_34(request).await,
}
}};
}
pub struct ReqwestRpcClient {
compat: CompatMode,
inner: reqwest::Client,
url: Url,
}
@@ -16,12 +36,21 @@ pub struct ReqwestRpcClient {
impl ReqwestRpcClient {
pub fn new(url: Url) -> Self {
ReqwestRpcClient {
compat: CompatMode::V0_34,
inner: reqwest::Client::new(),
url,
}
}
fn build_request<R: SimpleRequest>(&self, request: R) -> RequestBuilder {
pub fn set_compat_mode(&mut self, compat: CompatMode) {
self.compat = compat;
}
fn build_request<R, S>(&self, request: R) -> RequestBuilder
where
R: SimpleRequest<S>,
S: Dialect,
{
let mut headers = HeaderMap::new();
headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
headers.insert(
@@ -39,6 +68,38 @@ impl ReqwestRpcClient {
.body(request.into_json().into_bytes())
.headers(headers)
}
async fn perform_request<R, S>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<S>,
S: Dialect,
{
let request = self.build_request(request);
// that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping
let response = request
.send()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
let bytes = response
.bytes()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
R::Response::from_string(bytes).map(Into::into)
}
async fn perform_v0_34<R>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<dialect::v0_34::Dialect>,
{
self.perform_request(request).await
}
async fn perform_v0_37<R>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<dialect::v0_37::Dialect>,
{
self.perform_request(request).await
}
}
trait TendermintRpcErrorMap {
@@ -58,17 +119,81 @@ impl TendermintRpcClient for ReqwestRpcClient {
where
R: SimpleRequest,
{
let request = self.build_request(request);
// that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping
let response = request
.send()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
let bytes = response
.bytes()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
R::Response::from_string(bytes).map(Into::into)
self.perform_request(request).await
}
async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
where
H: Into<Height> + Send,
{
perform_with_compat!(self, block_results::Request::new(height.into()))
}
async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
perform_with_compat!(self, block_results::Request::default())
}
async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
where
H: Into<Height> + Send,
{
let height = height.into();
match self.compat {
CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await,
CompatMode::V0_34 => {
// Back-fill with a request to /block endpoint and
// taking just the header from the response.
let resp = self.perform_v0_34(block::Request::new(height)).await?;
Ok(resp.into())
}
}
}
async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
match self.compat {
CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await,
CompatMode::V0_34 => {
// Back-fill with a request to /block_by_hash endpoint and
// taking just the header from the response.
let resp = self
.perform_v0_34(block_by_hash::Request::new(hash))
.await?;
Ok(resp.into())
}
}
}
/// `/broadcast_evidence`: broadcast an evidence.
async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
match self.compat {
CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await,
CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await,
}
}
async fn tx(&self, hash: Hash, prove: bool) -> Result<tx::Response, Error> {
perform_with_compat!(self, tx::Request::new(hash, prove))
}
async fn tx_search(
&self,
query: Query,
prove: bool,
page: u32,
per_page: u8,
order: Order,
) -> Result<tx_search::Response, Error> {
perform_with_compat!(
self,
tx_search::Request::new(query, prove, page, per_page, order)
)
}
async fn broadcast_tx_commit<T>(&self, tx: T) -> Result<broadcast::tx_commit::Response, Error>
where
T: Into<Vec<u8>> + Send,
{
perform_with_compat!(self, broadcast::tx_commit::Request::new(tx))
}
}
@@ -199,16 +199,14 @@ mod tests {
#[test]
fn generating_account_addresses() {
// test vectors produced from our js wallet
let mnemonics = vec![
"crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
let mnemonics = ["crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"];
let prefix = NymNetworkDetails::new_mainnet()
.chain_details
.bech32_account_prefix;
let addrs = vec![
let addrs = [
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
+11 -2
View File
@@ -8,7 +8,14 @@ use serde::{Deserialize, Serialize};
use error::CoconutInterfaceError;
pub use nym_coconut::*;
// We list these explicity instead of glob export due to shadowing warnings with the pub tests
// module.
pub use nym_coconut::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar,
prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest,
BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute,
PublicAttribute, Signature, SignatureShare, Theta, VerificationKey,
};
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)]
pub struct Credential {
@@ -57,7 +64,7 @@ impl Credential {
pub fn verify(&self, verification_key: &VerificationKey) -> bool {
let params = Parameters::new(self.n_params).unwrap();
let public_attributes = vec![
let public_attributes = [
self.voucher_value.to_string().as_bytes(),
self.voucher_info.as_bytes(),
]
@@ -138,6 +145,8 @@ impl Base58 for Credential {}
#[cfg(test)]
mod tests {
use nym_coconut::{prove_bandwidth_credential, Signature};
use super::*;
#[test]
+7
View File
@@ -5,6 +5,7 @@ authors.workspace = true
edition = "2021"
[dependencies]
anyhow = { workspace = true }
base64 = "0.13.0"
bip39 = { workspace = true }
bs58 = "0.4"
@@ -33,6 +34,7 @@ nym-bin-common = { path = "../../common/bin-common", features = ["output_format"
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
nym-network-defaults = { path = "../network-defaults" }
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" }
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
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" }
@@ -41,6 +43,11 @@ nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-co
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-client-core = { path = "../../common/client-core" }
nym-config = { path = "../../common/config" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credential-utils = { path = "../../common/credential-utils" }
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
nym-types = { path = "../../common/types" }
@@ -0,0 +1,51 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use crate::utils::CommonConfigsWrapper;
use anyhow::bail;
use clap::Parser;
use nym_credential_storage::initialise_persistent_storage;
use nym_credential_utils::utils;
use nym_validator_client::nyxd::Coin;
use std::path::PathBuf;
#[derive(Debug, Parser)]
pub struct Args {
/// Config file of the client that is supposed to use the credential.
#[clap(long)]
pub(crate) client_config: PathBuf,
/// The amount of utokens the credential will hold.
#[clap(long, default_value = "0")]
pub(crate) amount: u64,
/// Path to a directory used to store recovery files for unconsumed deposits
#[clap(long)]
pub(crate) recovery_dir: PathBuf,
}
pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
if let Ok(id) = loaded.try_get_id() {
println!("loaded config file for client '{id}'");
}
let Ok(credentials_store) = loaded.try_get_credentials_store() else {
bail!("the loaded config does not have a credentials store information")
};
println!(
"using credentials store at '{}'",
credentials_store.display()
);
let denom = &client.current_chain_details().mix_denom.base;
let coin = Coin::new(args.amount as u128, denom);
let persistent_storage = initialise_persistent_storage(credentials_store).await;
utils::issue_credential(&client, coin, &persistent_storage, args.recovery_dir).await?;
Ok(())
}
+17 -1
View File
@@ -1,4 +1,20 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO: add coconut commands here
use clap::{Args, Subcommand};
pub mod issue_credentials;
pub mod recover_credentials;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct Coconut {
#[clap(subcommand)]
pub command: CoconutCommands,
}
#[derive(Debug, Subcommand)]
pub enum CoconutCommands {
IssueCredentials(issue_credentials::Args),
RecoverCredentials(recover_credentials::Args),
}
@@ -0,0 +1,48 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClient;
use crate::utils::CommonConfigsWrapper;
use anyhow::bail;
use clap::Parser;
use nym_credential_storage::initialise_persistent_storage;
use nym_credential_utils::{recovery_storage, utils};
use std::path::PathBuf;
#[derive(Debug, Parser)]
pub struct Args {
/// Config file of the client that is supposed to use the credential.
#[clap(long)]
pub(crate) client_config: PathBuf,
/// Path to a directory used to store recovery files for unconsumed deposits
#[clap(long)]
pub(crate) recovery_dir: PathBuf,
}
pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> {
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
if let Ok(id) = loaded.try_get_id() {
println!("loaded config file for client '{id}'");
}
let Ok(credentials_store) = loaded.try_get_credentials_store() else {
bail!("the loaded config does not have a credentials store information")
};
println!(
"using credentials store at '{}'",
credentials_store.display()
);
let persistent_storage = initialise_persistent_storage(credentials_store).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(args.recovery_dir)?;
let recovered =
utils::recover_credentials(&client, &recovery_storage, &persistent_storage).await?;
// TODO: denom?
println!("recovered {recovered} worth of credentials");
Ok(())
}
+152 -1
View File
@@ -1,13 +1,17 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use anyhow::{anyhow, bail};
use cosmrs::AccountId;
use cosmwasm_std::{Addr, Coin as CosmWasmCoin, Decimal};
use log::error;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_validator_client::nyxd::Coin;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::fs;
use std::path::{Path, PathBuf};
// TODO: perhaps it should be moved to some global common crate?
pub fn account_id_to_cw_addr(account_id: &AccountId) -> Addr {
@@ -75,3 +79,150 @@ impl<T> DataWrapper<T> {
DataWrapper { data }
}
}
fn find_toml_value<'a>(root: &'a toml::Value, key: &str) -> Option<&'a toml::Value> {
if let toml::Value::Table(table) = root {
for (k, v) in table {
if k == key {
return Some(v);
}
if v.is_table() {
if let Some(res) = find_toml_value(v, key) {
return Some(res);
}
}
}
}
None
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub(crate) enum CommonConfigsWrapper {
// native, socks5, NR, etc. clients
NymClients(Box<ClientConfigCommonWrapper>),
// nym-api
NymApi(NymApiConfigLight),
// anything else that might get get introduced
Unknown(UnknownConfigWrapper),
}
impl CommonConfigsWrapper {
pub(crate) fn try_load<P: AsRef<Path>>(path: P) -> anyhow::Result<CommonConfigsWrapper> {
let content = fs::read_to_string(path)?;
Ok(toml::from_str(&content)?)
}
pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
match self {
CommonConfigsWrapper::NymClients(cfg) => cfg.try_get_id(),
CommonConfigsWrapper::NymApi(cfg) => Ok(&cfg.base.id),
CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_id(),
}
}
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
match self {
CommonConfigsWrapper::NymClients(cfg) => {
Ok(cfg.storage_paths.inner.credentials_database.clone())
}
CommonConfigsWrapper::NymApi(cfg) => Ok(cfg
.network_monitor
.storage_paths
.credentials_database_path
.clone()),
CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_credentials_store(),
}
}
}
// ideally we would have just imported the full nym-api config structure, but that'd have been an overkill,
// because we'd have to import the whole crate
#[derive(Deserialize, Debug)]
pub(crate) struct NymApiConfigLight {
base: NymApiConfigBaseLight,
network_monitor: NymApiConfigNetworkMonitorLight,
}
#[derive(Deserialize, Debug)]
struct NymApiConfigBaseLight {
id: String,
}
#[derive(Deserialize, Debug)]
struct NymApiConfigNetworkMonitorLight {
storage_paths: NetworkMonitorPaths,
}
#[derive(Deserialize, Debug)]
struct NetworkMonitorPaths {
credentials_database_path: PathBuf,
}
// a hacky way of reading common data from client configs (native, socks5, etc.)
// it works because all clients follow the same structure for storage paths
// (or so I thought)
#[derive(Deserialize, Debug)]
pub(crate) struct ClientConfigCommonWrapper {
storage_paths: StoragePathsWrapper,
// ... but they have different structure for `nym_client_core::config::Client`
// native client has it on the top layer, whilsts socks5 has it under 'core' table
#[serde(flatten)]
other: toml::Value,
}
// wrapper to allow for any additional entries besides the common paths, like allow list for NR
#[derive(Deserialize, Debug)]
struct StoragePathsWrapper {
#[serde(flatten)]
inner: CommonClientPaths,
}
impl ClientConfigCommonWrapper {
pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
let id_val = find_toml_value(&self.other, "id")
.ok_or_else(|| anyhow!("no id field present in the config"))?;
if let toml::Value::String(id) = id_val {
Ok(id)
} else {
bail!("no id field present in the config")
}
}
}
#[derive(Deserialize, Debug)]
pub(crate) struct UnknownConfigWrapper {
#[serde(flatten)]
inner: toml::Value,
}
impl UnknownConfigWrapper {
fn find_value(&self, key: &str) -> Option<&toml::Value> {
find_toml_value(&self.inner, key)
}
pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
let id_val = self
.find_value("id")
.ok_or_else(|| anyhow!("no id field present in the config"))?;
if let toml::Value::String(id) = id_val {
Ok(id)
} else {
bail!("no id field present in the config")
}
}
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
let id_val = self
.find_value("credentials_database_path")
.ok_or_else(|| anyhow!("no 'credentials_database_path' field present in the config"))?;
if let toml::Value::String(credentials_store) = id_val {
Ok(credentials_store.parse()?)
} else {
bail!("no 'credentials_database_path' field present in the config")
}
}
}
@@ -26,7 +26,7 @@ humantime-serde = "1.1.1"
# TO CHECK WHETHER STILL NEEDED:
log = { workspace = true }
time = { version = "0.3.6", features = ["parsing", "formatting"] }
ts-rs = { version = "6.1.2", optional = true }
ts-rs = { workspace = true, optional = true }
[dev-dependencies]
rand_chacha = "0.3"
@@ -15,7 +15,7 @@ mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-co
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
ts-rs = {version = "6.1.2", optional = true}
ts-rs = { workspace = true, optional = true}
[features]
schema = ["cw2"]
@@ -1,25 +1,18 @@
[package]
name = "nym-credential-client"
name = "nym-credential-utils"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.0", features = ["cargo", "derive"] }
log = "0.4"
serde = { workspace = true, features = ["derive"] }
thiserror = "1.0"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
log = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-client-core = { path = "../../common/client-core" }
nym-config = { path = "../../common/config" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-bin-common = { path = "../../common/bin-common"}
nym-network-defaults = { path = "../../common/network-defaults" }
nym-pemstore = { path = "../../common/pemstore" }
nym-validator-client = { path = "../../common/client-libs/validator-client" }
nym-config = { path = "../../common/config" }
nym-client-core = { path = "../../common/client-core" }
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credential_storage::error::StorageError;
use nym_credentials::error::Error as CredentialError;
use nym_validator_client::nyxd::error::NyxdError;
use std::num::ParseIntError;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
IOError(#[from] std::io::Error),
#[error(transparent)]
BandwidthControllerError(#[from] nym_bandwidth_controller::error::BandwidthControllerError),
#[error(transparent)]
Nyxd(#[from] NyxdError),
#[error(transparent)]
Credential(#[from] CredentialError),
#[error("Could not use shared storage: {0}")]
SharedStorageError(#[from] StorageError),
#[error("failed to parse credential value: {0}")]
MalformedCredentialValue(#[from] ParseIntError),
}
+5
View File
@@ -0,0 +1,5 @@
pub mod errors;
pub mod recovery_storage;
pub mod utils;
pub use errors::{Error, Result};
@@ -0,0 +1,64 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::errors::Result;
use log::error;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use std::fs::{create_dir_all, read_dir, File};
use std::io::{Read, Write};
use std::path::PathBuf;
pub struct RecoveryStorage {
recovery_dir: PathBuf,
}
impl RecoveryStorage {
pub fn new(recovery_dir: PathBuf) -> Result<Self> {
create_dir_all(&recovery_dir)?;
Ok(Self { recovery_dir })
}
pub fn unconsumed_vouchers(&self) -> Result<Vec<BandwidthVoucher>> {
let entries = read_dir(&self.recovery_dir)?;
let mut paths = vec![];
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
paths.push(path)
}
}
let mut vouchers = vec![];
for path in paths {
if let Ok(mut file) = File::open(&path) {
let mut buff = Vec::new();
if file.read_to_end(&mut buff).is_ok() {
match BandwidthVoucher::try_from_bytes(&buff) {
Ok(voucher) => vouchers.push(voucher),
Err(err) => {
error!("failed to parse the voucher at {}: {err}", path.display())
}
}
}
}
}
Ok(vouchers)
}
pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> Result<PathBuf> {
let file_name = voucher.tx_hash().to_string();
let file_path = self.recovery_dir.join(file_name);
let mut file = File::create(&file_path)?;
let buff = voucher.to_bytes();
file.write_all(&buff)?;
Ok(file_path)
}
pub fn remove_voucher(&self, file_name: String) -> Result<()> {
let file_path = self.recovery_dir.join(file_name);
Ok(std::fs::remove_file(file_path)?)
}
}
+139
View File
@@ -0,0 +1,139 @@
use crate::errors::{Error, Result};
use crate::recovery_storage::RecoveryStorage;
use log::*;
use nym_bandwidth_controller::acquire::state::State;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_config::DEFAULT_DATA_DIR;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient};
use nym_validator_client::nyxd::Coin;
use std::path::PathBuf;
use std::process::exit;
use std::time::{Duration, SystemTime};
const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute
pub async fn issue_credential<C>(
client: &C,
amount: Coin,
persistent_storage: &PersistentStorage,
recovery_storage_path: PathBuf,
) -> Result<()>
where
C: DkgQueryClient + CoconutBandwidthSigningClient + Send + Sync,
{
let recovery_storage = setup_recovery_storage(recovery_storage_path).await;
block_until_coconut_is_available(client).await?;
info!("Starting to deposit funds, don't kill the process");
if let Ok(recovered_amount) =
recover_credentials(client, &recovery_storage, persistent_storage).await
{
if recovered_amount != 0 {
info!(
"Recovered credentials in the amount of {}",
recovered_amount
);
return Ok(());
}
};
let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?;
if nym_bandwidth_controller::acquire::get_credential(&state, client, persistent_storage)
.await
.is_err()
{
warn!("Failed to obtain credential. Dumping recovery data.",);
match recovery_storage.insert_voucher(&state.voucher) {
Ok(file_path) => {
warn!("Dumped recovery data to {}. Try using recovery mode to convert it to a credential", file_path.to_str().unwrap());
}
Err(e) => {
error!("Could not dump recovery data to file system due to {:?}, the deposit will be lost!", e)
}
}
return Err(Error::Credential(
nym_credentials::error::Error::BandwidthCredentialError,
));
}
info!("Succeeded adding a credential with amount {amount}");
Ok(())
}
pub async fn setup_recovery_storage(recovery_dir: PathBuf) -> RecoveryStorage {
RecoveryStorage::new(recovery_dir).expect("")
}
pub async fn setup_persistent_storage(client_home_directory: PathBuf) -> PersistentStorage {
let data_dir = client_home_directory.join(DEFAULT_DATA_DIR);
let paths = CommonClientPaths::new_default(data_dir);
let db_path = paths.credentials_database;
nym_credential_storage::initialise_persistent_storage(db_path).await
}
pub async fn block_until_coconut_is_available<C>(client: &C) -> Result<()>
where
C: DkgQueryClient + Send + Sync,
{
loop {
let epoch = client.get_current_epoch().await?;
let current_timestamp_secs = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("the system clock is set to 01/01/1970 (or earlier)")
.as_secs();
if epoch.state.is_final() {
if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() {
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
exit(0);
}
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;
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
tokio::time::sleep(Duration::from_secs(secs_until_final)).await;
}
}
Ok(())
}
pub async fn recover_credentials<C>(
client: &C,
recovery_storage: &RecoveryStorage,
shared_storage: &PersistentStorage,
) -> Result<u128>
where
C: DkgQueryClient + Send + Sync,
{
let mut recovered_amount: u128 = 0;
for voucher in recovery_storage.unconsumed_vouchers()? {
let voucher_value = voucher.get_voucher_value();
recovered_amount += voucher_value.parse::<u128>()?;
let state = State::new(voucher);
let voucher = state.voucher.tx_hash();
if let Err(e) =
nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await
{
error!("Could not recover deposit {voucher} due to {e}, try again later",)
} else {
info!("Converted deposit {voucher} to a credential, removing recovery data for it",);
if let Err(e) = recovery_storage.remove_voucher(voucher.to_string()) {
warn!("Could not remove recovery data: {e}");
}
}
}
Ok(recovered_amount)
}
+2 -1
View File
@@ -8,7 +8,8 @@ edition = "2021"
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { workspace = true }
thiserror = "1.0"
thiserror = { workspace = true }
log = { workspace = true }
# I guess temporarily until we get serde support in coconut up and running
nym-coconut-interface = { path = "../coconut-interface" }
+21 -8
View File
@@ -1,6 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm};
use crate::error::Error;
use log::{debug, warn};
use nym_api_requests::coconut::BlindSignRequestBody;
use nym_coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute,
@@ -11,10 +15,6 @@ use nym_crypto::shared_key::recompute_shared_key;
use nym_crypto::symmetric::stream_cipher;
use nym_validator_client::client::CoconutApiClient;
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm};
use crate::error::Error;
pub async fn obtain_aggregate_verification_key(
api_clients: &[CoconutApiClient],
) -> Result<VerificationKey, Error> {
@@ -107,7 +107,12 @@ pub async fn obtain_aggregate_signature(
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
for coconut_api_client in coconut_api_clients.iter() {
if let Ok(signature) = obtain_partial_credential(
debug!(
"attempting to obtain partial credential from {}",
coconut_api_client.api_client.api_url()
);
match obtain_partial_credential(
params,
attributes,
&coconut_api_client.api_client,
@@ -115,9 +120,17 @@ pub async fn obtain_aggregate_signature(
)
.await
{
let share = SignatureShare::new(signature, coconut_api_client.node_id);
shares.push(share)
}
Ok(signature) => {
let share = SignatureShare::new(signature, coconut_api_client.node_id);
shares.push(share)
}
Err(err) => {
warn!(
"failed to obtain partial credential from {}: {err}",
coconut_api_client.api_client.api_url()
);
}
};
}
if shares.len() < threshold as usize {
return Err(Error::NotEnoughShares);
+12 -2
View File
@@ -4,11 +4,11 @@
use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::{
env::{var, VarError},
ffi::OsStr,
ops::Not,
path::PathBuf,
};
use url::Url;
@@ -43,6 +43,7 @@ pub struct NymNetworkDetails {
pub chain_details: ChainDetails,
pub endpoints: Vec<ValidatorDetails>,
pub contracts: NymContracts,
pub explorer_api: Option<String>,
}
// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
@@ -71,6 +72,7 @@ impl NymNetworkDetails {
},
endpoints: Default::default(),
contracts: Default::default(),
explorer_api: Default::default(),
}
}
@@ -136,6 +138,7 @@ impl NymNetworkDetails {
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
))
.with_name_service_contract(get_optional_env(var_names::NAME_SERVICE_CONTRACT_ADDRESS))
.with_explorer_api(get_optional_env(var_names::EXPLORER_API))
}
pub fn new_mainnet() -> Self {
@@ -167,6 +170,7 @@ impl NymNetworkDetails {
service_provider_directory_contract_address: None,
name_service_contract_address: None,
},
explorer_api: parse_optional_str(mainnet::EXPLORER_API),
}
}
@@ -278,6 +282,12 @@ impl NymNetworkDetails {
self.contracts.name_service_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_explorer_api<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
self.explorer_api = endpoint.map(Into::into);
self
}
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -378,7 +388,7 @@ fn fix_deprecated_environmental_variables() {
}
}
pub fn setup_env(config_env_file: Option<&PathBuf>) {
pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
match std::env::var(var_names::CONFIGURED) {
// if the configuration is not already set in the env vars
Err(std::env::VarError::NotPresent) => {
+4
View File
@@ -26,6 +26,8 @@ pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hh
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))]
}
@@ -99,6 +101,7 @@ pub fn export_to_env() {
);
set_var_to_default(var_names::NYXD, NYXD_URL);
set_var_to_default(var_names::NYM_API, NYM_API);
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
}
pub fn export_to_env_if_not_set() {
@@ -145,4 +148,5 @@ pub fn export_to_env_if_not_set() {
);
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API);
}
+1
View File
@@ -26,6 +26,7 @@ pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
pub const EXPLORER_API: &str = "EXPLORER_API";
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
+2 -2
View File
@@ -354,7 +354,7 @@ impl ProofKappaZeta {
let witness_blinder = params.random_scalar();
let witness_serial_number = params.random_scalar();
let witness_binding_number = params.random_scalar();
let witness_attributes = vec![witness_serial_number, witness_binding_number];
let witness_attributes = [witness_serial_number, witness_binding_number];
let beta_bytes = verification_key
.beta_g2
@@ -417,7 +417,7 @@ impl ProofKappaZeta {
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
let response_attributes = vec![self.response_serial_number, self.response_binding_number];
let response_attributes = [self.response_serial_number, self.response_binding_number];
// re-compute witnesses commitments
// Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i])
let commitment_kappa = kappa * self.challenge
-18
View File
@@ -1,18 +0,0 @@
[package]
name = "nym-noise"
version = "0.1.0"
authors = ["Simon Wicky <simon@nymtech.net>"]
edition = "2021"
[dependencies]
snow = "0.9.2"
futures = "0.3"
tokio = { version = "1.24.1", features = ["net","io-util","time"] }
pin-project = "1"
log = "0.4.19"
sha2 = "0.10.7"
bytes = "1.0"
thiserror = "1.0.44"
# internal
nym-topology = { path = "../topology"}
-405
View File
@@ -1,405 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use log::*;
use nym_topology::NymTopology;
use pin_project::pin_project;
use sha2::{Digest, Sha256};
use snow::error::Prerequisite;
use snow::Builder;
use snow::Error;
use snow::HandshakeState;
use snow::TransportState;
use std::cmp::min;
use std::collections::VecDeque;
use std::io;
use std::io::ErrorKind;
use std::num::TryFromIntError;
use std::pin::Pin;
use std::task::Poll;
use std::time::Duration;
use thiserror::Error;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::io::ReadBuf;
use tokio::{
io::{AsyncRead, AsyncWrite},
net::TcpStream,
};
const NOISE_HS_PATTERN: &str = "Noise_XKpsk3_25519_AESGCM_SHA256";
const MAXMSGLEN: usize = 65535;
const TAGLEN: usize = 16;
const HEADER_SIZE: usize = 2;
#[derive(Error, Debug)]
pub enum NoiseError {
#[error("encountered a Noise decryption error")]
DecryptionError,
#[error("encountered a Noise Protocol error - {0}")]
ProtocolError(Error),
#[error("encountered an IO error - {0}")]
IoError(#[from] io::Error),
#[error("Incorrect state")]
IncorrectStateError,
#[error("Handshake timeout")]
HandshakeTimeoutError(#[from] tokio::time::error::Elapsed),
#[error(transparent)]
IntConversionError(#[from] TryFromIntError),
}
impl From<Error> for NoiseError {
fn from(err: Error) -> Self {
match err {
Error::Decrypt => NoiseError::DecryptionError,
err => NoiseError::ProtocolError(err),
}
}
}
/// Wrapper around a TcpStream
#[pin_project]
pub struct NoiseStream {
#[pin]
inner_stream: TcpStream,
handshake: Option<HandshakeState>,
noise: Option<TransportState>,
enc_storage: VecDeque<u8>,
dec_storage: VecDeque<u8>,
}
impl NoiseStream {
fn new(inner_stream: TcpStream, handshake: HandshakeState) -> NoiseStream {
NoiseStream {
inner_stream,
handshake: Some(handshake),
noise: None,
enc_storage: VecDeque::with_capacity(MAXMSGLEN + TAGLEN + HEADER_SIZE), //At least one message
dec_storage: VecDeque::with_capacity(MAXMSGLEN),
}
}
async fn send_handshake_msg(&mut self) -> Result<(), NoiseError> {
let mut buf = vec![0u8; MAXMSGLEN];
let len = match &mut self.handshake {
Some(handshake_state) => handshake_state.write_message(&[], &mut buf)?,
None => return Err(NoiseError::IncorrectStateError),
};
self.inner_stream.write_u16(len.try_into()?).await?; //len is always < 2^16, so it shouldn't fail
self.inner_stream.write_all(&buf[..len]).await?;
Ok(())
}
async fn recv_handshake_msg(&mut self) -> Result<(), NoiseError> {
let msg_len = self.inner_stream.read_u16().await?;
let mut msg = vec![0u8; msg_len.into()];
self.inner_stream.read_exact(&mut msg[..]).await?;
let mut buf = vec![0u8; MAXMSGLEN];
match &mut self.handshake {
Some(handshake_state) => handshake_state.read_message(&msg, &mut buf)?,
None => return Err(NoiseError::IncorrectStateError),
};
Ok(())
}
fn into_transport_mode(mut self) -> Result<Self, NoiseError> {
let Some(handshake) = self.handshake else {return Err(NoiseError::IncorrectStateError)};
self.handshake = None;
self.noise = Some(handshake.into_transport_mode()?);
Ok(self)
}
}
impl AsyncRead for NoiseStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
let projected_self = self.project();
let enc_storage = projected_self.enc_storage;
let ready_to_read = projected_self.inner_stream.poll_read_ready(cx);
match ready_to_read {
Poll::Pending => {
//no new data, waking is already scheduled.
//Nothing new to decrypt, only check if we can return something from dec_storage, happens after
}
Poll::Ready(Ok(())) => {
//Read what we can into enc_storage, decrypt what we can into dec_storage
let mut tcp_buf = vec![0u8; MAXMSGLEN + HEADER_SIZE + TAGLEN];
if let Ok(tcp_len) = projected_self.inner_stream.try_read(&mut tcp_buf) {
if tcp_len == 0 && projected_self.dec_storage.is_empty() {
//EOF
return Poll::Ready(Ok(()));
}
enc_storage.extend(&tcp_buf[..tcp_len]);
//we can at least read the length
while enc_storage.len() >= HEADER_SIZE {
let msg_len = ((enc_storage[0] as usize) << 8) + (enc_storage[1] as usize);
//no more messages to read
if enc_storage.len() < HEADER_SIZE + msg_len {
break;
}
//we have a full message to decrypt
//remove size
enc_storage.pop_front();
enc_storage.pop_front();
let noise_msg = enc_storage.drain(..msg_len).collect::<Vec<u8>>();
let mut dec_msg = vec![0u8; MAXMSGLEN];
let Ok(len) = (match projected_self.noise {
Some(transport_state) => transport_state.read_message(&noise_msg, &mut dec_msg),
None => return Poll::Ready(Err(ErrorKind::Other.into())),
}) else {
return Poll::Ready(Err(ErrorKind::InvalidInput.into()));
};
projected_self.dec_storage.extend(&dec_msg[..len]);
}
}
}
//an error occured, let's return it right away
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
}
//check if we can return something
let read_len = min(buf.remaining(), projected_self.dec_storage.len());
if read_len > 0 {
buf.put_slice(
&projected_self
.dec_storage
.drain(..read_len)
.collect::<Vec<u8>>(),
);
return Poll::Ready(Ok(()));
}
//can't return anything, schedule the wakeup and return pending
if let Poll::Ready(Ok(())) = projected_self.inner_stream.poll_read_ready(cx) {
//we got data in the meantime, we can wake up immediately
cx.waker().wake_by_ref();
}
Poll::Pending
}
}
impl AsyncWrite for NoiseStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
let projected_self = self.project();
let mut noise_buf = vec![0u8; MAXMSGLEN];
let Ok(len) = (match projected_self.noise {
Some(transport_state) => transport_state.write_message(buf, &mut noise_buf),
None => return Poll::Ready(Err(ErrorKind::Other.into())),
}) else {
return Poll::Ready(Err(ErrorKind::InvalidInput.into()));
};
let to_send = [&[(len >> 8) as u8, (len & 0xff) as u8], &noise_buf[..len]].concat();
match projected_self.inner_stream.poll_write(cx, &to_send) {
Poll::Pending => Poll::Pending,
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Ready(Ok(n)) => {
//didn't send a thing, no problem for the underlying stream
if n == 0 {
return Poll::Ready(Ok(0));
}
//we sent the whole thing, no problem for the underlying stream
//We must guarantee that the return number is <= buf.len()
if n == to_send.len() {
return Poll::Ready(Ok(n - HEADER_SIZE - TAGLEN));
}
//We didn't write the whole message, the stream will be corrupted
error!(
"Partial write on Noise Stream, it will be corrupted - {}",
n
);
Poll::Ready(Err(ErrorKind::WriteZero.into()))
}
}
}
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
self.project().inner_stream.poll_flush(cx)
}
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
self.project().inner_stream.poll_shutdown(cx)
}
}
pub async fn upgrade_noise_initiator(
conn: TcpStream,
local_public_key: Option<&[u8]>,
local_private_key: &[u8],
remote_pub_key: &[u8],
epoch: u32,
) -> Result<NoiseStream, NoiseError> {
trace!("Perform Noise Handshake, initiator side");
//In case the local key cannot be known by the remote party, e.g. in a client-gateway connection
let secret = [
local_public_key.unwrap_or(&[]),
remote_pub_key,
&epoch.to_be_bytes(),
]
.concat();
let secret_hash = Sha256::digest(secret);
let builder = Builder::new(NOISE_HS_PATTERN.parse().unwrap()); //This cannot fail, hardcoded pattern must be correct
let handshake = builder
.local_private_key(local_private_key)
.remote_public_key(remote_pub_key)
.psk(3, &secret_hash)
.build_initiator()?;
let mut noise_stream = NoiseStream::new(conn, handshake);
tokio::time::timeout(Duration::from_secs(5), async {
// -> e, es
noise_stream.send_handshake_msg().await?;
// <- e, ee
noise_stream.recv_handshake_msg().await?;
// -> s, se, psk
noise_stream.send_handshake_msg().await
})
.await??;
noise_stream.into_transport_mode()
}
pub async fn upgrade_noise_initiator_with_topology(
conn: TcpStream,
topology: &NymTopology,
epoch: u32,
local_public_key: &[u8],
local_private_key: &[u8],
) -> Result<NoiseStream, NoiseError> {
//Get init material
let responder_addr = match conn.peer_addr() {
Ok(addr) => addr,
Err(err) => {
error!("Unable to extract peer address from connection - {err}");
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
}
};
let remote_pub_key = match topology.find_node_key_by_mix_host(responder_addr) {
Some(pub_key) => pub_key.to_bytes(),
None => {
error!(
"Cannot find public key for node with address {:?}",
responder_addr
);
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
}
};
upgrade_noise_initiator(
conn,
Some(local_public_key),
local_private_key,
&remote_pub_key,
epoch,
)
.await
}
pub async fn upgrade_noise_responder(
conn: TcpStream,
local_public_key: &[u8],
local_private_key: &[u8],
remote_pub_key: Option<&[u8]>,
epoch: u32,
) -> Result<NoiseStream, NoiseError> {
trace!("Perform Noise Handshake, responder side");
//If the remote_key cannot be kwnown, e.g. in a client-gateway connection
let secret = [
remote_pub_key.unwrap_or(&[]),
local_public_key,
&epoch.to_be_bytes(),
]
.concat();
let secret_hash = Sha256::digest(secret);
let builder = Builder::new(NOISE_HS_PATTERN.parse().unwrap()); //This cannot fail, hardcoded pattern must be correct
let handshake = builder
.local_private_key(local_private_key)
.psk(3, &secret_hash)
.build_responder()?;
let mut noise_stream = NoiseStream::new(conn, handshake);
tokio::time::timeout(Duration::from_secs(5), async {
//Actual Handshake
// <- e, es
noise_stream.recv_handshake_msg().await?;
// -> e, ee
noise_stream.send_handshake_msg().await?;
// <- s, se, psk
noise_stream.recv_handshake_msg().await
})
.await??;
noise_stream.into_transport_mode()
}
pub async fn upgrade_noise_responder_with_topology(
conn: TcpStream,
topology: &NymTopology,
epoch: u32,
local_public_key: &[u8],
local_private_key: &[u8],
) -> Result<NoiseStream, NoiseError> {
//Get init material
let initiator_addr = match conn.peer_addr() {
Ok(addr) => addr,
Err(err) => {
error!("Unable to extract peer address from connection - {err}");
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
}
};
let remote_pub_key = match topology.find_node_key_by_mix_host(initiator_addr) {
Some(pub_key) => pub_key.to_bytes(),
None => {
error!(
"Cannot find public key for node with address {:?}",
initiator_addr
);
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
}
};
upgrade_noise_responder(
conn,
local_public_key,
local_private_key,
Some(&remote_pub_key),
epoch,
)
.await
}
@@ -17,7 +17,7 @@ pub fn prepare_identifier<R: RngCore + CryptoRng>(
let id_ciphertext = encrypt::<AckEncryptionAlgorithm>(key.inner(), &iv, &serialized_id);
// IV || ID_CIPHERTEXT
iv.into_iter().chain(id_ciphertext.into_iter()).collect()
iv.into_iter().chain(id_ciphertext).collect()
}
pub fn recover_identifier(
@@ -122,7 +122,7 @@ impl SurbAck {
.first_hop_address
.as_zero_padded_bytes(MAX_NODE_ADDRESS_UNPADDED_LEN)
.into_iter()
.chain(self.surb_ack_packet.to_bytes()?.into_iter())
.chain(self.surb_ack_packet.to_bytes()?)
.collect();
Ok((self.expected_total_delay, surb_bytes))
}
@@ -130,7 +130,7 @@ impl ReplySurb {
self.encryption_key
.to_bytes()
.into_iter()
.chain(self.surb.to_bytes().into_iter())
.chain(self.surb.to_bytes())
.collect()
}
@@ -275,7 +275,7 @@ impl RepliableMessageContent {
.to_be_bytes()
.into_iter()
.chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes()))
.chain(message.into_iter())
.chain(message)
.collect()
}
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
@@ -465,7 +465,7 @@ impl ReplyMessageContent {
ReplyMessageContent::SurbRequest { recipient, amount } => recipient
.to_bytes()
.into_iter()
.chain(amount.to_be_bytes().into_iter())
.chain(amount.to_be_bytes())
.collect(),
}
}
+1 -1
View File
@@ -204,7 +204,7 @@ impl Fragment {
self.header
.to_bytes()
.into_iter()
.chain(self.payload.into_iter())
.chain(self.payload)
.collect()
}
+1 -1
View File
@@ -115,7 +115,7 @@ where
let packet_payload: Vec<_> = ack_bytes
.into_iter()
.chain(ephemeral_keypair.public_key().to_bytes().iter().cloned())
.chain(cover_content.into_iter())
.chain(cover_content)
.collect();
let route =
+2 -2
View File
@@ -106,8 +106,8 @@ impl MixPacket {
pub fn into_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
Ok(std::iter::once(self.packet_type as u8)
.chain(self.next_hop.as_bytes().into_iter())
.chain(self.packet.to_bytes()?.into_iter())
.chain(self.next_hop.as_bytes())
.chain(self.packet.to_bytes()?)
.collect())
}
}
+3 -6
View File
@@ -50,8 +50,8 @@ impl NymPayloadBuilder {
Ok(NymPayload(
surb_ack_bytes
.into_iter()
.chain(variant_data.into_iter())
.chain(fragment_data.into_iter())
.chain(variant_data)
.chain(fragment_data)
.collect(),
))
}
@@ -61,10 +61,7 @@ impl NymPayloadBuilder {
packet_encryption_key: &SurbEncryptionKey,
) -> Result<NymPayload, SurbAckRecoveryError> {
let key_digest = packet_encryption_key.compute_digest();
self.build::<ReplySurbEncryptionAlgorithm>(
packet_encryption_key.inner(),
key_digest.into_iter(),
)
self.build::<ReplySurbEncryptionAlgorithm>(packet_encryption_key.inner(), key_digest)
}
pub fn build_regular<R>(
+5 -9
View File
@@ -73,7 +73,7 @@ impl SocketDataHeader {
.to_be_bytes()
.into_iter()
.chain(std::iter::once(self.local_socket_closed as u8))
.chain(self.seq.to_be_bytes().into_iter())
.chain(self.seq.to_be_bytes())
}
pub fn try_from_response_bytes(
@@ -107,8 +107,8 @@ impl SocketDataHeader {
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
std::iter::once(self.local_socket_closed as u8)
.chain(self.connection_id.to_be_bytes().into_iter())
.chain(self.seq.to_be_bytes().into_iter())
.chain(self.connection_id.to_be_bytes())
.chain(self.seq.to_be_bytes())
}
}
@@ -170,9 +170,7 @@ impl SocketData {
}
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
self.header
.into_request_bytes_iter()
.chain(self.data.into_iter())
self.header.into_request_bytes_iter().chain(self.data)
}
pub fn try_from_response_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
@@ -190,9 +188,7 @@ impl SocketData {
}
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
self.header
.into_response_bytes_iter()
.chain(self.data.into_iter())
self.header.into_response_bytes_iter().chain(self.data)
}
}
+9 -15
View File
@@ -113,7 +113,7 @@ impl Serializable for Socks5Request {
fn into_bytes(self) -> Vec<u8> {
if let Some(version) = self.protocol_version.as_u8() {
std::iter::once(version)
.chain(self.content.into_bytes().into_iter())
.chain(self.content.into_bytes())
.collect()
} else {
std::iter::once(Self::LEGACY_TYPE_TAG)
@@ -335,12 +335,12 @@ impl Socks5RequestContent {
let remote_address_bytes_len = remote_address_bytes.len() as u16;
let iter = std::iter::once(RequestFlag::Connect as u8)
.chain(req.conn_id.to_be_bytes().into_iter())
.chain(remote_address_bytes_len.to_be_bytes().into_iter())
.chain(remote_address_bytes.into_iter());
.chain(req.conn_id.to_be_bytes())
.chain(remote_address_bytes_len.to_be_bytes())
.chain(remote_address_bytes);
if let Some(return_address) = req.return_address {
iter.chain(return_address.to_bytes().into_iter()).collect()
iter.chain(return_address.to_bytes()).collect()
} else {
iter.collect()
}
@@ -358,7 +358,7 @@ impl Socks5RequestContent {
})
.unwrap_or_default();
std::iter::once(RequestFlag::Query as u8)
.chain(query_bytes.into_iter())
.chain(query_bytes)
.collect()
}
}
@@ -495,7 +495,7 @@ mod request_deserialization_tests {
let request_bytes: Vec<_> = request_bytes_prefix
.iter()
.cloned()
.chain(recipient_bytes.into_iter())
.chain(recipient_bytes)
.collect();
assert!(Socks5RequestContent::try_from_bytes(&request_bytes)
.unwrap_err()
@@ -530,10 +530,7 @@ mod request_deserialization_tests {
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let recipient_bytes = recipient.to_bytes();
let request_bytes: Vec<_> = request_bytes
.into_iter()
.chain(recipient_bytes.into_iter())
.collect();
let request_bytes: Vec<_> = request_bytes.into_iter().chain(recipient_bytes).collect();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request {
@@ -577,10 +574,7 @@ mod request_deserialization_tests {
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let recipient_bytes = recipient.to_bytes();
let request_bytes: Vec<_> = request_bytes
.into_iter()
.chain(recipient_bytes.into_iter())
.collect();
let request_bytes: Vec<_> = request_bytes.into_iter().chain(recipient_bytes).collect();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request {
+5 -5
View File
@@ -84,7 +84,7 @@ impl Serializable for Socks5Response {
fn into_bytes(self) -> Vec<u8> {
if let Some(version) = self.protocol_version.as_u8() {
std::iter::once(version)
.chain(self.content.into_bytes().into_iter())
.chain(self.content.into_bytes())
.collect()
} else {
self.content.into_bytes()
@@ -192,7 +192,7 @@ impl Socks5ResponseContent {
}
Socks5ResponseContent::ConnectionError(res) => {
std::iter::once(ResponseFlag::ConnectionError as u8)
.chain(res.into_bytes().into_iter())
.chain(res.into_bytes())
.collect()
}
Socks5ResponseContent::Query(query) => {
@@ -204,7 +204,7 @@ impl Socks5ResponseContent {
})
.unwrap_or_default();
std::iter::once(ResponseFlag::Query as u8)
.chain(query_bytes.into_iter())
.chain(query_bytes)
.collect()
}
}
@@ -290,7 +290,7 @@ impl ConnectionError {
.to_be_bytes()
.iter()
.copied()
.chain(self.network_requester_error.into_bytes().into_iter())
.chain(self.network_requester_error.into_bytes())
.collect()
}
}
@@ -339,7 +339,7 @@ mod tests {
let bytes: Vec<u8> = 42u64
.to_be_bytes()
.into_iter()
.chain([0, 159, 146, 150].into_iter())
.chain([0, 159, 146, 150])
.collect();
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
assert!(matches!(
+1 -1
View File
@@ -67,7 +67,7 @@ impl Node {
}
pub fn clients_address(&self) -> String {
format!("{}:{}", self.host, self.clients_port)
format!("ws://{}:{}", self.host, self.clients_port)
}
}
+2 -75
View File
@@ -3,7 +3,6 @@
use crate::filter::VersionFilterable;
use log::warn;
use nym_crypto::asymmetric::encryption;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_sphinx_addressing::nodes::NodeIdentity;
@@ -72,64 +71,11 @@ pub type MixLayer = u8;
pub struct NymTopology {
mixes: BTreeMap<MixLayer, Vec<mix::Node>>,
gateways: Vec<gateway::Node>,
all_mixes: Vec<mix::Node>,
all_gateways: Vec<gateway::Node>,
}
impl NymTopology {
pub fn new(mixes: BTreeMap<MixLayer, Vec<mix::Node>>, gateways: Vec<gateway::Node>) -> Self {
NymTopology {
mixes: mixes.clone(),
gateways: gateways.clone(),
all_mixes: mixes.values().flatten().cloned().collect(),
all_gateways: gateways,
}
}
pub fn empty() -> Self {
NymTopology {
mixes: BTreeMap::new(),
gateways: Vec::new(),
all_mixes: Vec::new(),
all_gateways: Vec::new(),
}
}
pub fn with_all_mixes(mut self, all_mixes: Vec<MixNodeDetails>) -> Self {
let mut mixes = Vec::new();
for bond in all_mixes
.into_iter()
.map(|details| details.bond_information)
{
let mix_id = bond.mix_id;
let mix_identity = bond.mix_node.identity_key.clone();
match bond.try_into() {
Ok(mix) => mixes.push(mix),
Err(err) => {
warn!("Mix {} / {} is malformed - {err}", mix_id, mix_identity);
continue;
}
}
}
self.all_mixes = mixes;
self
}
pub fn with_all_gateways(mut self, all_gateways: Vec<GatewayBond>) -> Self {
let mut gateways = Vec::with_capacity(all_gateways.len());
for bond in all_gateways.into_iter() {
let gate_id = bond.gateway.identity_key.clone();
match bond.try_into() {
Ok(gate) => gateways.push(gate),
Err(err) => {
warn!("Gateway {} is malformed - {err}", gate_id);
continue;
}
}
}
self.all_gateways = gateways;
self
NymTopology { mixes, gateways }
}
pub fn from_detailed(
@@ -161,23 +107,6 @@ impl NymTopology {
None
}
pub fn find_node_key_by_mix_host(
&self,
mix_host: SocketAddr,
) -> Option<&encryption::PublicKey> {
for node in self.all_gateways.iter() {
if node.mix_host.ip() == mix_host.ip() {
return Some(&node.sphinx_key);
}
}
for node in self.all_mixes.iter() {
if node.mix_host.ip() == mix_host.ip() {
return Some(&node.sphinx_key);
}
}
None
}
pub fn find_gateway(&self, gateway_identity: IdentityKeyRef) -> Option<&gateway::Node> {
self.gateways
.iter()
@@ -202,7 +131,7 @@ impl NymTopology {
}
pub fn mixes_in_layer(&self, layer: MixLayer) -> Vec<mix::Node> {
assert!(vec![1, 2, 3].contains(&layer));
assert!([1, 2, 3].contains(&layer));
self.mixes.get(&layer).unwrap().to_owned()
}
@@ -385,8 +314,6 @@ impl NymTopology {
NymTopology {
mixes: self.mixes.filter_by_version(expected_mix_version),
gateways: self.gateways.clone(),
all_mixes: self.all_mixes.clone(),
all_gateways: self.all_gateways.clone(),
}
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ serde_json = { workspace = true }
strum = { version = "0.23", features = ["derive"] }
thiserror = { workspace = true }
url = { workspace = true }
ts-rs = "6.1.2"
ts-rs = { workspace = true }
cosmwasm-std = { workspace = true }
cosmrs = { workspace = true }
-1
View File
@@ -5,7 +5,6 @@ use crate::console_log;
use crate::storage::cipher_export::StoredExportedStoreCipher;
use crate::storage::error::StorageError;
use futures::TryFutureExt;
use indexed_db_futures::IdbDatabase;
use nym_store_cipher::{
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
Version,
+1 -1
View File
@@ -176,7 +176,7 @@ mod tests {
use cosmwasm_std::Order;
use rand_chacha::rand_core::RngCore;
fn read_entire_set(storage: &mut dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
fn read_entire_set(storage: &dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
REWARDED_SET
.range(storage, None, None, Order::Ascending)
.map(|r| r.unwrap())
-1
View File
@@ -286,7 +286,6 @@ mod tests {
let owner = "steve";
let (name, owner_signature) =
new_name_details_with_sign(deps.as_mut(), &mut rng, "my-name", owner, deposit.clone());
dbg!(&name);
// Register
let msg = ExecuteMsg::Register {
@@ -265,7 +265,6 @@ fn can_register_multiple_names_for_the_same_nym_address(mut setup: TestSetup) {
let reg_name2 = reg_name2.sign(payload);
setup.register(&reg_name2, &owner);
dbg!(&setup.query_all().names);
assert_eq!(
setup.query_all().names,
vec![
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@@ -63,16 +63,16 @@ To check available configuration options for initializing your node use:
```
~~~
Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information.
Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl -4 https://ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information.
```
./nym-mixnode init --id <NODE_NAME> --host $(curl ifconfig.me)
./nym-mixnode init --id <NODE_NAME> --host $(curl -4 https://ifconfig.me)
```
<!---serinko: The automatized command did not work, printing the output manually--->
~~~admonish example collapsible=true title="Console output"
```
.nym-mixnode init --id <YOUR_ID> --host $(curl ifconfig.me) --wallet-address <WALLET_ADDRESS>
.nym-mixnode init --id <YOUR_ID> --host $(curl -4 https://ifconfig.me) --wallet-address <WALLET_ADDRESS>
Initialising mixnode <YOUR_ID>...
+1
View File
@@ -22,5 +22,6 @@ REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://rpc.nymtech.net"
NYM_API="https://validator.nymtech.net/api/"
EXPLORER_API="https://explorer.nymtech.net/api/"
DKG_TIME_CONFIGURATION="259200,300,300,60,60,1209600"

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