Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e20ed854df | |||
| c6509c3a95 | |||
| 8a9d242d03 | |||
| 769a26fdeb | |||
| 2e7ddcb195 | |||
| 84d893198b | |||
| 2f6617daac | |||
| 9e483da802 | |||
| d530e492ec | |||
| 6c67cff235 | |||
| 66f04d17b7 | |||
| 492155d04a | |||
| 069226125b | |||
| 1ce536c2fa | |||
| 2fbb901deb | |||
| 3c3ec9c831 | |||
| 6a34a99fee | |||
| ec75a06c9d | |||
| 2fbe7bb350 | |||
| ee67f3d0f0 | |||
| 188650ea05 | |||
| decae2b54d | |||
| 8104761914 | |||
| 8503def37f | |||
| 48023eab41 | |||
| b050ae72de | |||
| 377c9be790 | |||
| 5d7fd66cfc | |||
| 5128aef193 | |||
| 6a327b0bd6 | |||
| 8a38c61065 | |||
| f0a888d59c | |||
| f1b9cf4d68 | |||
| 392e7b5268 | |||
| 776a9d508a | |||
| 2f624d4f10 | |||
| 3c9faff4ec | |||
| 026d52a218 | |||
| f8b70097d3 | |||
| 0d3df4b58d | |||
| 5241047f45 | |||
| 2651784e8b | |||
| 2cd2b1ccd4 | |||
| af4e8241e7 | |||
| 92350daca8 | |||
| b429c64168 | |||
| f88622ac08 | |||
| 0a5a2c6747 | |||
| 824bd636f9 | |||
| cf6411ac08 | |||
| 6428133122 | |||
| 3c69f9c2f9 | |||
| c1e4b87744 | |||
| dffe171b7f | |||
| 6d3b198f00 | |||
| d4920b82f0 | |||
| 8dfe8f4678 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -32,5 +32,5 @@ jobs:
|
||||
run: yarn
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test:qa
|
||||
run: yarn test:sandbox
|
||||
working-directory: nym-api/tests
|
||||
|
||||
@@ -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/"
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
Generated
+2
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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,
|
||||
¤t_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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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)?)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ impl Fragment {
|
||||
self.header
|
||||
.to_bytes()
|
||||
.into_iter()
|
||||
.chain(self.payload.into_iter())
|
||||
.chain(self.payload)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(®_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>...
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user