Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7b03be4c7 | |||
| d8bef263b5 | |||
| 2df42e222c | |||
| a1482a2887 | |||
| b57df35f8c | |||
| 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 | |||
| e04c759c14 | |||
| 3c9faff4ec | |||
| 9d22387b18 | |||
| 026d52a218 | |||
| f8b70097d3 | |||
| 0d3df4b58d | |||
| 5241047f45 | |||
| 2651784e8b | |||
| 2cd2b1ccd4 | |||
| af4e8241e7 | |||
| 92350daca8 | |||
| 6254656ab6 | |||
| b429c64168 | |||
| f88622ac08 | |||
| 0a5a2c6747 | |||
| afda62a5cf | |||
| 824bd636f9 | |||
| cf6411ac08 | |||
| 6428133122 | |||
| 3c69f9c2f9 | |||
| c1e4b87744 | |||
| dffe171b7f | |||
| 6d3b198f00 | |||
| d4920b82f0 | |||
| 8dfe8f4678 | |||
| a322becfec | |||
| 9b4e25221f | |||
| f46cc9d1bb | |||
| bf53a107af | |||
| 25ebdbb6eb | |||
| 47d045b1c7 | |||
| 0b0bb8175f |
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+22
-2
@@ -4,7 +4,27 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.1.28] (2023-08-22)
|
||||
## [v1.1.29-snickers] (2023-08-29)
|
||||
|
||||
- Add EXPLORER_API configurable url ([#3810])
|
||||
- Bugfix/use correct tendermint dialect ([#3802])
|
||||
- Explorer - look up gateways based on geo-location ([#3776])
|
||||
- Speedy mode - select the mixnodes based on the location of the NR ([#3775])
|
||||
- NR - reduce response time by removing poisson delay ([#3774])
|
||||
- [demo] libp2p example with nym-sdk ([#3763])
|
||||
- introduced /network/details endpoint to nym-api to return used network information ([#3758])
|
||||
- Feature/issue credentials ([#3691])
|
||||
|
||||
[#3810]: https://github.com/nymtech/nym/pull/3810
|
||||
[#3802]: https://github.com/nymtech/nym/pull/3802
|
||||
[#3776]: https://github.com/nymtech/nym/issues/3776
|
||||
[#3775]: https://github.com/nymtech/nym/issues/3775
|
||||
[#3774]: https://github.com/nymtech/nym/issues/3774
|
||||
[#3763]: https://github.com/nymtech/nym/pull/3763
|
||||
[#3758]: https://github.com/nymtech/nym/pull/3758
|
||||
[#3691]: https://github.com/nymtech/nym/pull/3691
|
||||
|
||||
## [v1.1.28] (2023-08-22)
|
||||
|
||||
- [final step3]: add [rust] support to nyxd client in wasm ([#3743])
|
||||
- Feature/ephemera upgrade ([#3791])
|
||||
@@ -19,7 +39,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#3767]: https://github.com/nymtech/nym/pull/3767
|
||||
|
||||
|
||||
## [1.1.27] (2023-08-16)
|
||||
## [v1.1.27] (2023-08-16)
|
||||
|
||||
- fix serialisation of contract types ([#3752])
|
||||
- Investigate spending credentials from the main API (coconut enabled to a gateway) from feature/ephemera branch ([#3741])
|
||||
|
||||
Generated
+643
-112
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)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.26"
|
||||
version = "1.1.27"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.26"
|
||||
version = "1.1.27"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -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,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::storage::error::ClientStorageError;
|
||||
use js_sys::Promise;
|
||||
@@ -8,7 +10,6 @@ use nym_client_core::client::base_client::storage::gateway_details::PersistedGat
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
|
||||
@@ -43,7 +44,7 @@ mod v1 {
|
||||
pub struct ClientStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) name: String,
|
||||
pub(crate) inner: Arc<WasmStorage>,
|
||||
pub(crate) inner: Rc<WasmStorage>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -88,7 +89,7 @@ impl ClientStorage {
|
||||
.await?;
|
||||
|
||||
Ok(ClientStorage {
|
||||
inner: Arc::new(inner),
|
||||
inner: Rc::new(inner),
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -29,6 +29,7 @@ use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::QueryReqwestRpcNyxdClient;
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Mutex as SyncMutex};
|
||||
use std::time::Duration;
|
||||
@@ -42,7 +43,7 @@ pub(crate) mod helpers;
|
||||
|
||||
pub type NodeTestMessage = TestMessage<WasmTestMessageExt>;
|
||||
type LockedGatewayClient =
|
||||
Arc<AsyncMutex<GatewayClient<QueryReqwestRpcNyxdClient, EphemeralStorage>>>;
|
||||
Rc<AsyncMutex<GatewayClient<QueryReqwestRpcNyxdClient, EphemeralStorage>>>;
|
||||
|
||||
pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20;
|
||||
@@ -210,7 +211,7 @@ impl NymNodeTesterBuilder {
|
||||
test_in_progress: Arc::new(AtomicBool::new(false)),
|
||||
current_test_nonce: Default::default(),
|
||||
tester: Arc::new(SyncMutex::new(tester)),
|
||||
gateway_client: Arc::new(AsyncMutex::new(gateway_client)),
|
||||
gateway_client: Rc::new(AsyncMutex::new(gateway_client)),
|
||||
processed_receiver: ReceivedReceiverWrapper::new(processed_receiver),
|
||||
_task_manager: task_manager,
|
||||
})
|
||||
@@ -343,7 +344,7 @@ impl NymNodeTester {
|
||||
));
|
||||
|
||||
let processed_receiver_clone = self.processed_receiver.clone();
|
||||
let gateway_client_clone = Arc::clone(&self.gateway_client);
|
||||
let gateway_client_clone = Rc::clone(&self.gateway_client);
|
||||
let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress));
|
||||
|
||||
// start doing async things (send packets and watch for anything coming back)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -354,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,
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::{
|
||||
addressing::clients::Recipient,
|
||||
params::{PacketSize, PacketType},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
@@ -480,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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -131,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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
- [Integration FAQ](integrations/faq.md)
|
||||
- [Mixnet Integration](integrations/mixnet-integration.md)
|
||||
- [Payment Integration](integrations/payment-integration.md)
|
||||
|
||||
@@ -39,12 +38,17 @@
|
||||
- [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md)
|
||||
- [IPFS Service Provider (coming soon)](tutorials/ipfs-service-provider.md)
|
||||
|
||||
# FAQ
|
||||
|
||||
- [General](faq/general-faq.md)
|
||||
- [Integrations](faq/integrations-faq.md)
|
||||
- [Rewards & Token](faq/rewards-faq.md)
|
||||
|
||||
# Community Resources
|
||||
|
||||
- [Nym DevRel AMAs](community-resources/ama.md)
|
||||
- [Community Applications and Guides](community-resources/community-applications-and-guides.md)
|
||||
- [Change Service Grantee Information](info-request.md)
|
||||
- [Rewards FAQ](community-resources/rewards-faq.md)
|
||||
|
||||
---
|
||||
# Misc.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Nym General FAQ
|
||||
|
||||
> If you have questions which are not answered below, please share them at our [DevRel AMAs](../community-resources/ama.md) or seek support in [Nym's community](https://nymtech.net/community)
|
||||
|
||||
### What is Nym?
|
||||
|
||||
Nym is a privacy platform that secures user data and protects against surveillance at the network level.
|
||||
|
||||
The platform does so by leveraging different technological components:
|
||||
|
||||
- **Nym Mixnet**, a type of overlay network that makes both content and metadata of transactions private through mixing, network-level obfuscation and incentivisation (using Sphinx);
|
||||
- A blockchain called **Nyx**, our Cosmos SDK blockchain, to allow for us to use payment tokens in the form of NYM, as well as smart contracts, in order to create a robust, decentralized, and secure environment incentives for the Mixnet;
|
||||
- **Coconut**, a zero-knowledge signature scheme, that creates an application-level private access control layer to power Zk-Nyms;
|
||||
- A utility token **NYM**, to pay for usage, measure reputation and serve as rewards for the privacy infrastructure.
|
||||
|
||||
Simply put, the [Nym network ("Nym")](https://www.feat-nym-update-nym-web.websites.dev.nymte.ch/nym-whitepaper.pdf) is a decentralized and incentivized infrastructure to provision privacy to a broad range of message-based applications and services. Think of it as a [*"Layer 0" privacy infrastructure*](https://blog.nymtech.net/nym-layer-0-privacy-infrastructure-for-the-whole-internet-e53238f9b8e7) for the entire internet.
|
||||
|
||||
**Related articles:**
|
||||
- [*Nym is not a blockchain, but is powered by one*](https://blog.nymtech.net/nym-is-not-a-blockchain-but-it-is-powered-by-one-4bb16ef16587)
|
||||
- [*Nym tokens*](https://blog.nymtech.net/nym-tokens-where-do-they-live-and-how-are-they-distributed-cross-chain-8d134bf9c41f)
|
||||
- [*A breakdown of the smart contracts that run the Nym token economics*](https://blog.nymtech.net/a-breakdown-of-the-smart-contracts-that-run-the-nym-token-economics-3a61b4139f95)
|
||||
- [*Zk-Nyms are here*](https://blog.nymtech.net/a-breakdown-of-the-smart-contracts-that-run-the-nym-token-economics-3a61b4139f95)
|
||||
- [*Sphinx: the anonymous data format that powers Nym*](https://blog.nymtech.net/sphinx-tl-dr-the-data-packet-that-can-anonymize-bitcoin-and-the-internet-18d152c6e4dc)
|
||||
|
||||
|
||||
### What's the difference between Nym and VPNs?
|
||||
|
||||
Nym is not an onion routing system, it is not a decentralized VPN - it’s much more than that. Nym is a mixnet meant to stop precisely the traffic analysis attacks that Tor and dVPNs are vulnerable to.
|
||||
|
||||
It is an orthogonal design that maintains better privacy and can support anonymity, although usually with a cost in terms of latency.
|
||||
It basically is an infrastructure on which privacy preserving apps can be built, leveraging the Mixnet and Coconut credentials, amongst others.
|
||||
The **Nym mixnet and VPNs differ** because VPNs do not mix nor do they protect metadata from an adversary who may be able to watch the entire network.
|
||||
|
||||
**Related articles:**
|
||||
- [*VPNs, Tor, I2P, how does Nym compare? *](https://blog.nymtech.net/vpns-tor-i2p-how-does-nym-compare-8576824617b8)
|
||||
|
||||
|
||||
### What is Nym’s VPN?
|
||||
|
||||
Since Q2 2023 the Nym core team has been working on launching the **first major consumer facing product** that runs on top of the Nym mixnet: a high speed, trustless and decentralized VPN, paid for via the NYM token - facilitating anonymous payments if wished.
|
||||
The product positions itself as a full-network protection service available across all of a user’s devices, **leveraging the Nym Mixnet** and other primitives to offer split tunneling and traffic obfuscation techniques to protect against censorship.
|
||||
|
||||
**Related articles:**
|
||||
- [*What does it take to build the most powerful VPN?*](https://blog.nymtech.net/what-does-it-take-to-build-the-worlds-most-powerful-vpn-d351a76ec4e6)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
## Rewards FAQ
|
||||
# Rewards FAQ
|
||||
|
||||
On this page you will find important information about participation in community activities which involve NYM token rewards, such as contests, giveaways, and promotions. Before participating in any such activity, make sure to read this page carefully.
|
||||
|
||||
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"
|
||||
|
||||
+2
-1
@@ -23,4 +23,5 @@ EPHEMERA_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62kfu
|
||||
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
|
||||
NYXD="https://sandbox-validator1.nymtech.net"
|
||||
NYM_API="https://sandbox-nym-api1.nymtech.net/api/"
|
||||
NYM_API="https://sandbox-nym-api1.nymtech.net/api/"
|
||||
EXPLORER_API="https://sandbox-explorer.nymtech.net/api/"
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" }
|
||||
pretty_env_logger = "0.4"
|
||||
refinery = { version = "0.8.7", features = ["rusqlite"], optional = true }
|
||||
reqwest = { version = "0.11.6", features = ["json"] }
|
||||
rocksdb = { version = "0.20.1", optional = true }
|
||||
rocksdb = { version = "0.21.0", optional = true }
|
||||
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_derive = "1.0.149"
|
||||
|
||||
@@ -211,7 +211,7 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
|
||||
#[cfg(feature = "rocksdb_storage")]
|
||||
fn connect_rocksdb(&self) -> anyhow::Result<RocksDbStorage> {
|
||||
info!("Opening database...");
|
||||
RocksDbStorage::open(self.init.config.storage.clone())
|
||||
RocksDbStorage::open(&self.init.config.storage)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ fn block_hash_key(block_hash: &str) -> String {
|
||||
format!("{PREFIX_BLOCK_HASH}:{block_hash}")
|
||||
}
|
||||
|
||||
fn block_height_key(height: &u64) -> String {
|
||||
fn block_height_key(height: u64) -> String {
|
||||
format!("{PREFIX_BLOCK_HEIGHT}:{height}")
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ impl Database {
|
||||
pub(crate) fn get_block_by_height(&self, height: u64) -> anyhow::Result<Option<Block>> {
|
||||
trace!("Getting block by height: {}", height);
|
||||
|
||||
if let Some(block_hash) = self.database.get(block_height_key(&height))? {
|
||||
if let Some(block_hash) = self.database.get(block_height_key(height))? {
|
||||
let block_hash = String::from_utf8(block_hash)?;
|
||||
self.get_block_by_hash(&block_hash)
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,7 @@ use rocksdb::{TransactionDB, WriteBatchWithTransaction};
|
||||
|
||||
use crate::utilities::crypto::Certificate;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct DbStore {
|
||||
connection: Arc<TransactionDB>,
|
||||
}
|
||||
@@ -34,7 +35,7 @@ impl DbStore {
|
||||
|
||||
let block_id_key = block_hash_key(&hash_str);
|
||||
let certificates_key = certificates_key(&hash_str);
|
||||
let height_key = block_height_key(&block.header.height);
|
||||
let height_key = block_height_key(block.header.height);
|
||||
let members_key = members_key(&hash_str);
|
||||
let merkle_tree_key = merkle_tree_key(&hash_str);
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
CONFIGURED=true
|
||||
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
BECH32_PREFIX=n
|
||||
MIX_DENOM=unym
|
||||
MIX_DENOM_DISPLAY=nym
|
||||
STAKE_DENOM=unyx
|
||||
STAKE_DENOM_DISPLAY=nyx
|
||||
DENOMS_EXPONENT=6
|
||||
|
||||
REWARDING_VALIDATOR_ADDRESS=n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa
|
||||
MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
|
||||
VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l
|
||||
GROUP_CONTRACT_ADDRESS=n1pd7kfgvr5tpcv0xnlv46c4jsq9jg2r799xxrcwqdm4l2jhq2pjwqrmz5ju
|
||||
MULTISIG_CONTRACT_ADDRESS=n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a
|
||||
NAME_SERVICE_CONTRACT_ADDRESS=n12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs36asfp
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzqwucsfxmm445d70u8asqxpur4
|
||||
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
|
||||
NYXD="https://sandbox-validator1.nymtech.net"
|
||||
NYM_API="https://sandbox-nym-api1.nymtech.net/api"
|
||||
|
||||
GEOIP_DB_PATH=geo_ip/GeoLite2-City.mmdb
|
||||
# MaxMind account ID
|
||||
# TODO replace with your own account ID
|
||||
GEOIPUPDATE_ACCOUNT_ID=xxx
|
||||
# MaxMind license key
|
||||
# TODO replace with your own license key
|
||||
GEOIPUPDATE_LICENSE_KEY=xxx
|
||||
# List of space-separated database edition IDs. Edition IDs may
|
||||
# consist of letters, digits, and dashes. For example, GeoIP2-City
|
||||
# would download the GeoIP2 City database (GeoIP2-City).
|
||||
GEOIPUPDATE_EDITION_IDS=GeoLite2-City
|
||||
# The number of hours between geoipupdate runs. If this is not set
|
||||
# or is set to 0, geoipupdate will run once and exit.
|
||||
GEOIPUPDATE_FREQUENCY=72
|
||||
# The path to the directory where geoipupdate will download the
|
||||
# database.
|
||||
GEOIP_DB_DIRECTORY=./geo_ip
|
||||
@@ -1,3 +1,4 @@
|
||||
target
|
||||
explorer-api-state.json
|
||||
/geo_ip
|
||||
!.env.dev
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.26"
|
||||
version = "1.1.27"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
+10
-8
@@ -8,9 +8,17 @@ Features:
|
||||
- calculates how many nodes are in each country
|
||||
- proxies mixnode API requests to add HTTPS
|
||||
|
||||
## Development
|
||||
|
||||
Several environment variables are required. They can be
|
||||
provisioned via a `.env` file. For convenience a `.env.dev` is
|
||||
provided, just copy its content into `.env`.
|
||||
|
||||
Follow the steps to setup the geoip database.
|
||||
|
||||
## GeoIP db install/update
|
||||
|
||||
First we need to install the geoip database.
|
||||
A geoip database needs to be installed.
|
||||
|
||||
We use https://github.com/maxmind/geoipupdate to automatically
|
||||
download and update GeoLite2 binary database. For convenience we
|
||||
@@ -37,13 +45,7 @@ When starting the explorer-api, supply the environment variable
|
||||
`GEOIP_DB_PATH`, pointing to the GeoLite2 binary database file.
|
||||
It should be previously installed thanks to `geoipupdate` service.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run
|
||||
```
|
||||
|
||||
Note: explorer-api binary reads the provided `.env` file.
|
||||
Note: As mentioned above the explorer-api binary reads the provided `.env` file.
|
||||
|
||||
Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt.
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mix
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
ts-rs = { version = "6.1.2", optional = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use nym_api_requests::models::NodePerformance;
|
||||
use nym_contracts_common::Percent;
|
||||
use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode};
|
||||
use nym_mixnet_contract_common::{Addr, Coin, Gateway, Layer, MixId, MixNode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -42,3 +42,13 @@ pub struct Location {
|
||||
pub latitude: Option<f64>,
|
||||
pub longitude: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct PrettyDetailedGatewayBond {
|
||||
pub pledge_amount: Coin,
|
||||
pub owner: Addr,
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
pub proxy: Option<Addr>,
|
||||
pub location: Option<Location>,
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@ impl GeoLocateTask {
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
info!("Spawning mix node locator task runner...");
|
||||
info!("Spawning locator task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval_timer.tick() => {
|
||||
self.locate_mix_nodes().await;
|
||||
self.locate_gateways().await;
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
@@ -107,4 +108,68 @@ impl GeoLocateTask {
|
||||
|
||||
trace!("All mix nodes located");
|
||||
}
|
||||
|
||||
async fn locate_gateways(&mut self) {
|
||||
let gateways = self.state.inner.gateways.get_gateways().await;
|
||||
|
||||
let geo_ip = self.state.inner.geo_ip.0.clone();
|
||||
|
||||
for (i, cache_item) in gateways.iter().enumerate() {
|
||||
if self
|
||||
.state
|
||||
.inner
|
||||
.gateways
|
||||
.is_location_valid(cache_item.identity().to_owned())
|
||||
.await
|
||||
{
|
||||
// when the cached location is valid, don't locate and continue to next gateway
|
||||
continue;
|
||||
}
|
||||
|
||||
match geo_ip.query(&cache_item.gateway.host, Some(cache_item.gateway.mix_port)) {
|
||||
Ok(opt) => match opt {
|
||||
Some(location) => {
|
||||
let location: Location = location.into();
|
||||
|
||||
trace!(
|
||||
"{} gateways already located. Ip {} is located in {:#?}",
|
||||
i,
|
||||
cache_item.gateway.host,
|
||||
location.three_letter_iso_country_code,
|
||||
);
|
||||
|
||||
if i > 0 && (i % 100) == 0 {
|
||||
info!("Located {} gateways...", i + 1,);
|
||||
}
|
||||
|
||||
self.state
|
||||
.inner
|
||||
.gateways
|
||||
.set_location(cache_item.identity().to_owned(), Some(location))
|
||||
.await;
|
||||
|
||||
// one node has been located, so return out of the loop
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
warn!("❌ Location for {} not found.", cache_item.gateway.host);
|
||||
self.state
|
||||
.inner
|
||||
.gateways
|
||||
.set_location(cache_item.identity().to_owned(), None)
|
||||
.await;
|
||||
}
|
||||
},
|
||||
Err(_e) => {
|
||||
// warn!(
|
||||
// "❌ Oh no! Location for {} failed. Error: {:#?}",
|
||||
// cache_item.gateway.host,
|
||||
// e
|
||||
// );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trace!("All gateways located");
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user