Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a94272ffd6 | |||
| 02da10e222 | |||
| 2637924e9c | |||
| bbaa06417d | |||
| edcaf72714 | |||
| 80ad7c1798 |
@@ -1,50 +0,0 @@
|
||||
name: Publish Nym CLI binaries
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym-cli:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Check the release tag starts with `nym-cli-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-cli-') == false && github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Release tag did not start with nym-cli-...')
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build binary
|
||||
run: make build-nym-cli
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: nym-cli-${{ matrix.platform }}
|
||||
path: |
|
||||
target/release/nym-cli*
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: |
|
||||
target/release/nym-cli
|
||||
@@ -1,7 +1,5 @@
|
||||
name: Publish Nym binaries
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
@@ -20,7 +18,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Check the release tag starts with `nym-binaries-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
|
||||
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
@@ -37,24 +35,8 @@ jobs:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: my-artifact
|
||||
path: |
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: |
|
||||
target/release/nym-client
|
||||
@@ -63,5 +45,4 @@ jobs:
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nym-network-statistics
|
||||
@@ -51,12 +51,12 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
working-directory: nym-wallet/webdriver
|
||||
working-directory: nym-wallet/wallet-ui-tests
|
||||
|
||||
- name: Remove existing user datafile
|
||||
uses: JesseTG/rm@v1.0.2
|
||||
with:
|
||||
path: nym-wallet/webdriver/common/data/user-data.json
|
||||
path: nym-wallet/wallet-ui-tests/common/user-data.json
|
||||
|
||||
- name: Create user data json file
|
||||
id: create-json
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
with:
|
||||
name: "user-data.json"
|
||||
json: ${{ secrets.WALLET_USERDATA }}
|
||||
dir: "nym-wallet/webdriver/common/data/"
|
||||
dir: "nym-wallet/wallet-ui-tests/common/"
|
||||
|
||||
- name: Install tauri-driver
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -73,5 +73,5 @@ jobs:
|
||||
args: tauri-driver
|
||||
|
||||
- name: Launch tests
|
||||
run: xvfb-run yarn test:runall
|
||||
working-directory: nym-wallet/webdriver
|
||||
run: xvfb-run yarn test
|
||||
working-directory: nym-wallet/wallet-ui-tests
|
||||
|
||||
+1
-13
@@ -4,19 +4,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
|
||||
|
||||
[#1541]: https://github.com/nymtech/nym/pull/1541
|
||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||
[#1585]: https://github.com/nymtech/nym/pull/1585
|
||||
|
||||
|
||||
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
|
||||
@@ -35,7 +27,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- validator-api: add Swagger to document the REST API ([#1249]).
|
||||
- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261])
|
||||
- validator-api: add `uptime`, `estimated_operator_apy`, `estimated_delegators_apy` to `/mixnodes/detailed` endpoint ([#1393])
|
||||
- validator-api: add node info cache storing simulated active set inclusion probabilities
|
||||
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
|
||||
- mixnode: Added basic mixnode hardware reporting to the HTTP API ([#1308]).
|
||||
- validator-api: endpoint, in coconut mode, for returning the validator-api cosmos address ([#1404]).
|
||||
@@ -55,7 +46,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
|
||||
- explorer-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1482]).
|
||||
- network-requester: fix filter for suffix-only domains ([#1487])
|
||||
- validator-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service; cleaner shutdown, without panics ([#1496], [#1573]).
|
||||
- validator-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1496]).
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -72,7 +63,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
|
||||
- network explorer: tweak how active set probability is shown ([#1503])
|
||||
- validator-api: rewarder set update fails without panicking on possible nymd queries ([#1520])
|
||||
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
|
||||
|
||||
|
||||
[#1249]: https://github.com/nymtech/nym/pull/1249
|
||||
@@ -102,8 +92,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1496]: https://github.com/nymtech/nym/pull/1496
|
||||
[#1503]: https://github.com/nymtech/nym/pull/1503
|
||||
[#1520]: https://github.com/nymtech/nym/pull/1520
|
||||
[#1573]: https://github.com/nymtech/nym/pull/1573
|
||||
[#1576]: https://github.com/nymtech/nym/pull/1576
|
||||
|
||||
## [v1.0.1](https://github.com/nymtech/nym/tree/v1.0.1) (2022-05-04)
|
||||
|
||||
|
||||
Generated
+537
-467
File diff suppressed because it is too large
Load Diff
+1
@@ -133,6 +133,7 @@ where
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
real_messages.push(RealMessage::new(
|
||||
|
||||
+1
@@ -83,6 +83,7 @@ where
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, packet_recipient)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// if we have the ONLY strong reference to the ack data, it means it was removed from the
|
||||
|
||||
@@ -18,7 +18,6 @@ url = "2.2"
|
||||
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
|
||||
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
config = { path = "../../common/config" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::{MNEMONIC, NYMD_URL};
|
||||
use bip39::Mnemonic;
|
||||
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
|
||||
use std::str::FromStr;
|
||||
@@ -16,9 +17,9 @@ pub(crate) struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(nymd_url: &str, mnemonic: &str) -> Self {
|
||||
let nymd_url = Url::from_str(nymd_url).unwrap();
|
||||
let mnemonic = Mnemonic::from_str(mnemonic).unwrap();
|
||||
pub fn new() -> Self {
|
||||
let nymd_url = Url::from_str(NYMD_URL).unwrap();
|
||||
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config = nymd::Config::try_from_nym_network_details(&network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
|
||||
@@ -6,6 +6,7 @@ use clap::{Args, Subcommand};
|
||||
use pickledb::PickleDb;
|
||||
use rand::rngs::OsRng;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
use coconut_interface::{Attribute, Base58, BlindSignRequest, Bytable, Parameters};
|
||||
use credential_storage::storage::Storage;
|
||||
@@ -19,6 +20,7 @@ use validator_client::nymd::tx::Hash;
|
||||
use crate::client::Client;
|
||||
use crate::error::{CredentialClientError, Result};
|
||||
use crate::state::{KeyPair, RequestData, State};
|
||||
use crate::SIGNER_AUTHORITIES;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
@@ -37,12 +39,6 @@ pub(crate) trait Execute {
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Deposit {
|
||||
/// The nymd URL that should be used
|
||||
#[clap(long)]
|
||||
nymd_url: String,
|
||||
/// A mnemonic for the account that does the deposit
|
||||
#[clap(long)]
|
||||
mnemonic: String,
|
||||
/// The amount that needs to be deposited
|
||||
#[clap(long)]
|
||||
amount: u64,
|
||||
@@ -55,7 +51,7 @@ impl Execute for Deposit {
|
||||
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
|
||||
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
|
||||
|
||||
let client = Client::new(&self.nymd_url, &self.mnemonic);
|
||||
let client = Client::new();
|
||||
let tx_hash = client
|
||||
.deposit(
|
||||
self.amount,
|
||||
@@ -100,10 +96,6 @@ pub(crate) struct GetCredential {
|
||||
/// The hash of a successful deposit transaction
|
||||
#[clap(long)]
|
||||
tx_hash: String,
|
||||
/// The URLs to the validator-api endpoints the are run as coconut signer authorities, separated
|
||||
/// by comma (,)
|
||||
#[clap(long)]
|
||||
signer_authorities: String,
|
||||
/// If we want to get the signature without attaching a blind sign request; it is expected that
|
||||
/// there is already a signature stored on the signer
|
||||
#[clap(long, parse(from_flag))]
|
||||
@@ -116,8 +108,7 @@ impl Execute for GetCredential {
|
||||
let mut state = db
|
||||
.get::<State>(&self.tx_hash)
|
||||
.ok_or(CredentialClientError::NoDeposit)?;
|
||||
|
||||
let urls = config::parse_validators(&self.signer_authorities);
|
||||
let urls = SIGNER_AUTHORITIES.map(|addr| Url::from_str(addr).unwrap());
|
||||
|
||||
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
let bandwidth_credential_attributes = if self.__no_request {
|
||||
|
||||
@@ -11,24 +11,20 @@ cfg_if::cfg_if! {
|
||||
|
||||
use commands::{Commands, Execute};
|
||||
use error::Result;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
use clap::Parser;
|
||||
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
|
||||
|
||||
pub const MNEMONIC: &str = "jazz fatigue diagram account outer wrist slide cherry mother grid network pause wolf pig round answer mail junior better hair dismiss toward access end";
|
||||
pub const NYMD_URL: &str = "http://127.0.0.1:26657";
|
||||
pub const CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
|
||||
pub const SIGNER_AUTHORITIES: [&str; 1] = [
|
||||
"http://127.0.0.1:8080",
|
||||
];
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about)]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
/// Path where the sqlite credental database will be located.
|
||||
/// It should point to a $HOME/$CLIENT_ID/data/db.sqlite file of
|
||||
/// the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) credential_db_path: std::path::PathBuf,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
@@ -36,9 +32,8 @@ cfg_if::cfg_if! {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
|
||||
let shared_storage = credential_storage::initialise_storage(args.credential_db_path.clone()).await;
|
||||
let shared_storage = credential_storage::initialise_storage(std::path::PathBuf::from("/tmp/credential.db")).await;
|
||||
let mut db = match PickleDb::load(
|
||||
"credential.db",
|
||||
PickleDbDumpPolicy::AutoDump,
|
||||
|
||||
@@ -50,10 +50,6 @@ impl NymConfig for Config {
|
||||
.join("clients")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.base.get_nym_root_directory()
|
||||
}
|
||||
|
||||
@@ -199,9 +199,9 @@ impl NymClient {
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.set_disabled_credentials_mode(self.config.get_base().get_disabled_credentials_mode());
|
||||
|
||||
if self.config.get_base().get_disabled_credentials_mode() {
|
||||
gateway_client.set_disabled_credentials_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
|
||||
@@ -46,9 +46,10 @@ pub(crate) struct Init {
|
||||
fastmode: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[clap(long)]
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private_key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
@@ -78,7 +79,7 @@ impl From<Init> for OverrideConfig {
|
||||
port: init_config.port,
|
||||
fastmode: init_config.fastmode,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
|
||||
@@ -78,7 +78,7 @@ pub(crate) struct OverrideConfig {
|
||||
port: Option<u16>,
|
||||
fastmode: bool,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
@@ -126,15 +126,12 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
|
||||
}
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
{
|
||||
if args.enabled_credentials_mode {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
{
|
||||
if let Some(eth_endpoint) = args.eth_endpoint {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
}
|
||||
|
||||
@@ -35,9 +35,10 @@ pub(crate) struct Run {
|
||||
port: Option<u16>,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[clap(long)]
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private-key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
@@ -61,7 +62,7 @@ impl From<Run> for OverrideConfig {
|
||||
port: run_config.port,
|
||||
fastmode: false,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
|
||||
@@ -33,10 +33,6 @@ impl NymConfig for Config {
|
||||
.join("socks5-clients")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.base.get_nym_root_directory()
|
||||
}
|
||||
|
||||
@@ -198,9 +198,9 @@ impl NymClient {
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.set_disabled_credentials_mode(self.config.get_base().get_disabled_credentials_mode());
|
||||
|
||||
if self.config.get_base().get_disabled_credentials_mode() {
|
||||
gateway_client.set_disabled_credentials_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
|
||||
@@ -46,9 +46,10 @@ pub(crate) struct Init {
|
||||
fastmode: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[clap(long)]
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private_key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
@@ -77,7 +78,7 @@ impl From<Init> for OverrideConfig {
|
||||
port: init_config.port,
|
||||
fastmode: init_config.fastmode,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
|
||||
@@ -78,7 +78,7 @@ pub(crate) struct OverrideConfig {
|
||||
port: Option<u16>,
|
||||
fastmode: bool,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
@@ -121,14 +121,11 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
{
|
||||
if args.enabled_credentials_mode {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
{
|
||||
if let Some(eth_endpoint) = args.eth_endpoint {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
}
|
||||
|
||||
@@ -39,9 +39,10 @@ pub(crate) struct Run {
|
||||
port: Option<u16>,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[clap(long)]
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private-key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
@@ -64,7 +65,7 @@ impl From<Run> for OverrideConfig {
|
||||
port: run_config.port,
|
||||
fastmode: false,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
|
||||
@@ -54,13 +54,6 @@ impl MixnetResponseListener {
|
||||
return;
|
||||
}
|
||||
Ok(Message::Response(data)) => data,
|
||||
Ok(Message::NetworkRequesterResponse(r)) => {
|
||||
error!(
|
||||
"Network requester failed on connection id {} with error: {}",
|
||||
r.connection_id, r.network_requester_error
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.controller_sender
|
||||
|
||||
@@ -32,7 +32,7 @@ credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
topology = { path = "../../common/topology" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
wasm-utils = { path = "../../common/wasm-utils" }
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ async function main() {
|
||||
set_panic_hook();
|
||||
|
||||
// validator server we will use to get topology from
|
||||
const validator = "https://validator.nymtech.net/api"; //"http://localhost:8081";
|
||||
const validator = "https://sandbox-validator.nymtech.net/api"; //"http://localhost:8081";
|
||||
|
||||
client = new NymClient(validator);
|
||||
|
||||
|
||||
@@ -132,7 +132,9 @@ impl NymClient {
|
||||
bandwidth_controller,
|
||||
);
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(disabled_credentials_mode);
|
||||
if disabled_credentials_mode {
|
||||
gateway_client.set_disabled_credentials_mode(true)
|
||||
}
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
@@ -197,6 +199,7 @@ impl NymClient {
|
||||
// don't bother with acks etc. for time being
|
||||
let prepared_fragment = message_preparer
|
||||
.prepare_chunk_for_sending(message_chunk, topology, &self.ack_key, &recipient)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
console_warn!("packet is going to have round trip time of {:?}, but we're not going to do anything for acks anyway ", prepared_fragment.total_delay);
|
||||
|
||||
@@ -15,8 +15,8 @@ log = "0.4"
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
secp256k1 = { version = "0.20.3", optional = true }
|
||||
web3 = { version = "0.17.0", default-features = false, optional = true }
|
||||
secp256k1 = "0.20.3"
|
||||
web3 = { version = "0.17.0", default-features = false }
|
||||
async-trait = { version = "0.1.51" }
|
||||
|
||||
# internal
|
||||
@@ -73,5 +73,5 @@ features = ["js"]
|
||||
|
||||
[features]
|
||||
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
|
||||
wasm = []
|
||||
default = ["web3/default", "secp256k1"]
|
||||
wasm = ["web3/wasm", "web3/http", "web3/signing"]
|
||||
default = ["web3/default"]
|
||||
|
||||
@@ -23,7 +23,7 @@ use {
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use {
|
||||
credentials::token::bandwidth::TokenCredential,
|
||||
crypto::asymmetric::identity,
|
||||
@@ -45,7 +45,7 @@ use {
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
|
||||
Contract::from_json(
|
||||
web3.eth(),
|
||||
@@ -58,7 +58,7 @@ pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
|
||||
.expect("Invalid json abi")
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn eth_erc20_contract(web3: Web3<Http>) -> Contract<Http> {
|
||||
Contract::from_json(
|
||||
web3.eth(),
|
||||
@@ -76,11 +76,11 @@ pub struct BandwidthController<St: Storage> {
|
||||
storage: St,
|
||||
#[cfg(feature = "coconut")]
|
||||
validator_endpoints: Vec<url::Url>,
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
contract: Contract<Http>,
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
erc20_contract: Contract<Http>,
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
eth_private_key: SecretKey,
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn new(
|
||||
storage: St,
|
||||
eth_endpoint: String,
|
||||
@@ -120,7 +120,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
async fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
|
||||
self.storage
|
||||
.insert_erc20_credential(
|
||||
@@ -132,7 +132,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
async fn restore_keypair(&self) -> Result<identity::KeyPair, GatewayClientError> {
|
||||
let data = self.storage.get_next_erc20_credential().await?;
|
||||
let public_key = identity::PublicKey::from_base58_string(data.public_key).unwrap();
|
||||
@@ -141,7 +141,7 @@ where
|
||||
Ok(identity::KeyPair::from_keys(private_key, public_key))
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
async fn mark_keypair_as_spent(
|
||||
&self,
|
||||
keypair: &identity::KeyPair,
|
||||
@@ -180,7 +180,7 @@ where
|
||||
)?)
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub async fn prepare_token_credential(
|
||||
&self,
|
||||
gateway_identity: identity::PublicKey,
|
||||
@@ -219,7 +219,7 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub async fn buy_token_credential(
|
||||
&self,
|
||||
verification_key: identity::PublicKey,
|
||||
@@ -348,7 +348,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "coconut")))]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use network_defaults::ETH_EVENT_NAME;
|
||||
|
||||
@@ -162,10 +162,12 @@ impl PartiallyDelegated {
|
||||
.expect("stream sender was somehow dropped without sending anything!");
|
||||
|
||||
if let Some(res) = receive_res {
|
||||
let _res = res?;
|
||||
panic!(
|
||||
"This should have NEVER happened - returned a stream before receiving notification"
|
||||
)
|
||||
if let Err(err) = res {
|
||||
// the receiver got an error. most likely a network one.
|
||||
return Err(err);
|
||||
} else {
|
||||
panic!("This should have NEVER happened - returned a stream before receiving notification")
|
||||
}
|
||||
}
|
||||
|
||||
// this call failing is incredibly unlikely, but not impossible.
|
||||
|
||||
@@ -24,7 +24,7 @@ use mixnet_contract_common::{
|
||||
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedRewardedSetResponse, QueryMsg,
|
||||
RewardedSetUpdateDetails,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
use std::convert::TryInto;
|
||||
use std::time::SystemTime;
|
||||
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
|
||||
@@ -282,30 +282,6 @@ impl<C> NymdClient<C> {
|
||||
self.simulated_gas_multiplier = multiplier;
|
||||
}
|
||||
|
||||
pub async fn query_contract_smart<M, T>(
|
||||
&self,
|
||||
contract: &AccountId,
|
||||
query_msg: &M,
|
||||
) -> Result<T, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
M: ?Sized + Serialize + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.client.query_contract_smart(contract, query_msg).await
|
||||
}
|
||||
|
||||
pub async fn query_contract_raw(
|
||||
&self,
|
||||
contract: &AccountId,
|
||||
query_data: Vec<u8>,
|
||||
) -> Result<Vec<u8>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.query_contract_raw(contract, query_data).await
|
||||
}
|
||||
|
||||
pub fn wrap_contract_execute_message<M>(
|
||||
&self,
|
||||
contract_address: &AccountId,
|
||||
|
||||
@@ -7,11 +7,9 @@ use crate::nymd::error::NymdError;
|
||||
use crate::nymd::NymdClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use vesting_contract::vesting::Account;
|
||||
use vesting_contract_common::{
|
||||
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
|
||||
OriginalVestingResponse, Period, PledgeData, VestingDelegation,
|
||||
messages::QueryMsg as VestingQueryMsg, OriginalVestingResponse, Period, PledgeData,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
@@ -72,37 +70,6 @@ pub trait VestingQueryClient {
|
||||
&self,
|
||||
vesting_account_address: &str,
|
||||
) -> Result<Period, NymdError>;
|
||||
|
||||
async fn get_delegation_timestamps(
|
||||
&self,
|
||||
address: &str,
|
||||
mix_identity: String,
|
||||
) -> Result<DelegationTimesResponse, NymdError>;
|
||||
|
||||
async fn get_all_vesting_delegations_paged(
|
||||
&self,
|
||||
start_after: Option<(u32, IdentityKey, u64)>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<AllDelegationsResponse, NymdError>;
|
||||
|
||||
async fn get_all_vesting_delegations(&self) -> Result<Vec<VestingDelegation>, NymdError> {
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.get_all_vesting_delegations_paged(start_after.take(), None)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -265,29 +232,4 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
.query_contract_smart(self.vesting_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_delegation_timestamps(
|
||||
&self,
|
||||
address: &str,
|
||||
mix_identity: String,
|
||||
) -> Result<DelegationTimesResponse, NymdError> {
|
||||
let request = VestingQueryMsg::GetDelegationTimes {
|
||||
address: address.to_string(),
|
||||
mix_identity,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.vesting_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_vesting_delegations_paged(
|
||||
&self,
|
||||
start_after: Option<(u32, IdentityKey, u64)>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<AllDelegationsResponse, NymdError> {
|
||||
let request = VestingQueryMsg::GetAllDelegations { start_after, limit };
|
||||
self.client
|
||||
.query_contract_smart(self.vesting_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,30 +41,6 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
Self::default_config_directory(id).join(Self::config_file_name())
|
||||
}
|
||||
|
||||
// We provide a second set of functions that tries to not panic.
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf>;
|
||||
|
||||
fn try_default_config_directory(id: Option<&str>) -> Option<PathBuf> {
|
||||
if let Some(id) = id {
|
||||
Self::try_default_root_directory().map(|d| d.join(id).join("config"))
|
||||
} else {
|
||||
Self::try_default_root_directory().map(|d| d.join("config"))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_default_data_directory(id: Option<&str>) -> Option<PathBuf> {
|
||||
if let Some(id) = id {
|
||||
Self::try_default_root_directory().map(|d| d.join(id).join("data"))
|
||||
} else {
|
||||
Self::try_default_root_directory().map(|d| d.join("data"))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_default_config_file_path(id: Option<&str>) -> Option<PathBuf> {
|
||||
Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name()))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf;
|
||||
fn config_directory(&self) -> PathBuf;
|
||||
fn data_directory(&self) -> PathBuf;
|
||||
|
||||
@@ -217,7 +217,7 @@ pub enum QueryMsg {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {
|
||||
pub mixnet_denom: String,
|
||||
pub nodes_to_remove: Option<Vec<NodeToRemove>>,
|
||||
nodes_to_remove: Option<Vec<NodeToRemove>>,
|
||||
}
|
||||
|
||||
impl MigrateMsg {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
|
||||
use cosmwasm_std::{Coin, Timestamp};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
|
||||
pub mod events;
|
||||
pub mod messages;
|
||||
@@ -74,35 +73,3 @@ impl OriginalVestingResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)]
|
||||
pub struct VestingDelegation {
|
||||
pub account_id: u32,
|
||||
pub mix_identity: IdentityKey,
|
||||
pub block_timestamp: u64,
|
||||
pub amount: Uint128,
|
||||
}
|
||||
|
||||
impl VestingDelegation {
|
||||
pub fn storage_key(&self) -> (u32, IdentityKey, u64) {
|
||||
(
|
||||
self.account_id,
|
||||
self.mix_identity.clone(),
|
||||
self.block_timestamp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)]
|
||||
pub struct DelegationTimesResponse {
|
||||
pub owner: Addr,
|
||||
pub account_id: u32,
|
||||
pub mix_identity: IdentityKey,
|
||||
pub delegation_timestamps: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)]
|
||||
pub struct AllDelegationsResponse {
|
||||
pub delegations: Vec<VestingDelegation>,
|
||||
pub start_next_after: Option<(u32, IdentityKey, u64)>,
|
||||
}
|
||||
|
||||
@@ -170,12 +170,4 @@ pub enum QueryMsg {
|
||||
address: String,
|
||||
},
|
||||
GetLockedPledgeCap {},
|
||||
GetDelegationTimes {
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
GetAllDelegations {
|
||||
start_after: Option<(u32, IdentityKey, u64)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,6 +6,5 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.17"
|
||||
rand = "0.8.5"
|
||||
thiserror = "1.0.32"
|
||||
|
||||
@@ -4,8 +4,6 @@ pub enum Error {
|
||||
EmptyListCumulStake,
|
||||
#[error("Sample point was unexpectedly out of bounds")]
|
||||
SamplePointOutOfBounds,
|
||||
#[error("Norm computation failed on different size arrays")]
|
||||
#[error("Norm computation failed on different size arrarys")]
|
||||
NormDifferenceSizeArrays,
|
||||
#[error("Computed probabilities are fewer than input number of nodes")]
|
||||
ResultsShorterThanInput,
|
||||
}
|
||||
|
||||
@@ -1,50 +1,26 @@
|
||||
//! Active set inclusion probability simulator
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use error::Error;
|
||||
use rand::Rng;
|
||||
|
||||
mod error;
|
||||
|
||||
const TOLERANCE_L2_NORM: f64 = 1e-4;
|
||||
const TOLERANCE_MAX_NORM: f64 = 1e-4;
|
||||
const TOLERANCE_MAX_NORM: f64 = 1e-3;
|
||||
|
||||
pub struct SelectionProbability {
|
||||
pub active_set_probability: Vec<f64>,
|
||||
pub reserve_set_probability: Vec<f64>,
|
||||
pub samples: u64,
|
||||
pub time: Duration,
|
||||
pub samples: u32,
|
||||
pub delta_l2: f64,
|
||||
pub delta_max: f64,
|
||||
}
|
||||
|
||||
pub fn simulate_selection_probability_mixnodes<R>(
|
||||
list_stake_for_mixnodes: &[u128],
|
||||
pub fn simulate_selection_probability_mixnodes(
|
||||
list_stake_for_mixnodes: &[u64],
|
||||
active_set_size: usize,
|
||||
reserve_set_size: usize,
|
||||
max_samples: u64,
|
||||
max_time: Duration,
|
||||
rng: &mut R,
|
||||
) -> Result<SelectionProbability, Error>
|
||||
where
|
||||
R: Rng + ?Sized,
|
||||
{
|
||||
log::trace!("Simulating mixnode active set selection probability");
|
||||
|
||||
// In case the active set size is larger than the number of bonded mixnodes, they all have 100%
|
||||
// chance we don't have to go through with the simulation
|
||||
if list_stake_for_mixnodes.len() <= active_set_size {
|
||||
return Ok(SelectionProbability {
|
||||
active_set_probability: vec![1.0; list_stake_for_mixnodes.len()],
|
||||
reserve_set_probability: vec![0.0; list_stake_for_mixnodes.len()],
|
||||
samples: 0,
|
||||
time: Duration::ZERO,
|
||||
delta_l2: 0.0,
|
||||
delta_max: 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
max_samples: u32,
|
||||
) -> Result<SelectionProbability, Error> {
|
||||
// Total number of existing (registered) nodes
|
||||
let num_mixnodes = list_stake_for_mixnodes.len();
|
||||
|
||||
@@ -59,9 +35,7 @@ where
|
||||
let mut samples = 0;
|
||||
let mut delta_l2;
|
||||
let mut delta_max;
|
||||
|
||||
// Make sure we bound the time we allow it to run
|
||||
let start_time = Instant::now();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
loop {
|
||||
samples += 1;
|
||||
@@ -72,10 +46,8 @@ where
|
||||
let active_set_probability_previous = active_set_probability.clone();
|
||||
|
||||
// Select the active nodes for the epoch (hour)
|
||||
while sample_active_mixnodes.len() < active_set_size
|
||||
&& sample_active_mixnodes.len() < list_cumul_temp.len()
|
||||
{
|
||||
let candidate = sample_candidate(&list_cumul_temp, rng)?;
|
||||
while sample_active_mixnodes.len() < active_set_size {
|
||||
let candidate = sample_candidate(&list_cumul_temp, &mut rng)?;
|
||||
|
||||
if !sample_active_mixnodes.contains(&candidate) {
|
||||
sample_active_mixnodes.push(candidate);
|
||||
@@ -84,10 +56,8 @@ where
|
||||
}
|
||||
|
||||
// Select the reserve nodes for the epoch (hour)
|
||||
while sample_reserve_mixnodes.len() < reserve_set_size
|
||||
&& sample_reserve_mixnodes.len() + sample_active_mixnodes.len() < list_cumul_temp.len()
|
||||
{
|
||||
let candidate = sample_candidate(&list_cumul_temp, rng)?;
|
||||
while sample_reserve_mixnodes.len() < reserve_set_size {
|
||||
let candidate = sample_candidate(&list_cumul_temp, &mut rng)?;
|
||||
|
||||
if !sample_reserve_mixnodes.contains(&candidate)
|
||||
&& !sample_active_mixnodes.contains(&candidate)
|
||||
@@ -108,49 +78,35 @@ where
|
||||
// Convergence critera only on active set.
|
||||
// We devide by samples to get the average, that is not really part of the delta
|
||||
// computation.
|
||||
delta_l2 =
|
||||
l2_diff(&active_set_probability, &active_set_probability_previous)? / (samples as f64);
|
||||
delta_max =
|
||||
max_diff(&active_set_probability, &active_set_probability_previous)? / (samples as f64);
|
||||
delta_l2 = l2_diff(&active_set_probability, &active_set_probability_previous)?
|
||||
/ f64::from(samples);
|
||||
delta_max = max_diff(&active_set_probability, &active_set_probability_previous)?
|
||||
/ f64::from(samples);
|
||||
if samples > 10 && delta_l2 < TOLERANCE_L2_NORM && delta_max < TOLERANCE_MAX_NORM
|
||||
|| samples >= max_samples
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop if we run out of time
|
||||
if start_time.elapsed() > max_time {
|
||||
log::debug!("Simulation ran out of time, stopping");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Divide occurrences with the number of samples once we're done to get the probabilities.
|
||||
active_set_probability
|
||||
.iter_mut()
|
||||
.for_each(|x| *x /= samples as f64);
|
||||
.for_each(|x| *x /= f64::from(samples));
|
||||
reserve_set_probability
|
||||
.iter_mut()
|
||||
.for_each(|x| *x /= samples as f64);
|
||||
|
||||
// Some sanity checks of the output
|
||||
if active_set_probability.len() != num_mixnodes || reserve_set_probability.len() != num_mixnodes
|
||||
{
|
||||
return Err(Error::ResultsShorterThanInput);
|
||||
}
|
||||
.for_each(|x| *x /= f64::from(samples));
|
||||
|
||||
Ok(SelectionProbability {
|
||||
active_set_probability,
|
||||
reserve_set_probability,
|
||||
samples,
|
||||
time: start_time.elapsed(),
|
||||
delta_l2,
|
||||
delta_max,
|
||||
})
|
||||
}
|
||||
|
||||
// Compute the cumulative sum
|
||||
fn cumul_sum<'a>(list: impl IntoIterator<Item = &'a u128>) -> Vec<u128> {
|
||||
fn cumul_sum<'a>(list: impl IntoIterator<Item = &'a u64>) -> Vec<u64> {
|
||||
let mut list_cumul = Vec::new();
|
||||
let mut cumul = 0;
|
||||
for entry in list {
|
||||
@@ -160,10 +116,7 @@ fn cumul_sum<'a>(list: impl IntoIterator<Item = &'a u128>) -> Vec<u128> {
|
||||
list_cumul
|
||||
}
|
||||
|
||||
fn sample_candidate<R>(list_cumul: &[u128], rng: &mut R) -> Result<usize, Error>
|
||||
where
|
||||
R: Rng + ?Sized,
|
||||
{
|
||||
fn sample_candidate(list_cumul: &[u64], rng: &mut rand::rngs::ThreadRng) -> Result<usize, Error> {
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
let uniform = Uniform::from(0..*list_cumul.last().ok_or(Error::EmptyListCumulStake)?);
|
||||
let r = uniform.sample(rng);
|
||||
@@ -179,7 +132,7 @@ where
|
||||
}
|
||||
|
||||
// Update list of cumulative stake to reflect eliminating the picked node
|
||||
fn remove_mixnode_from_cumul_stake(candidate: usize, list_cumul_stake: &mut [u128]) {
|
||||
fn remove_mixnode_from_cumul_stake(candidate: usize, list_cumul_stake: &mut [u64]) {
|
||||
let prob_candidate = if candidate == 0 {
|
||||
list_cumul_stake[0]
|
||||
} else {
|
||||
@@ -218,14 +171,8 @@ fn max_diff(v1: &[f64], v2: &[f64]) -> Result<f64, Error> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn test_rng() -> StdRng {
|
||||
StdRng::seed_from_u64(42)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_cumul_sum() {
|
||||
let v = cumul_sum(&vec![1, 2, 3]);
|
||||
@@ -265,14 +212,11 @@ mod tests {
|
||||
];
|
||||
|
||||
let max_samples = 100_000;
|
||||
let max_time = Duration::from_secs(10);
|
||||
let mut rng = test_rng();
|
||||
|
||||
let SelectionProbability {
|
||||
active_set_probability,
|
||||
reserve_set_probability,
|
||||
samples,
|
||||
time,
|
||||
delta_l2,
|
||||
delta_max,
|
||||
} = simulate_selection_probability_mixnodes(
|
||||
@@ -280,15 +224,9 @@ mod tests {
|
||||
active_set_size,
|
||||
standby_set_size,
|
||||
max_samples,
|
||||
max_time,
|
||||
&mut rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Check that any possible test failure wasn't because we ran it on 1970s hardware, and the
|
||||
// sampling aborted prematurely due to hitting `max_time`.
|
||||
assert!(time < max_time);
|
||||
|
||||
// These values comes from running the python simulator for a very long time
|
||||
let expected_active_set_probability = vec![
|
||||
0.025_070_8,
|
||||
@@ -333,93 +271,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// We converge around 20_000, add another 500 for some slack due to random values
|
||||
assert_eq!(samples, 20_001);
|
||||
assert!(delta_l2 < TOLERANCE_L2_NORM);
|
||||
assert!(delta_max < TOLERANCE_MAX_NORM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fewer_nodes_than_active_set_size() {
|
||||
let active_set_size = 10;
|
||||
let standby_set_size = 3;
|
||||
let list_mix = vec![100, 100, 3000];
|
||||
let max_samples = 100_000;
|
||||
let max_time = Duration::from_secs(10);
|
||||
let mut rng = test_rng();
|
||||
|
||||
let SelectionProbability {
|
||||
active_set_probability,
|
||||
reserve_set_probability,
|
||||
samples,
|
||||
time: _,
|
||||
delta_l2,
|
||||
delta_max,
|
||||
} = simulate_selection_probability_mixnodes(
|
||||
&list_mix,
|
||||
active_set_size,
|
||||
standby_set_size,
|
||||
max_samples,
|
||||
max_time,
|
||||
&mut rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// These values comes from running the python simulator for a very long time
|
||||
let expected_active_set_probability = vec![1.0, 1.0, 1.0];
|
||||
let expected_reserve_set_probability = vec![0.0, 0.0, 0.0];
|
||||
assert!(
|
||||
max_diff(&active_set_probability, &expected_active_set_probability).unwrap()
|
||||
< 1e1 * f64::EPSILON
|
||||
);
|
||||
assert!(
|
||||
max_diff(&reserve_set_probability, &expected_reserve_set_probability).unwrap()
|
||||
< 1e1 * f64::EPSILON
|
||||
);
|
||||
|
||||
// We converge around 20_000, add another 500 for some slack due to random values
|
||||
assert_eq!(samples, 0);
|
||||
assert!(delta_l2 < f64::EPSILON);
|
||||
assert!(delta_max < f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fewer_nodes_than_reward_set_size() {
|
||||
let active_set_size = 4;
|
||||
let standby_set_size = 3;
|
||||
let list_mix = vec![100, 100, 3000, 342, 3_498_234];
|
||||
let max_samples = 100_000_000;
|
||||
let max_time = Duration::from_secs(10);
|
||||
let mut rng = test_rng();
|
||||
|
||||
let SelectionProbability {
|
||||
active_set_probability,
|
||||
reserve_set_probability,
|
||||
samples,
|
||||
time: _,
|
||||
delta_l2,
|
||||
delta_max,
|
||||
} = simulate_selection_probability_mixnodes(
|
||||
&list_mix,
|
||||
active_set_size,
|
||||
standby_set_size,
|
||||
max_samples,
|
||||
max_time,
|
||||
&mut rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// These values comes from running the python simulator for a very long time
|
||||
let expected_active_set_probability = vec![0.546, 0.538, 0.999, 0.915, 1.0];
|
||||
let expected_reserve_set_probability = vec![0.453, 0.461, 0.0005, 0.084, 0.0];
|
||||
assert!(
|
||||
max_diff(&active_set_probability, &expected_active_set_probability).unwrap() < 1e-2,
|
||||
);
|
||||
assert!(
|
||||
max_diff(&reserve_set_probability, &expected_reserve_set_probability).unwrap() < 1e-2,
|
||||
);
|
||||
|
||||
// We converge around 20_000, add another 500 for some slack due to random values
|
||||
assert_eq!(samples, 20_001);
|
||||
assert!(samples < 20_500);
|
||||
assert!(delta_l2 < TOLERANCE_L2_NORM);
|
||||
assert!(delta_max < TOLERANCE_MAX_NORM);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ pub enum CoconutError {
|
||||
)]
|
||||
DeserializationMinLength { min: usize, actual: usize },
|
||||
|
||||
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {object} or {modulus_target} % {modulus} == 0")]
|
||||
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
|
||||
DeserializationInvalidLength {
|
||||
actual: usize,
|
||||
target: usize,
|
||||
|
||||
@@ -213,7 +213,7 @@ where
|
||||
/// - compute vk_b = g^x || v_b
|
||||
/// - compute sphinx_plaintext = SURB_ACK || g^x || v_b
|
||||
/// - compute sphinx_packet = Sphinx(recipient, sphinx_plaintext)
|
||||
pub fn prepare_chunk_for_sending(
|
||||
pub async fn prepare_chunk_for_sending(
|
||||
&mut self,
|
||||
fragment: Fragment,
|
||||
topology: &NymTopology,
|
||||
@@ -222,7 +222,8 @@ where
|
||||
) -> Result<PreparedFragment, NymTopologyError> {
|
||||
// create an ack
|
||||
let (ack_delay, surb_ack_bytes) = self
|
||||
.generate_surb_ack(fragment.fragment_identifier(), topology, ack_key)?
|
||||
.generate_surb_ack(fragment.fragment_identifier(), topology, ack_key)
|
||||
.await?
|
||||
.prepare_for_sending();
|
||||
|
||||
// TODO:
|
||||
@@ -293,7 +294,7 @@ where
|
||||
}
|
||||
|
||||
/// Construct an acknowledgement SURB for the given [`FragmentIdentifier`]
|
||||
fn generate_surb_ack(
|
||||
async fn generate_surb_ack(
|
||||
&mut self,
|
||||
fragment_id: FragmentIdentifier,
|
||||
topology: &NymTopology,
|
||||
@@ -356,7 +357,8 @@ where
|
||||
// gateways could not distinguish reply packets from normal messages due to lack of said acks
|
||||
// note: the ack delay is irrelevant since we do not know the delay of actual surb
|
||||
let (_, surb_ack_bytes) = self
|
||||
.generate_surb_ack(reply_id, topology, ack_key)?
|
||||
.generate_surb_ack(reply_id, topology, ack_key)
|
||||
.await?
|
||||
.prepare_for_sending();
|
||||
|
||||
let zero_pad_len = self.packet_size.plaintext_size()
|
||||
|
||||
@@ -9,4 +9,3 @@ edition = "2021"
|
||||
[dependencies]
|
||||
nymsphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
|
||||
ordered-buffer = {path = "../ordered-buffer"}
|
||||
thiserror = "1"
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod msg;
|
||||
pub mod network_requester_response;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub use msg::*;
|
||||
pub use network_requester_response::*;
|
||||
pub use request::*;
|
||||
pub use response::*;
|
||||
|
||||
@@ -1,40 +1,36 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::network_requester_response::{Error as NrError, NetworkRequesterResponse};
|
||||
use crate::request::{Request, RequestError};
|
||||
use crate::response::{Response, ResponseError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum MessageError {
|
||||
#[error("{0}")]
|
||||
Request(RequestError),
|
||||
|
||||
#[error("{0:?}")]
|
||||
Response(ResponseError),
|
||||
|
||||
#[error("{0}")]
|
||||
NetworkRequesterResponseError(NrError),
|
||||
|
||||
#[error("no data")]
|
||||
NoData,
|
||||
|
||||
#[error("unknown message type received")]
|
||||
UnknownMessageType,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MessageError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MessageError::Request(r) => write!(f, "{}", r),
|
||||
MessageError::Response(r) => write!(f, "{:?}", r),
|
||||
MessageError::NoData => write!(f, "no data provided"),
|
||||
MessageError::UnknownMessageType => write!(f, "unknown message type received"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
Request(Request),
|
||||
Response(Response),
|
||||
NetworkRequesterResponse(NetworkRequesterResponse),
|
||||
}
|
||||
|
||||
impl Message {
|
||||
const REQUEST_FLAG: u8 = 0;
|
||||
const RESPONSE_FLAG: u8 = 1;
|
||||
const NR_RESPONSE_FLAG: u8 = 2;
|
||||
|
||||
pub fn conn_id(&self) -> u64 {
|
||||
match self {
|
||||
@@ -43,7 +39,6 @@ impl Message {
|
||||
Request::Send(conn_id, _, _) => *conn_id,
|
||||
},
|
||||
Message::Response(resp) => resp.connection_id,
|
||||
Message::NetworkRequesterResponse(resp) => resp.connection_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +49,6 @@ impl Message {
|
||||
Request::Send(_, data, _) => data.len(),
|
||||
},
|
||||
Message::Response(resp) => resp.data.len(),
|
||||
Message::NetworkRequesterResponse(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +65,6 @@ impl Message {
|
||||
Response::try_from_bytes(&b[1..])
|
||||
.map(Message::Response)
|
||||
.map_err(MessageError::Response)
|
||||
} else if b[0] == Self::NR_RESPONSE_FLAG {
|
||||
NetworkRequesterResponse::try_from_bytes(&b[1..])
|
||||
.map(Message::NetworkRequesterResponse)
|
||||
.map_err(MessageError::NetworkRequesterResponseError)
|
||||
} else {
|
||||
Err(MessageError::UnknownMessageType)
|
||||
}
|
||||
@@ -88,9 +78,6 @@ impl Message {
|
||||
Self::Response(r) => std::iter::once(Self::RESPONSE_FLAG)
|
||||
.chain(r.into_bytes().iter().cloned())
|
||||
.collect(),
|
||||
Self::NetworkRequesterResponse(r) => std::iter::once(Self::NR_RESPONSE_FLAG)
|
||||
.chain(r.into_bytes().iter().cloned())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ConnectionId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NetworkRequesterResponse {
|
||||
pub connection_id: ConnectionId,
|
||||
pub network_requester_error: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
#[error("no data provided")]
|
||||
NoData,
|
||||
|
||||
#[error("not enough bytes to recover the connection id")]
|
||||
ConnectionIdTooShort,
|
||||
|
||||
#[error("message is not utf8 encoded")]
|
||||
MalformedErrorMessage(#[from] std::string::FromUtf8Error),
|
||||
}
|
||||
|
||||
impl NetworkRequesterResponse {
|
||||
pub fn new(connection_id: ConnectionId, network_requester_error: String) -> Self {
|
||||
NetworkRequesterResponse {
|
||||
connection_id,
|
||||
network_requester_error,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<NetworkRequesterResponse, Error> {
|
||||
if b.is_empty() {
|
||||
return Err(Error::NoData);
|
||||
}
|
||||
|
||||
if b.len() < 8 {
|
||||
return Err(Error::ConnectionIdTooShort);
|
||||
}
|
||||
|
||||
let mut connection_id_bytes = b.to_vec();
|
||||
let network_requester_error_bytes = connection_id_bytes.split_off(8);
|
||||
|
||||
let connection_id = u64::from_be_bytes([
|
||||
connection_id_bytes[0],
|
||||
connection_id_bytes[1],
|
||||
connection_id_bytes[2],
|
||||
connection_id_bytes[3],
|
||||
connection_id_bytes[4],
|
||||
connection_id_bytes[5],
|
||||
connection_id_bytes[6],
|
||||
connection_id_bytes[7],
|
||||
]);
|
||||
let network_requester_error = String::from_utf8(network_requester_error_bytes)?;
|
||||
|
||||
Ok(NetworkRequesterResponse {
|
||||
connection_id,
|
||||
network_requester_error,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.connection_id
|
||||
.to_be_bytes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.network_requester_error.into_bytes().into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod network_requester_response_serde_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_serde() {
|
||||
let conn_id = 42;
|
||||
let network_requester_error = String::from("This is a test msg");
|
||||
let response = NetworkRequesterResponse::new(conn_id, network_requester_error.clone());
|
||||
let bytes = response.into_bytes();
|
||||
let deserialized_response = NetworkRequesterResponse::try_from_bytes(&bytes).unwrap();
|
||||
|
||||
assert_eq!(conn_id, deserialized_response.connection_id);
|
||||
assert_eq!(
|
||||
network_requester_error,
|
||||
deserialized_response.network_requester_error
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialization_errors() {
|
||||
let err = NetworkRequesterResponse::try_from_bytes(&[]).err().unwrap();
|
||||
assert_eq!(err, Error::NoData);
|
||||
|
||||
let bytes: [u8; 5] = [1, 2, 3, 4, 5];
|
||||
let err = NetworkRequesterResponse::try_from_bytes(&bytes)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(err, Error::ConnectionIdTooShort);
|
||||
|
||||
let bytes: Vec<u8> = 42u64
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain([0, 159, 146, 150].into_iter())
|
||||
.collect();
|
||||
let err = NetworkRequesterResponse::try_from_bytes(&bytes)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert!(matches!(err, Error::MalformedErrorMessage(_)));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError};
|
||||
use std::convert::TryFrom;
|
||||
use thiserror::Error;
|
||||
use std::fmt::{self};
|
||||
|
||||
pub type ConnectionId = u64;
|
||||
pub type RemoteAddress = String;
|
||||
@@ -15,30 +12,39 @@ pub enum RequestFlag {
|
||||
Send = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum RequestError {
|
||||
#[error("not enough bytes to recover the length of the address")]
|
||||
AddressLengthTooShort,
|
||||
|
||||
#[error("not enough bytes to recover the address")]
|
||||
AddressTooShort,
|
||||
|
||||
#[error("not enough bytes to recover the connection id")]
|
||||
ConnectionIdTooShort,
|
||||
|
||||
#[error("no data provided")]
|
||||
NoData,
|
||||
|
||||
#[error("request of unknown type")]
|
||||
UnknownRequestFlag,
|
||||
|
||||
#[error("too short return address")]
|
||||
ReturnAddressTooShort,
|
||||
|
||||
#[error("malformed return address - {0}")]
|
||||
MalformedReturnAddress(RecipientFormattingError),
|
||||
}
|
||||
|
||||
impl fmt::Display for RequestError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RequestError::AddressLengthTooShort => {
|
||||
write!(f, "not enough bytes to recover the length of the address")
|
||||
}
|
||||
RequestError::AddressTooShort => write!(f, "not enough bytes to recover the address"),
|
||||
RequestError::ConnectionIdTooShort => {
|
||||
write!(f, "not enough bytes to recover the connection id")
|
||||
}
|
||||
RequestError::NoData => write!(f, "no data provided"),
|
||||
RequestError::UnknownRequestFlag => write!(f, "request of unknown type"),
|
||||
RequestError::ReturnAddressTooShort => write!(f, "too short return address"),
|
||||
RequestError::MalformedReturnAddress(recipient_err) => {
|
||||
write!(f, "malformed return address - {}", recipient_err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RequestError {}
|
||||
|
||||
impl RequestError {
|
||||
pub fn is_malformed_return(&self) -> bool {
|
||||
matches!(self, RequestError::MalformedReturnAddress(_))
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ConnectionId;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ResponseError {
|
||||
#[error("not enough bytes to recover the connection id")]
|
||||
ConnectionIdTooShort,
|
||||
#[error("no data provided")]
|
||||
NoData,
|
||||
}
|
||||
/// A remote network response retrieved by the Socks5 service provider. This
|
||||
|
||||
@@ -5,14 +5,13 @@ use std::time::Duration;
|
||||
|
||||
use tokio::sync::watch::{self, error::SendError};
|
||||
|
||||
const DEFAULT_SHUTDOWN_TIMER_SECS: u64 = 5;
|
||||
const SHUTDOWN_TIMER_SECS: u64 = 5;
|
||||
|
||||
/// Used to notify other tasks to gracefully shutdown
|
||||
#[derive(Debug)]
|
||||
pub struct ShutdownNotifier {
|
||||
notify_tx: watch::Sender<()>,
|
||||
notify_rx: Option<watch::Receiver<()>>,
|
||||
shutdown_timer_secs: u64,
|
||||
}
|
||||
|
||||
impl Default for ShutdownNotifier {
|
||||
@@ -21,19 +20,11 @@ impl Default for ShutdownNotifier {
|
||||
Self {
|
||||
notify_tx,
|
||||
notify_rx: Some(notify_rx),
|
||||
shutdown_timer_secs: DEFAULT_SHUTDOWN_TIMER_SECS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShutdownNotifier {
|
||||
pub fn new(shutdown_timer_secs: u64) -> Self {
|
||||
Self {
|
||||
shutdown_timer_secs,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> ShutdownListener {
|
||||
ShutdownListener::new(
|
||||
self.notify_rx
|
||||
@@ -59,7 +50,7 @@ impl ShutdownNotifier {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
log::info!("Forcing shutdown");
|
||||
}
|
||||
_ = tokio::time::sleep(Duration::from_secs(self.shutdown_timer_secs)) => {
|
||||
_ = tokio::time::sleep(Duration::from_secs(SHUTDOWN_TIMER_SECS)) => {
|
||||
log::info!("Timout reached, forcing shutdown");
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,22 +1,3 @@
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569])
|
||||
|
||||
### Changed
|
||||
|
||||
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571])
|
||||
|
||||
### Fixed
|
||||
|
||||
- vesting-contract: the contract now correctly stores delegations with their timestamp as opposed to using block height ([#1544])
|
||||
- mixnet-contract: compounding delegator rewards is now possible even if the associated mixnode had already unbonded ([#1571])
|
||||
|
||||
[#1544]: https://github.com/nymtech/nym/pull/1544
|
||||
[#1569]: https://github.com/nymtech/nym/pull/1569
|
||||
[#1569]: https://github.com/nymtech/nym/pull/1571
|
||||
|
||||
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -8,6 +8,7 @@ use super::storage::{
|
||||
use crate::constants;
|
||||
use crate::contract::debug_with_visibility;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::delegations::transactions::_try_delegate_to_mixnode;
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage::mix_denom;
|
||||
use crate::mixnodes::storage::mixnodes;
|
||||
@@ -17,7 +18,7 @@ use crate::rewards::helpers;
|
||||
use crate::support::helpers::{is_authorized, operator_cost_at_epoch};
|
||||
use cosmwasm_std::{
|
||||
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Order, Response,
|
||||
StdResult, Storage, Uint128,
|
||||
Storage, Uint128,
|
||||
};
|
||||
use cw_storage_plus::Bound;
|
||||
use mixnet_contract_common::events::{
|
||||
@@ -449,15 +450,18 @@ pub fn try_compound_delegator_reward(
|
||||
|
||||
pub fn _try_compound_delegator_reward(
|
||||
block_height: u64,
|
||||
deps: DepsMut<'_>,
|
||||
mut deps: DepsMut<'_>,
|
||||
owner_address: &str,
|
||||
mix_identity: &str,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let delegation_map = crate::delegations::storage::delegations();
|
||||
let mix_denom = mix_denom(deps.storage)?;
|
||||
let owner = deps.api.addr_validate(owner_address)?;
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(&owner, proxy.as_ref());
|
||||
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(
|
||||
&deps.api.addr_validate(owner_address)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
let reward = calculate_delegator_reward(deps.storage, deps.api, key.clone(), mix_identity)?;
|
||||
let mut total_delegation_delegate = Uint128::zero();
|
||||
|
||||
@@ -465,7 +469,8 @@ pub fn _try_compound_delegator_reward(
|
||||
let delegation_heights = delegation_map
|
||||
.prefix((mix_identity.to_string(), key.clone()))
|
||||
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
.filter_map(|v| v.ok())
|
||||
.collect::<Vec<u64>>();
|
||||
|
||||
for h in delegation_heights {
|
||||
let delegation =
|
||||
@@ -489,36 +494,30 @@ pub fn _try_compound_delegator_reward(
|
||||
// since we know that the target node exists and because the total_delegation bucket
|
||||
// entry is created whenever the node itself is added, the unwrap here is fine
|
||||
// as the entry MUST exist
|
||||
Ok(total_delegation.unwrap() + reward)
|
||||
Ok(total_delegation
|
||||
.unwrap()
|
||||
.saturating_sub(total_delegation_delegate))
|
||||
},
|
||||
)?;
|
||||
|
||||
// let's simplify the entire procedure. Rather than creating a fresh delegation on the mixnode
|
||||
// via `_try_delegate_to_mixnode` and then waiting for reconcile to happen,
|
||||
// just save it directly to the storage right now.
|
||||
// my reasoning for that is simple: `_try_delegate_to_mixnode` could fail if the node the
|
||||
// delegator has delegated to no longer exists.
|
||||
let delegation = Delegation::new(
|
||||
owner,
|
||||
mix_identity.into(),
|
||||
_try_delegate_to_mixnode(
|
||||
deps.branch(),
|
||||
block_height,
|
||||
mix_identity,
|
||||
owner_address,
|
||||
Coin {
|
||||
amount: compounded_delegation,
|
||||
denom: mix_denom,
|
||||
},
|
||||
block_height,
|
||||
proxy,
|
||||
);
|
||||
|
||||
delegation_map.save(
|
||||
deps.storage,
|
||||
(mix_identity.into(), key.clone(), block_height),
|
||||
&delegation,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(mut bond) = mixnodes().may_load(deps.storage, mix_identity)? {
|
||||
bond.accumulated_rewards = Some(bond.accumulated_rewards().saturating_sub(reward));
|
||||
mixnodes().save(deps.storage, mix_identity, &bond, block_height)?;
|
||||
{
|
||||
if let Some(mut bond) = mixnodes().may_load(deps.storage, mix_identity)? {
|
||||
bond.accumulated_rewards = Some(bond.accumulated_rewards().saturating_sub(reward));
|
||||
mixnodes().save(deps.storage, mix_identity, &bond, block_height)?;
|
||||
}
|
||||
}
|
||||
|
||||
DELEGATOR_REWARD_CLAIMED_HEIGHT.save(
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::queued_migrations::migrate_config_from_env;
|
||||
use crate::storage::{
|
||||
account_from_address, locked_pledge_cap, update_locked_pledge_cap, BlockTimestampSecs, ADMIN,
|
||||
DELEGATIONS, MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
account_from_address, locked_pledge_cap, update_locked_pledge_cap, ADMIN,
|
||||
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
};
|
||||
use crate::traits::{
|
||||
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
|
||||
};
|
||||
use crate::vesting::{populate_vesting_periods, Account};
|
||||
use cosmwasm_std::{
|
||||
coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order,
|
||||
QueryResponse, Response, StdResult, Timestamp, Uint128,
|
||||
coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
|
||||
Response, Timestamp, Uint128,
|
||||
};
|
||||
use cw_storage_plus::Bound;
|
||||
use mixnet_contract_common::{Gateway, IdentityKey, MixNode};
|
||||
use vesting_contract_common::events::{
|
||||
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
||||
@@ -23,10 +22,7 @@ use vesting_contract_common::events::{
|
||||
use vesting_contract_common::messages::{
|
||||
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
|
||||
};
|
||||
use vesting_contract_common::{
|
||||
AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeData,
|
||||
VestingDelegation,
|
||||
};
|
||||
use vesting_contract_common::{OriginalVestingResponse, Period, PledgeData};
|
||||
|
||||
pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000);
|
||||
|
||||
@@ -522,13 +518,6 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
QueryMsg::GetCurrentVestingPeriod { address } => {
|
||||
to_binary(&try_get_current_vesting_period(&address, deps, env)?)
|
||||
}
|
||||
QueryMsg::GetDelegationTimes {
|
||||
address,
|
||||
mix_identity,
|
||||
} => to_binary(&try_get_delegation_times(deps, &address, mix_identity)?),
|
||||
QueryMsg::GetAllDelegations { start_after, limit } => {
|
||||
to_binary(&try_get_all_delegations(deps, start_after, limit)?)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
@@ -645,63 +634,6 @@ pub fn try_get_delegated_vesting(
|
||||
account.get_delegated_vesting(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
pub fn try_get_delegation_times(
|
||||
deps: Deps<'_>,
|
||||
vesting_account_address: &str,
|
||||
mix_identity: String,
|
||||
) -> Result<DelegationTimesResponse, ContractError> {
|
||||
let owner = deps.api.addr_validate(vesting_account_address)?;
|
||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||
|
||||
let delegation_timestamps = DELEGATIONS
|
||||
.prefix((account.storage_key(), mix_identity.clone()))
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
Ok(DelegationTimesResponse {
|
||||
owner,
|
||||
account_id: account.storage_key(),
|
||||
mix_identity,
|
||||
delegation_timestamps,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_get_all_delegations(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<(u32, IdentityKey, BlockTimestampSecs)>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<AllDelegationsResponse, ContractError> {
|
||||
let limit = limit.unwrap_or(100).min(200) as usize;
|
||||
|
||||
let start = start_after.map(Bound::exclusive);
|
||||
let delegations = DELEGATIONS
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.map(|kv| {
|
||||
kv.map(
|
||||
|((account_id, mix_identity, block_timestamp), amount)| VestingDelegation {
|
||||
account_id,
|
||||
mix_identity,
|
||||
block_timestamp,
|
||||
amount,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = if delegations.len() < limit {
|
||||
None
|
||||
} else {
|
||||
delegations
|
||||
.last()
|
||||
.map(|delegation| delegation.storage_key())
|
||||
};
|
||||
|
||||
Ok(AllDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after,
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractError> {
|
||||
if funds.is_empty() || funds[0].amount.is_zero() {
|
||||
return Err(ContractError::EmptyFunds);
|
||||
|
||||
@@ -5,7 +5,7 @@ use cw_storage_plus::{Item, Map};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use vesting_contract_common::PledgeData;
|
||||
|
||||
pub(crate) type BlockTimestampSecs = u64;
|
||||
type BlockHeight = u64;
|
||||
|
||||
pub const KEY: Item<'_, u32> = Item::new("key");
|
||||
const ACCOUNTS: Map<'_, String, Account> = Map::new("acc");
|
||||
@@ -14,7 +14,7 @@ const BALANCES: Map<'_, u32, Uint128> = Map::new("blc");
|
||||
const WITHDRAWNS: Map<'_, u32, Uint128> = Map::new("wthd");
|
||||
const BOND_PLEDGES: Map<'_, u32, PledgeData> = Map::new("bnd");
|
||||
const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw");
|
||||
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockTimestampSecs), Uint128> = Map::new("dlg");
|
||||
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockHeight), Uint128> = Map::new("dlg");
|
||||
pub const ADMIN: Item<'_, String> = Item::new("adm");
|
||||
pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix");
|
||||
pub const MIX_DENOM: Item<'_, String> = Item::new("den");
|
||||
@@ -35,7 +35,7 @@ pub fn update_locked_pledge_cap(
|
||||
}
|
||||
|
||||
pub fn save_delegation(
|
||||
key: (u32, IdentityKey, BlockTimestampSecs),
|
||||
key: (u32, IdentityKey, BlockHeight),
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
@@ -44,7 +44,7 @@ pub fn save_delegation(
|
||||
}
|
||||
|
||||
pub fn remove_delegation(
|
||||
key: (u32, IdentityKey, BlockTimestampSecs),
|
||||
key: (u32, IdentityKey, BlockHeight),
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
DELEGATIONS.remove(storage, key);
|
||||
|
||||
@@ -88,7 +88,7 @@ impl DelegatingAccount for Account {
|
||||
vec![coin.clone()],
|
||||
)?;
|
||||
self.track_delegation(
|
||||
env.block.time.seconds(),
|
||||
env.block.height,
|
||||
mix_identity,
|
||||
current_balance,
|
||||
coin,
|
||||
@@ -129,14 +129,14 @@ impl DelegatingAccount for Account {
|
||||
|
||||
fn track_delegation(
|
||||
&self,
|
||||
block_timestamp_secs: u64,
|
||||
block_height: u64,
|
||||
mix_identity: IdentityKey,
|
||||
current_balance: Uint128,
|
||||
delegation: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
save_delegation(
|
||||
(self.storage_key(), mix_identity, block_timestamp_secs),
|
||||
(self.storage_key(), mix_identity, block_height),
|
||||
delegation.amount,
|
||||
storage,
|
||||
)?;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::errors::ContractError;
|
||||
use crate::storage::{
|
||||
load_balance, load_bond_pledge, load_gateway_pledge, load_withdrawn, remove_bond_pledge,
|
||||
remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge,
|
||||
save_gateway_pledge, save_withdrawn, BlockTimestampSecs, DELEGATIONS, KEY,
|
||||
save_gateway_pledge, save_withdrawn, DELEGATIONS, KEY,
|
||||
};
|
||||
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
|
||||
use cw_storage_plus::Bound;
|
||||
@@ -261,19 +261,4 @@ impl Account {
|
||||
.filter_map(|x| x.ok())
|
||||
.fold(Uint128::zero(), |acc, (_key, val)| acc + val))
|
||||
}
|
||||
|
||||
pub fn total_delegations_at_timestamp(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
start_time: BlockTimestampSecs,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
Ok(DELEGATIONS
|
||||
.sub_prefix(self.storage_key())
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.filter(|((_mix, block_time), _amount)| *block_time <= start_time)
|
||||
.fold(Uint128::zero(), |acc, ((_mix, _block_time), amount)| {
|
||||
acc + amount
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::{delete_account, save_account, MIX_DENOM};
|
||||
use crate::storage::{delete_account, save_account, DELEGATIONS, MIX_DENOM};
|
||||
use crate::traits::VestingAccount;
|
||||
use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp, Uint128};
|
||||
use cosmwasm_std::{Addr, Coin, Env, Order, Storage, Timestamp, Uint128};
|
||||
use vesting_contract_common::{OriginalVestingResponse, Period};
|
||||
|
||||
use super::Account;
|
||||
@@ -16,6 +16,13 @@ impl VestingAccount for Account {
|
||||
+ self.get_pledged_vesting(None, env, storage)?.amount)
|
||||
}
|
||||
|
||||
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let new_balance = current_balance + amount.amount;
|
||||
self.save_balance(new_balance, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn locked_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -134,7 +141,14 @@ impl VestingAccount for Account {
|
||||
Period::In(idx) => self.periods[idx as usize].start_time,
|
||||
};
|
||||
|
||||
let coin = self.total_delegations_at_timestamp(storage, start_time)?;
|
||||
let coin = DELEGATIONS
|
||||
.sub_prefix(self.storage_key())
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.filter(|((_mix, block_time), _amount)| *block_time < start_time)
|
||||
.fold(Uint128::zero(), |acc, ((_mix, _block_time), amount)| {
|
||||
acc + amount
|
||||
});
|
||||
|
||||
let amount = Uint128::new(coin.u128().min(max_available.u128()));
|
||||
|
||||
@@ -144,7 +158,6 @@ impl VestingAccount for Account {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: why do we allow querying for block times in the past? - just use env.block.time all the time
|
||||
fn get_delegated_vesting(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -153,18 +166,9 @@ impl VestingAccount for Account {
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?;
|
||||
let total_delegations = self.total_delegations(storage)?;
|
||||
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
let start_time = match period {
|
||||
Period::Before => 0,
|
||||
Period::After => u64::MAX,
|
||||
Period::In(idx) => self.periods[idx as usize].start_time,
|
||||
};
|
||||
|
||||
let delegations_before_start_time =
|
||||
self.total_delegations_at_timestamp(storage, start_time)?;
|
||||
|
||||
let amount = delegations_before_start_time - delegated_free.amount;
|
||||
let amount = total_delegations - delegated_free.amount;
|
||||
|
||||
Ok(Coin {
|
||||
amount,
|
||||
@@ -257,11 +261,4 @@ impl VestingAccount for Account {
|
||||
save_account(self, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let new_balance = current_balance + amount.amount;
|
||||
self.save_balance(new_balance, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,10 @@ mod tests {
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
use crate::vesting::{populate_vesting_periods, Account};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, Addr, Coin, Timestamp, Uint128};
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification};
|
||||
use vesting_contract_common::messages::ExecuteMsg;
|
||||
use vesting_contract_common::Period;
|
||||
|
||||
#[test]
|
||||
@@ -758,171 +757,4 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(Uint128::zero(), bonded_vesting.amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegated_free() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
|
||||
let vesting_period_length_secs = 3600;
|
||||
|
||||
let account_creation_timestamp = 1650000000;
|
||||
let account_creation_blockheight = 12345;
|
||||
|
||||
// this value is completely arbitrary, I just wanted to keep consistent
|
||||
// (and make sure that if block timestamp increases so does the block height)
|
||||
let blocks_per_period = 100;
|
||||
|
||||
env.block.height = account_creation_blockheight;
|
||||
env.block.time = Timestamp::from_seconds(account_creation_timestamp);
|
||||
|
||||
// lets define some helper timestamps
|
||||
|
||||
// our account is set to be created after 2 vesting periods already passed
|
||||
let vesting_start_blockheight = account_creation_blockheight - 2 * blocks_per_period;
|
||||
let vesting_start_timestamp = account_creation_timestamp - 2 * vesting_period_length_secs;
|
||||
|
||||
let vesting_period2_start_blockheight = vesting_start_blockheight + blocks_per_period;
|
||||
let vesting_period2_start_timestamp = vesting_start_timestamp + vesting_period_length_secs;
|
||||
|
||||
// this vesting period is currently in progress!
|
||||
let vesting_period3_start_blockheight =
|
||||
vesting_period2_start_blockheight + blocks_per_period;
|
||||
let vesting_period3_start_timestamp =
|
||||
vesting_period2_start_timestamp + vesting_period_length_secs;
|
||||
|
||||
// and this one is in the future! (in relation to account creation)
|
||||
let vesting_period4_start_blockheight =
|
||||
vesting_period3_start_blockheight + blocks_per_period;
|
||||
let vesting_period4_start_timestamp =
|
||||
vesting_period3_start_timestamp + vesting_period_length_secs;
|
||||
|
||||
// lets create our vesting account
|
||||
let periods = populate_vesting_periods(
|
||||
vesting_start_timestamp,
|
||||
VestingSpecification::new(None, Some(vesting_period_length_secs), None),
|
||||
);
|
||||
|
||||
let vesting_account = Account::new(
|
||||
Addr::unchecked("owner"),
|
||||
Some(Addr::unchecked("staking")),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
Timestamp::from_seconds(account_creation_timestamp),
|
||||
periods,
|
||||
deps.as_mut().storage,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// time for some delegations
|
||||
|
||||
let mix_identity = "alice".to_string();
|
||||
|
||||
let delegation = Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
};
|
||||
|
||||
// delegate explicitly at the time the account was created
|
||||
// (i.e. after 2 vesting periods already elapsed)
|
||||
env.block.height = account_creation_blockheight;
|
||||
env.block.time = Timestamp::from_seconds(account_creation_timestamp);
|
||||
let ok = vesting_account.try_delegate_to_mixnode(
|
||||
mix_identity.clone(),
|
||||
delegation.clone(),
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let vested_coins = vesting_account
|
||||
.get_vested_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let vesting_coins = vesting_account
|
||||
.get_vesting_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000));
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
|
||||
// all good so far
|
||||
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
|
||||
// some time passes, and we're now into the next vesting period, more of our coins got unlocked!
|
||||
env.block.height = vesting_period4_start_blockheight;
|
||||
env.block.time = Timestamp::from_seconds(vesting_period4_start_timestamp);
|
||||
|
||||
let vested_coins = vesting_account
|
||||
.get_vested_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let vesting_coins = vesting_account
|
||||
.get_vesting_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(375_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(625_000_000_000));
|
||||
|
||||
// and nothing about our existing delegation changed
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
|
||||
// however, create a new delegation now in this brand new vesting period
|
||||
let delegation = Coin {
|
||||
amount: Uint128::new(50_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
};
|
||||
let ok = vesting_account.try_delegate_to_mixnode(
|
||||
mix_identity.clone(),
|
||||
delegation.clone(),
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
// we're still good here, we have delegated in total 140M from our vested tokens!
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(delegated_free.amount, Uint128::new(140_000_000_000));
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
|
||||
// but let's ask now a different question:
|
||||
// how many vested tokens have I had delegated during vesting period3? (i.e. after account creation)
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(
|
||||
Some(Timestamp::from_seconds(vesting_period3_start_timestamp)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(
|
||||
Some(Timestamp::from_seconds(vesting_period3_start_timestamp)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// returns 90M as the 50M delegation didn't exist at this point of time
|
||||
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
|
||||
|
||||
// the 50M delegation wasn't a thing here for VESTING tokens either
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ pub struct Init {
|
||||
mnemonic: Option<String>,
|
||||
|
||||
/// Set this gateway to work in a enabled credentials mode that would disallow clients to bypass bandwidth credential requirement
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
@@ -83,7 +83,7 @@ impl From<Init> for OverrideConfig {
|
||||
validators: init_config.validators,
|
||||
mnemonic: init_config.mnemonic,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
@@ -177,7 +177,7 @@ mod tests {
|
||||
mnemonic: None,
|
||||
statistics_service_url: None,
|
||||
enabled_statistics: None,
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: None,
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: "".to_string(),
|
||||
|
||||
@@ -52,7 +52,7 @@ pub(crate) struct OverrideConfig {
|
||||
validators: Option<String>,
|
||||
mnemonic: Option<String>,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
@@ -118,8 +118,8 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
config = config.with_custom_validator_apis(parse_validators(&raw_validators))
|
||||
}
|
||||
|
||||
if let Some(ref raw_validators) = args.validators {
|
||||
config = config.with_custom_validator_nymd(parse_validators(raw_validators));
|
||||
if let Some(raw_validators) = args.validators {
|
||||
config = config.with_custom_validator_nymd(parse_validators(&raw_validators));
|
||||
} else if std::env::var(CONFIGURED).is_ok() {
|
||||
let raw_validators = std::env::var(NYMD_VALIDATOR).expect("nymd validator not set");
|
||||
config = config.with_custom_validator_nymd(parse_validators(&raw_validators))
|
||||
@@ -146,15 +146,18 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
config = config.with_eth_endpoint(String::from(DEFAULT_ETH_ENDPOINT));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
{
|
||||
if let Some(enabled_credentials_mode) = args.enabled_credentials_mode {
|
||||
config = config.with_disabled_credentials_mode(!enabled_credentials_mode);
|
||||
}
|
||||
// We set the disabled credentials mode flag if we either compile without 'eth', or if there is a flag we
|
||||
// can read from, which is when we build with 'eth' (and without 'coconut').
|
||||
if cfg!(not(feature = "eth")) {
|
||||
config = config.with_disabled_credentials_mode(true);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
{
|
||||
if let Some(enabled_credentials_mode) = args.enabled_credentials_mode {
|
||||
config = config.with_disabled_credentials_mode(enabled_credentials_mode);
|
||||
}
|
||||
|
||||
if let Some(raw_validators) = args.validators {
|
||||
config = config.with_custom_validator_nymd(parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ pub struct Run {
|
||||
mnemonic: Option<String>,
|
||||
|
||||
/// Set this gateway to work in a enabled credentials mode that would disallow clients to bypass bandwidth credential requirement
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
@@ -83,7 +83,7 @@ impl From<Run> for OverrideConfig {
|
||||
validators: run_config.validators,
|
||||
mnemonic: run_config.mnemonic,
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
|
||||
@@ -66,10 +66,6 @@ impl NymConfig for Config {
|
||||
.join("gateways")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("gateways"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.gateway.nym_root_directory.clone()
|
||||
}
|
||||
@@ -127,7 +123,6 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "eth", feature = "coconut"))]
|
||||
pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self {
|
||||
self.gateway.disabled_credentials_mode = disabled_credentials_mode;
|
||||
self
|
||||
|
||||
@@ -96,10 +96,6 @@ impl NymConfig for Config {
|
||||
.join("mixnodes")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("mixnodes"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.mixnode.nym_root_directory.clone()
|
||||
}
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18)
|
||||
|
||||
### Changed
|
||||
|
||||
- nym-connect: "load balance" the service providers by picking a random Service Provider for each Service and storing in local storage so it remains sticky for the user ([#1540])
|
||||
- nym-connect: the ServiceProviderSelector only displays the available Services, and picks a random Service Provider for Services the user has never used before ([#1540])
|
||||
- nym-connect: add `local-forage` for storing user settings ([#1540])
|
||||
|
||||
[#1540]: https://github.com/nymtech/nym/pull/1540
|
||||
|
||||
|
||||
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
|
||||
|
||||
### Added
|
||||
|
||||
Generated
+1
-2
@@ -3404,7 +3404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-connect"
|
||||
version = "1.0.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"client-core",
|
||||
@@ -5227,7 +5227,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"nymsphinx-addressing",
|
||||
"ordered-buffer",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
"react-hook-form": "^7.14.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"semver": "^6.3.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.0.2"
|
||||
version = "1.0.1"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -37,7 +37,9 @@ pub async fn get_config_file_location(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<String> {
|
||||
let id = get_config_id(state).await?;
|
||||
Config::config_file_location(&id).map(|d| d.to_string_lossy().to_string())
|
||||
Ok(Config::config_file_location(&id)
|
||||
.to_string_lossy()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -92,9 +94,8 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config_file_location(id: &str) -> Result<PathBuf> {
|
||||
Socks5Config::try_default_config_file_path(Some(id))
|
||||
.ok_or(BackendError::CouldNotGetFilename)
|
||||
pub fn config_file_location(id: &str) -> PathBuf {
|
||||
Socks5Config::default_config_file_path(Some(id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +107,9 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
|
||||
log::debug!(
|
||||
"Attempting to use config file location: {}",
|
||||
Config::config_file_location(&id)?.to_string_lossy(),
|
||||
Config::config_file_location(&id).to_string_lossy(),
|
||||
);
|
||||
let already_init = Config::config_file_location(&id)?.exists();
|
||||
let already_init = Config::config_file_location(&id).exists();
|
||||
if already_init {
|
||||
log::info!(
|
||||
"SOCKS5 client \"{}\" was already initialised before! \
|
||||
@@ -146,12 +147,12 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
Some(&chosen_gateway_id),
|
||||
config.get_socks5(),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_socks5().get_config_file_save_location();
|
||||
config.get_socks5().save_to_file(None).tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
log::warn!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
log::info!("Saved configuration file to {:?}", config_save_location);
|
||||
@@ -182,7 +183,7 @@ async fn setup_gateway(
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Socks5Config,
|
||||
) -> Result<GatewayEndpoint> {
|
||||
) -> GatewayEndpoint {
|
||||
if register {
|
||||
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||
// the chosen one if it's among the available ones.
|
||||
@@ -200,7 +201,7 @@ async fn setup_gateway(
|
||||
.await;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
gateway.into()
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
@@ -212,20 +213,19 @@ async fn setup_gateway(
|
||||
)
|
||||
.await;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
gateway.into()
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
match Socks5Config::load_from_file(Some(id)) {
|
||||
Ok(existing_config) => Ok(existing_config.get_base().get_gateway_endpoint().clone()),
|
||||
Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
panic!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
Err(BackendError::CouldNotLoadExistingGatewayConfiguration(err))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +52,6 @@ pub enum BackendError {
|
||||
CouldNotInitWithoutServiceProvider,
|
||||
#[error("Could not get file name")]
|
||||
CouldNotGetFilename,
|
||||
#[error("Could not get config file location")]
|
||||
CouldNotGetConfigFilename,
|
||||
#[error("Could not load existing gateway configuration")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
|
||||
@@ -101,7 +101,7 @@ impl State {
|
||||
|
||||
// Setup configuration by writing to file
|
||||
if let Err(err) = self.init_config().await {
|
||||
log::error!("Failed to initialize: {}", err);
|
||||
log::warn!("Failed to initialize: {}", err);
|
||||
|
||||
// Wait a little to give the user some rudimentary feedback that the click actually
|
||||
// registered.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.0.2"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -65,9 +65,9 @@
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "NymConnect",
|
||||
"title": "Nym Connect",
|
||||
"width": 240,
|
||||
"height": 500,
|
||||
"height": 480,
|
||||
"resizable": false
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,56 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
|
||||
const getBusyFillColor = (color: string): string => {
|
||||
if (color === '#60D6EF') {
|
||||
return '#21D072';
|
||||
}
|
||||
return '#60D6EF';
|
||||
};
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
if (isError && hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
if (isError) {
|
||||
return '#40475C';
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case ConnectionStatusKind.disconnected:
|
||||
if (hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
return '#60D6EF';
|
||||
case ConnectionStatusKind.connecting:
|
||||
case ConnectionStatusKind.disconnecting:
|
||||
return '#60D6EF';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return '#DA465B';
|
||||
}
|
||||
return '#21D072';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => {
|
||||
switch (status) {
|
||||
case ConnectionStatusKind.disconnected:
|
||||
return 'Connect';
|
||||
case ConnectionStatusKind.connecting:
|
||||
return 'Connecting';
|
||||
case ConnectionStatusKind.disconnecting:
|
||||
return 'Connected';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return 'Disconnect';
|
||||
}
|
||||
return 'Connected';
|
||||
}
|
||||
};
|
||||
|
||||
export const ConnectionButton: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
disabled?: boolean;
|
||||
@@ -180,3 +130,53 @@ export const ConnectionButton: React.FC<{
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const getBusyFillColor = (color: string): string => {
|
||||
if (color === '#60D6EF') {
|
||||
return '#21D072';
|
||||
}
|
||||
return '#60D6EF';
|
||||
};
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
if (isError && hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
if (isError) {
|
||||
return '#40475C';
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case ConnectionStatusKind.disconnected:
|
||||
if (hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
return '#60D6EF';
|
||||
case ConnectionStatusKind.connecting:
|
||||
case ConnectionStatusKind.disconnecting:
|
||||
return '#60D6EF';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return '#DA465B';
|
||||
}
|
||||
return '#21D072';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => {
|
||||
switch (status) {
|
||||
case ConnectionStatusKind.disconnected:
|
||||
return 'Connect';
|
||||
case ConnectionStatusKind.connecting:
|
||||
return 'Connecting';
|
||||
case ConnectionStatusKind.disconnecting:
|
||||
return 'Connected';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return 'Disconnect';
|
||||
}
|
||||
return 'Connected';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,53 +1,24 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ArrowDropDownCircleIcon from '@mui/icons-material/ArrowDropDownCircle';
|
||||
import { Box, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { ServiceProvider, Service, Services } from '../types/directory';
|
||||
|
||||
type ServiceWithRandomSp = {
|
||||
id: string;
|
||||
description: string;
|
||||
sp: ServiceProvider;
|
||||
};
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
|
||||
export const ServiceProviderSelector: React.FC<{
|
||||
onChange?: (serviceProvider: ServiceProvider) => void;
|
||||
services?: Services;
|
||||
currentSp?: ServiceProvider;
|
||||
}> = ({ services, currentSp, onChange }) => {
|
||||
const [service, setService] = React.useState<Service>();
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>(currentSp);
|
||||
}> = ({ services, onChange }) => {
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
|
||||
const textEl = React.useRef<null | HTMLElement>(null);
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
useEffect(() => {
|
||||
if (!serviceProvider && currentSp) {
|
||||
setServiceProvider(currentSp);
|
||||
}
|
||||
}, [currentSp]);
|
||||
|
||||
useEffect(() => {
|
||||
if (services && serviceProvider) {
|
||||
// retrieve the service corresponding to this service provider
|
||||
setService(
|
||||
services.find((s) =>
|
||||
s.items.some(
|
||||
({ id, address, gateway }) =>
|
||||
id === serviceProvider.id && address === serviceProvider.address && gateway === serviceProvider.gateway,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}, [serviceProvider, services]);
|
||||
|
||||
const handleClick = () => {
|
||||
setAnchorEl(textEl.current);
|
||||
};
|
||||
const handleClose = (newServiceProvider?: ServiceProvider) => {
|
||||
if (newServiceProvider && newServiceProvider !== currentSp) {
|
||||
if (newServiceProvider) {
|
||||
setServiceProvider(newServiceProvider);
|
||||
onChange?.(newServiceProvider);
|
||||
}
|
||||
@@ -68,16 +39,6 @@ export const ServiceProviderSelector: React.FC<{
|
||||
);
|
||||
}
|
||||
|
||||
const servicesWithRandomSp: ServiceWithRandomSp[] = useMemo(
|
||||
() =>
|
||||
services.map(({ id, items, description }) => ({
|
||||
id,
|
||||
description,
|
||||
sp: items[Math.floor(Math.random() * items.length)],
|
||||
})),
|
||||
[services],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
|
||||
@@ -87,7 +48,7 @@ export const ServiceProviderSelector: React.FC<{
|
||||
fontWeight={700}
|
||||
color={(theme) => (serviceProvider ? undefined : theme.palette.primary.main)}
|
||||
>
|
||||
{service ? service.description : 'Select a service'}
|
||||
{serviceProvider ? serviceProvider.description : 'Select a service'}
|
||||
</Typography>
|
||||
<IconButton
|
||||
id="service-provider-button"
|
||||
@@ -104,46 +65,44 @@ export const ServiceProviderSelector: React.FC<{
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={() => handleClose()}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'service-provider-button',
|
||||
sx: {
|
||||
minWidth: 160,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{servicesWithRandomSp.map(({ id, description, sp }) => (
|
||||
<MenuItem dense key={id} sx={{ fontSize: 'small', fontWeight: 'bold' }} onClick={() => handleClose(sp)}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Stack direction="column">
|
||||
<Typography fontSize="inherit">
|
||||
<code>{sp.id}</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit" fontWeight={700}>
|
||||
{services.map((service) => (
|
||||
<>
|
||||
<MenuItem disabled dense sx={{ fontSize: 'small', fontWeight: 'bold', mb: -1 }}>
|
||||
{service.description}
|
||||
</MenuItem>
|
||||
{service.items.map((sp) => (
|
||||
<MenuItem dense sx={{ fontSize: 'small', ml: 2, height: 'auto' }} onClick={() => handleClose(sp)}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Stack direction="column">
|
||||
<Typography fontSize="inherit">
|
||||
<code>{sp.id}</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit" fontWeight={700}>
|
||||
{sp.description}
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Gateway <code>{sp.gateway.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Provider <code>{sp.address.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
arrow
|
||||
placement="top"
|
||||
>
|
||||
<Typography fontSize="inherit" noWrap>
|
||||
{sp.description}
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Gateway <code>{sp.gateway.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Provider <code>{sp.address.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
arrow
|
||||
placement="top"
|
||||
>
|
||||
<Typography>{description}</Typography>
|
||||
</Tooltip>
|
||||
</MenuItem>
|
||||
</Tooltip>
|
||||
</MenuItem>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</Menu>
|
||||
</>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
@@ -37,7 +36,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.disconnected);
|
||||
const [connectionStats, setConnectionStats] = useState<ConnectionStatsItem[]>();
|
||||
const [connectedSince, setConnectedSince] = useState<DateTime>();
|
||||
const [services, setServices] = React.useState<Services>([]);
|
||||
const [services, setServices] = React.useState<Services>();
|
||||
const [serviceProvider, setRawServiceProvider] = React.useState<ServiceProvider>();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -73,71 +72,33 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
await invoke('start_disconnecting');
|
||||
}, []);
|
||||
|
||||
const setSpInStorage = async (sp: ServiceProvider) => {
|
||||
await forage.setItem({
|
||||
key: 'nym-connect-sp',
|
||||
value: sp,
|
||||
} as any)();
|
||||
};
|
||||
|
||||
const setServiceProvider = useCallback(async (newServiceProvider: ServiceProvider) => {
|
||||
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
await setSpInStorage(newServiceProvider);
|
||||
setRawServiceProvider(newServiceProvider);
|
||||
}, []);
|
||||
|
||||
const getSpFromStorage = async () => {
|
||||
try {
|
||||
const spFromStorage = await forage.getItem({ key: 'nym-connect-sp' })();
|
||||
if (spFromStorage) {
|
||||
setRawServiceProvider(spFromStorage);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const validityCheck = async () => {
|
||||
if (services.length > 0 && serviceProvider) {
|
||||
const isValid = services.some(({ items }) => items.some(({ id }) => id === serviceProvider.id));
|
||||
if (!isValid) {
|
||||
console.warn('invalid SP, cleaning local storage');
|
||||
await forage.removeItem({
|
||||
key: 'nym-connect-sp',
|
||||
})();
|
||||
setRawServiceProvider(undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
validityCheck();
|
||||
}, [services, serviceProvider]);
|
||||
|
||||
useEffect(() => {
|
||||
getSpFromStorage();
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
mode,
|
||||
setMode,
|
||||
connectionStatus,
|
||||
setConnectionStatus,
|
||||
connectionStats,
|
||||
setConnectionStats,
|
||||
connectedSince,
|
||||
setConnectedSince,
|
||||
startConnecting,
|
||||
startDisconnecting,
|
||||
services,
|
||||
serviceProvider,
|
||||
setServiceProvider,
|
||||
}),
|
||||
[mode, connectedSince, connectionStatus, connectionStats, connectedSince, services, serviceProvider],
|
||||
return (
|
||||
<ClientContext.Provider
|
||||
value={{
|
||||
mode,
|
||||
setMode,
|
||||
connectionStatus,
|
||||
setConnectionStatus,
|
||||
connectionStats,
|
||||
setConnectionStats,
|
||||
connectedSince,
|
||||
setConnectedSince,
|
||||
startConnecting,
|
||||
startDisconnecting,
|
||||
services,
|
||||
serviceProvider,
|
||||
setServiceProvider,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ClientContext.Provider>
|
||||
);
|
||||
|
||||
return <ClientContext.Provider value={contextValue}>{children}</ClientContext.Provider>;
|
||||
};
|
||||
|
||||
export const useClientContext = () => useContext(ClientContext);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ConnectionStatusKind } from '../types';
|
||||
import { NeedHelp } from '../components/NeedHelp';
|
||||
import { ServiceProviderSelector } from '../components/ServiceProviderSelector';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
import { useClientContext } from '../context/main';
|
||||
|
||||
export const DefaultLayout: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
@@ -21,8 +20,6 @@ export const DefaultLayout: React.FC<{
|
||||
setServiceProvider(newServiceProvider);
|
||||
onServiceProviderChange?.(newServiceProvider);
|
||||
};
|
||||
const { serviceProvider: currentSp } = useClientContext();
|
||||
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<Typography fontWeight="400" fontSize="12px" textAlign="center" sx={{ opacity: 0.6 }}>
|
||||
@@ -34,10 +31,10 @@ export const DefaultLayout: React.FC<{
|
||||
<br />
|
||||
Nym mixnet for privacy.
|
||||
</Typography>
|
||||
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} currentSp={currentSp} />
|
||||
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} />
|
||||
<ConnectionButton
|
||||
status={status}
|
||||
disabled={serviceProvider === undefined && currentSp === undefined}
|
||||
disabled={serviceProvider === undefined}
|
||||
busy={busy}
|
||||
isError={isError}
|
||||
onClick={onConnectClick}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Specify filenames and other platform specific constants to respect platform conventions, or at
|
||||
// least, something popular on each respective platform.
|
||||
|
||||
pub const CONFIG_DIR_NAME: &str = "nym-wallet";
|
||||
pub const CONFIG_FILENAME: &str = "config.toml";
|
||||
pub const STORAGE_DIR_NAME: &str = "nym-wallet";
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"accounts": [
|
||||
{
|
||||
"id": "default",
|
||||
"account": {
|
||||
"ciphertext": "cq5w4W5ex5eFFRcqLG+824XyUAUoYmrRY3NGw/rue6/mLoQKQE/07+BxzRuKjyYFasC1HBPg41KJwp2IY+/7+80rB9aXPpaKVLUcG9U40qgCw66WhgxTrXOnrt5toefpSTBL7f9N/PVwpuumfAgD9CS0ioB7/9Qoea7nYKkextGX15ex26B/ndQddvUkQ4gx+Vq7OLymv4l+nkdZ2nKMja349zd/BjnzPBB68/iIjyYlivVjtQ7FRbvpNRj6Mjg4905wGlO7bTpkw+RGiaGK4pK8fTWz8gAKr8GYoXPD",
|
||||
"salt": "SPVGdbVyoEayD4ZzM4I+Jg==",
|
||||
"iv": "tjpn/tRjD1gty+fQ"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"accounts": [
|
||||
{
|
||||
"id": "default",
|
||||
"account": {
|
||||
"ciphertext": "3MDgoU2i5QMc9r80yPeq2AMk5wpkke0tXum5NsOE5NcFciF+aHLQW0dvXbGszap1y3nN4+YZD3cgGrmtKh/cChqRGJDkniaxdf3XPHh9RkiWXw2KSHHeyGrFY0INJeiky1ZtUFhWhopcHJWSnfCmVC15YFpnM5xOKpITjHAFhGt98MaYR+mS+3zoUFrjYbaZRh2TR2lFWsbR8YU1uaTYqJZ1HX1PBCub6aS3vjQm0Fwa+hAtR/gMymXJc5qtruTO4NbqYtMj3Z9eIgoVB+56SLAXlIF1Uo1pjvV0mx1hWNNiIc10ujF/wl/nnKF6icOcmrfm9XhOtsvUYBsE/wAIJZw3LKXgSX+hJbOl+zLAwJZK1xiL8n/nM1IJZDn+Wu6z0OzRaj9S7T16+brMw1oaqjk56saM8n5z725fizJj+ur6gnPBWnoyHPaCHgHdB2PKQNY0ZlwRM6dVncRaEWQDLAboyMq3FXxK9UbusNcDFpYw6bdnuJlNVf6y9yxwyvkUrt5YtgfkLyoW42z1PVtVWsV9P8eE/A/tnYjXf34xvba3K8Y1/3DTi7uuydNrSR/XhA+pevz68VWCbY+j746Yi8Lz7altePphkjfJAezodobKvMplXzqInopIWNovyemw/+1E7WZbkQIOAXg1WC1+Y/df+dffRGuGRdDerfRLmA5XLej1M/wE3WQ7b9KwlAo6XJ4hnQKwyDCqYP/ButBXW1AOnnZpCq59gGbiccZJsTMZB4OP95yFPgz8//IeDgma2PDixVmDEp0SGHhN7dlSoNa5eoglblqzJu/TcTA6jmQFA3ef0GiA3QzBjmyB4bz0bFybh8XA1brVIVlsjRwXb3/UYaVqsP6Hy1QDUpZofXIJs5lK0hUd0ECdaNFXXgHd25ifPocp09WLFyK92H6i3ABDZ7pu3b4lTUt6kHt6LTVsKkyylmYf2iMHnCcmfy4uxGTXxRjPjMgKL8pd++OZ3q62jLBuoTjgdj6pccwDvD+NYQ2FFeHmBzxyTLqUyKltYiyFlJHWLKOcXyeDHzRhHic+e/wn3VhM3NdrvtqYWA9m72Ye1L1I7VX7KatGurG6CeiFiY5xHxxpLT7dF0fJ7uxRye4JnRyYQuU7iK72qCKjgYjwjCIha4qPi5Q/x6S+uVe7yX5Eb73L3eB+IlkyW9wPHmSOcE4GpbMU96tK8xoxT0T9eQlj050GDnJ/oI2XHfZTs1bIxsjfZqW03g==",
|
||||
"salt": "wXR3RnPmsoA3ncrixIvaUw==",
|
||||
"iv": "/Zjn1OXsLJhA43n/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"accounts": [
|
||||
{
|
||||
"id": "first",
|
||||
"account": {
|
||||
"ciphertext": "icnpxLmr/H7FIIOaEf7DYNLuM6uhh7poEppXpYCllQD33TjY+8eLtVvhEQmjX60IQeFOd+1JCcrHa2B12vlBAYlfM4gBxA6d2ZJ8+Dw/vNvBNyChiyUx2euV3vPGOs22r/XDBsmEeF40XZcXftQZa2kzYaPnkbP+eiMOIWkcY4FYOEHwx5SxT4VBPZIrVTC3iDalJLWybVbbw/Bc2zbzEXI1ckg4Ccydj95SMil9BiyDpALfZqwlai7I97S+BjmcVxSCsYqFjTkRUHVMjrEr7fWHKU4DIOM=",
|
||||
"salt": "CtnbfkxTybqz0U4cPHW2jQ==",
|
||||
"iv": "77ZROU6dAMttEWwS"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "second",
|
||||
"account": {
|
||||
"ciphertext": "nsqZHdQFlskglc5izKgnr8sBwdMmd82h2Rnjdos9EUca3cqkUdFYEjZDsK8OGR3GZ9alLTNt/1U97Rvvr2HPAWbzl23FW2YXaLTA6yj6ZwQK5w0MYE061NYbcxNHuzT9f5aQWkGULAk4RWb5t8eUX7y/NdJr3tA5xuGOLhooTfBB98/4RpupDsYGZp1DPC/GMFppOA3GmKs9bacZm805Bhfq5mwhXab1SjJQpFHMHisCMhxo/oLqulKML1tQMetBdqDTjJmPpdUnd1mi",
|
||||
"salt": "J2TMLjKv4dkZ/kXso9FGhg==",
|
||||
"iv": "CTqqoMa4LetvBKCP"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { simulateBondGateway, simulateVestingBondGateway } from 'src/requests';
|
||||
import { TBondGatewayArgs } from 'src/types';
|
||||
import { BondGatewayForm } from '../forms/BondGatewayForm';
|
||||
|
||||
const defaultGatewayValues: GatewayData = {
|
||||
const defaultMixnodeValues: GatewayData = {
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
ownerSignature: '',
|
||||
@@ -19,7 +19,7 @@ const defaultGatewayValues: GatewayData = {
|
||||
host: '',
|
||||
version: '',
|
||||
mixPort: 1789,
|
||||
clientsPort: 9000,
|
||||
clientsPort: 1790,
|
||||
};
|
||||
|
||||
const defaultAmountValues = (denom: CurrencyDenom) => ({
|
||||
@@ -43,7 +43,7 @@ export const BondGatewayModal = ({
|
||||
onError: (e: string) => void;
|
||||
}) => {
|
||||
const [step, setStep] = useState<1 | 2 | 3>(1);
|
||||
const [gatewayData, setGatewayData] = useState<GatewayData>(defaultGatewayValues);
|
||||
const [gatewayData, setGatewayData] = useState<GatewayData>(defaultMixnodeValues);
|
||||
const [amountData, setAmountData] = useState<GatewayAmount>(defaultAmountValues(denom));
|
||||
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
|
||||
@@ -28,7 +28,7 @@ type ClientAddressProps = {
|
||||
showEntireAddress?: boolean;
|
||||
};
|
||||
|
||||
export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }> = ({
|
||||
export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string } > = ({
|
||||
withLabel,
|
||||
withCopy,
|
||||
showEntireAddress,
|
||||
@@ -44,7 +44,7 @@ export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }>
|
||||
)}
|
||||
|
||||
<AddressTooltip address={address} visible={!showEntireAddress}>
|
||||
<Typography variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
|
||||
<Typography data-testid="accountNumber" variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
|
||||
{showEntireAddress ? address || '' : splice(6, address)}
|
||||
</Typography>
|
||||
</AddressTooltip>
|
||||
|
||||
@@ -37,6 +37,8 @@ export const CopyToClipboard = ({ text = '', iconButton }: { text?: string; icon
|
||||
}}
|
||||
>
|
||||
{!copied ? <ContentCopy sx={{ fontSize: 14 }} /> : <Check color="success" sx={{ fontSize: 14 }} />}
|
||||
{!copied ? <ContentCopy data-testid="copyIcon"
|
||||
fontSize="small" /> : <Check color="success" />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -18,10 +18,11 @@ export const Mnemonic = ({
|
||||
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
|
||||
</Typography>
|
||||
</Warning>
|
||||
<TextField multiline rows={3} value={mnemonic} fullWidth />
|
||||
<TextField multiline rows={3} value={mnemonic} fullWidth data-testid="mnemonicPhrase"/>
|
||||
|
||||
<Button
|
||||
color="inherit"
|
||||
data-testid="copyMnemonic"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
|
||||
@@ -56,7 +56,7 @@ export const ConfirmationModal = ({
|
||||
const ConfirmButton =
|
||||
typeof confirmButton === 'string' ? (
|
||||
<Button onClick={onConfirm} variant="contained" fullWidth disabled={disabled} sx={{ py: 1.6 }}>
|
||||
<Typography variant="button" fontSize="large">
|
||||
<Typography variant="button" fontSize="large" data-testid={confirmButton}>
|
||||
{confirmButton}
|
||||
</Typography>
|
||||
</Button>
|
||||
|
||||
@@ -16,7 +16,7 @@ const NetworkItem: React.FC<{ title: string; isSelected: boolean; onSelect: () =
|
||||
isSelected,
|
||||
onSelect,
|
||||
}) => (
|
||||
<ListItem button onClick={onSelect}>
|
||||
<ListItem button onClick={onSelect} data-testid={title}>
|
||||
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
|
||||
<ListItemText>{title}</ListItemText>
|
||||
</ListItem>
|
||||
@@ -38,6 +38,7 @@ export const NetworkSelector = () => {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
data-testid="networkEnv"
|
||||
variant="text"
|
||||
color="inherit"
|
||||
sx={{ color: 'text.primary' }}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const NymCard: React.FC<{
|
||||
{noPadding ? (
|
||||
<CardContentNoPadding>{children}</CardContentNoPadding>
|
||||
) : (
|
||||
<CardContent sx={{ p: 3 }}>{children}</CardContent>
|
||||
<CardContent sx={{ p: 3 }} >{children}</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -62,6 +62,9 @@ export const SendInputModal = ({
|
||||
fullWidth
|
||||
onChange={(e) => onAddressChange(e.target.value)}
|
||||
value={toAddress}
|
||||
inputProps={{
|
||||
"data-testid": "recipientAddress",
|
||||
}}
|
||||
/>
|
||||
<CurrencyFormField
|
||||
placeholder="Amount"
|
||||
@@ -73,7 +76,7 @@ export const SendInputModal = ({
|
||||
initialValue={amount?.amount}
|
||||
denom={denom}
|
||||
/>
|
||||
<Typography fontSize="smaller" sx={{ color: 'error.main' }}>
|
||||
<Typography fontSize="smaller" sx={{ color: 'error.main' }} >
|
||||
{error}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
@@ -31,7 +31,7 @@ export const SendSuccessModal = ({
|
||||
{txDetails && (
|
||||
<>
|
||||
<Typography variant="h5">{txDetails.amount}</Typography>
|
||||
<Link href={txDetails.txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />
|
||||
<Link href={txDetails.txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" data-testid="viewOnBlockchain"/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -13,6 +13,9 @@ export const MnemonicInput: React.FC<{
|
||||
<Stack spacing={2}>
|
||||
<TextField
|
||||
label="Mnemonic"
|
||||
inputProps={{
|
||||
"data-testid": "mnemonicInput",
|
||||
}}
|
||||
type={showPassword ? 'input' : 'password'}
|
||||
value={mnemonic}
|
||||
onChange={(e) => onUpdateMnemonic(e.target.value)}
|
||||
@@ -63,6 +66,9 @@ export const PasswordInput: React.FC<{
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
inputProps={{
|
||||
"data-testid": label,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{error && <Error message={error} />}
|
||||
|
||||
@@ -118,6 +118,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
stakeSaturation: 0,
|
||||
numberOfDelegators: 0,
|
||||
};
|
||||
|
||||
try {
|
||||
const statusResponse = await getMixnodeStatus(identityKey);
|
||||
additionalDetails.status = statusResponse.status;
|
||||
@@ -162,7 +163,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
try {
|
||||
operatorRewards = await getOperatorRewards(clientDetails?.client_address);
|
||||
} catch (e) {
|
||||
Console.warn(`get_operator_rewards request failed: ${e}`);
|
||||
console.warn(`get_operator_rewards request failed: ${e}`);
|
||||
}
|
||||
if (data) {
|
||||
const { status, stakeSaturation, numberOfDelegators } = await getAdditionalMixnodeDetails(
|
||||
@@ -185,7 +186,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
} as TBondedMixnode);
|
||||
}
|
||||
} catch (e: any) {
|
||||
Console.warn(e);
|
||||
console.warn(e);
|
||||
setError(`While fetching current bond state, an error occurred: ${e}`);
|
||||
}
|
||||
}
|
||||
@@ -206,7 +207,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
} as TBondedGateway);
|
||||
}
|
||||
} catch (e: any) {
|
||||
Console.warn(e);
|
||||
setError(`While fetching current bond state, an error occurred: ${e}`);
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
}
|
||||
return tx;
|
||||
} catch (e: any) {
|
||||
Console.warn(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -257,7 +256,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
}
|
||||
return tx;
|
||||
} catch (e: any) {
|
||||
Console.warn(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -274,7 +272,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
if (bondedNode && isGateway(bondedNode) && bondedNode.proxy) tx = await vestingUnbondGateway(fee?.fee);
|
||||
if (bondedNode && isGateway(bondedNode) && !bondedNode.proxy) tx = await unbondGatewayRequest(fee?.fee);
|
||||
} catch (e) {
|
||||
Console.warn(e);
|
||||
setError(`an error occurred: ${e as string}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -289,7 +286,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
if (bondedNode?.proxy) tx = await updateMixnodeVestingRequest(pm, fee?.fee);
|
||||
else tx = await updateMixnodeRequest(pm, fee?.fee);
|
||||
} catch (e: any) {
|
||||
Console.warn(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -6,7 +6,7 @@ export const Title = ({ title }: { title: string }) => (
|
||||
);
|
||||
|
||||
export const Subtitle = ({ subtitle }: { subtitle: string }) => (
|
||||
<Typography sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
|
||||
<Typography data-testid={subtitle} sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
|
||||
);
|
||||
|
||||
export const SubtitleSlick = ({ subtitle }: { subtitle: string }) => (
|
||||
|
||||
@@ -53,7 +53,8 @@ export const WordTiles = ({
|
||||
return (
|
||||
<Grid container spacing={3} justifyContent="center">
|
||||
{mnemonicWords.map(({ name, index, disabled }) => (
|
||||
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })}>
|
||||
<Grid
|
||||
item xs={2} key={index} onClick={() => onClick?.({ name, index })} data-testid="mnemonicWordTile">
|
||||
<WordTile
|
||||
mnemonicWord={name}
|
||||
index={showIndex ? index : undefined}
|
||||
@@ -79,7 +80,7 @@ const HiddenWord = ({ mnemonicWord }: { mnemonicWord: THiddenMnemonicWord }) =>
|
||||
</Box>
|
||||
</Fade>
|
||||
</Box>
|
||||
<Typography>{mnemonicWord.index}.</Typography>
|
||||
<Typography data-testid="wordIndex">{mnemonicWord.index}.</Typography>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export const ConfirmMnemonic = () => {
|
||||
<Subtitle subtitle="Enter the mnemonic you wish to create a password for" />
|
||||
<MnemonicInput mnemonic={localMnemonic} onUpdateMnemonic={(mnc) => setLocalMnemonic(mnc)} error={error} />
|
||||
<Button
|
||||
data-testid="nextToPasswordCreation"
|
||||
size="large"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
@@ -37,6 +38,7 @@ export const ConfirmMnemonic = () => {
|
||||
Next
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="backToMnemonicSignIn"
|
||||
size="large"
|
||||
color="inherit"
|
||||
fullWidth
|
||||
|
||||
@@ -57,6 +57,7 @@ export const ConnectPassword = () => {
|
||||
label="Confirm password"
|
||||
/>
|
||||
<Button
|
||||
data-testid="createPasswordButton"
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
|
||||
@@ -65,6 +66,7 @@ export const ConnectPassword = () => {
|
||||
{isLoading ? <CircularProgress size={25} /> : 'Create password'}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="backToStep1PasswordCreation"
|
||||
size="large"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
|
||||
@@ -23,6 +23,7 @@ export const CreateMnemonic = () => {
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
data-testid="iSavedMnemonic"
|
||||
color="primary"
|
||||
disableElevation
|
||||
size="large"
|
||||
@@ -33,6 +34,7 @@ export const CreateMnemonic = () => {
|
||||
I saved my mnemonic
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="backToWelcome"
|
||||
onClick={() => {
|
||||
resetState();
|
||||
navigate(-1);
|
||||
|
||||
@@ -57,13 +57,14 @@ export const CreatePassword = () => {
|
||||
/>
|
||||
<Button
|
||||
size="large"
|
||||
data-testid="nextStorePassword"
|
||||
variant="contained"
|
||||
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
|
||||
onClick={storePassword}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
<Button size="large" color="info" onClick={handleSkip}>
|
||||
<Button size="large" color="info" onClick={handleSkip} data-testid="skipPasswordAndSignInWithMnemonic">
|
||||
Skip and sign in with mnemonic
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -11,18 +11,18 @@ export const ExistingAccount = () => {
|
||||
<Title title="Welcome to Nym" />
|
||||
<SubtitleSlick subtitle="NEXT GENERATION OF PRIVACY" />
|
||||
<Stack spacing={2} sx={{ width: 300 }}>
|
||||
<Button variant="contained" size="large" onClick={() => navigate('/sign-in-mnemonic')} fullWidth>
|
||||
<Button variant="contained" size="large" onClick={() => navigate('/sign-in-mnemonic')} fullWidth data-testid="signInWithMnemonic">
|
||||
Sign in with mnemonic
|
||||
</Button>
|
||||
<Typography sx={{ textAlign: 'center', fontWeight: 600 }}>or</Typography>
|
||||
<Button variant="contained" size="large" fullWidth onClick={() => navigate('/sign-in-password')}>
|
||||
<Button variant="contained" size="large" fullWidth onClick={() => navigate('/sign-in-password')} data-testid="signInWithPassword">
|
||||
Sign in with password
|
||||
</Button>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Button color="inherit" onClick={() => navigate('/')}>
|
||||
<Button color="inherit" onClick={() => navigate('/')} data-testid="backToWelcomePage">
|
||||
Back
|
||||
</Button>
|
||||
<Button color="info" onClick={() => navigate('/forgot-password')}>
|
||||
<Button color="info" onClick={() => navigate('/forgot-password')} data-testid="forgotPassword">
|
||||
Forgot password?
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -39,15 +39,15 @@ export const SignInMnemonic = () => {
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
|
||||
<Button variant="contained" size="large" fullWidth type="submit">
|
||||
<Button variant="contained" size="large" fullWidth type="submit" data-testid="signInSubmitButton">
|
||||
Sign in with mnemonic
|
||||
</Button>
|
||||
<Box display="flex" justifyContent={passwordExists ? 'center' : 'space-between'}>
|
||||
<Button color="inherit" onClick={() => handlePageChange(-1)}>
|
||||
<Button color="inherit" onClick={() => handlePageChange(-1)} data-testid="backToSignInOptions">
|
||||
Back
|
||||
</Button>
|
||||
{!passwordExists && (
|
||||
<Button color="info" onClick={() => handlePageChange('/confirm-mnemonic')}>
|
||||
<Button color="info" onClick={() => handlePageChange('/confirm-mnemonic')} data-testid="goToCreatePassword">
|
||||
Create a password
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -29,6 +29,7 @@ export const SignInPassword = () => {
|
||||
autoFocus
|
||||
/>
|
||||
<Button
|
||||
data-testid="signInPasswordButton"
|
||||
variant="contained"
|
||||
size="large"
|
||||
fullWidth
|
||||
@@ -38,6 +39,7 @@ export const SignInPassword = () => {
|
||||
</Button>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Button
|
||||
data-testid="backToSignInOptionsFromPassword"
|
||||
color="inherit"
|
||||
disableElevation
|
||||
onClick={() => {
|
||||
@@ -49,6 +51,7 @@ export const SignInPassword = () => {
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
data-testid="forgotPasswordButton"
|
||||
color="info"
|
||||
onClick={() => {
|
||||
setError(undefined);
|
||||
|
||||
@@ -52,6 +52,7 @@ export const VerifyMnemonic = () => {
|
||||
<Stack spacing={3} sx={{ width: 300 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
data-testid="nextToStep3"
|
||||
fullWidth
|
||||
size="large"
|
||||
disabled={currentSelection !== numberOfRandomWords}
|
||||
@@ -59,7 +60,7 @@ export const VerifyMnemonic = () => {
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
<Button color="inherit" fullWidth size="large" onClick={() => navigate(-1)}>
|
||||
<Button color="inherit" fullWidth size="large" onClick={() => navigate(-1)} data-testid="backToStep1">
|
||||
Back
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -19,6 +19,7 @@ export const WelcomeContent: React.FC<{}> = () => {
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={() => navigate('/existing-account')}
|
||||
data-testid="signIn"
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
@@ -29,6 +30,7 @@ export const WelcomeContent: React.FC<{}> = () => {
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => navigate('/create-mnemonic')}
|
||||
data-testid="createAccount"
|
||||
>
|
||||
Create account
|
||||
</Button>
|
||||
|
||||
@@ -14,9 +14,8 @@ export const BalanceCard = () => {
|
||||
return (
|
||||
<NymCard
|
||||
title="Balance"
|
||||
data-testid="check-balance"
|
||||
borderless
|
||||
Action={<ClientAddress withCopy showEntireAddress />}
|
||||
Action={<ClientAddress withCopy showEntireAddress/>}
|
||||
>
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
@@ -27,7 +26,7 @@ export const BalanceCard = () => {
|
||||
)}
|
||||
{!userBalance.error && (
|
||||
<Typography
|
||||
data-testid="refresh-success"
|
||||
data-testid="nym-balance"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
textTransform: 'uppercase',
|
||||
|
||||
@@ -30,7 +30,7 @@ export const Bond = () => {
|
||||
<NymCard title="Bond" subheader="Bond a mixnode or gateway" noPadding>
|
||||
{status === EnumRequestStatus.initial && (
|
||||
<Box sx={{ px: 3, mb: 1 }}>
|
||||
<Alert severity="warning">Always ensure you leave yourself enough funds to UNBOND</Alert>
|
||||
<Alert severity="warning" data-testid="fundsAlert">Always ensure you leave yourself enough funds to UNBOND</Alert>
|
||||
</Box>
|
||||
)}
|
||||
{ownership?.hasOwnership && (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { Bond } from 'src/components/Bonding/Bond';
|
||||
@@ -16,8 +16,9 @@ import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs } from 'src/ty
|
||||
import { BondedGateway } from 'src/components/Bonding/BondedGateway';
|
||||
import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal';
|
||||
import { CompoundRewardsModal } from 'src/components/Bonding/modals/CompoundRewardsModal';
|
||||
import { Box } from '@mui/material';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { BondingContextProvider, useBondingContext } from '../../context';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
const Bonding = () => {
|
||||
const [showModal, setShowModal] = useState<
|
||||
@@ -41,31 +42,19 @@ const Bonding = () => {
|
||||
compoundRewards,
|
||||
isLoading,
|
||||
checkOwnership,
|
||||
error,
|
||||
} = useBondingContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
setShowModal(undefined);
|
||||
setConfirmationDetails({
|
||||
status: 'error',
|
||||
title: 'An error occurred',
|
||||
subtitle: error,
|
||||
});
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const handleCloseModal = async () => {
|
||||
setShowModal(undefined);
|
||||
await checkOwnership();
|
||||
};
|
||||
|
||||
const handleError = (e: string) => {
|
||||
const handleError = (error: string) => {
|
||||
setShowModal(undefined);
|
||||
setConfirmationDetails({
|
||||
status: 'error',
|
||||
title: 'An error occurred',
|
||||
subtitle: e,
|
||||
subtitle: error,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user