Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a06d25a866 | |||
| 56a5d847c4 | |||
| 096a599673 | |||
| 41da67ad6f | |||
| 273a741cdf | |||
| b5c8b69547 | |||
| 5bd87bdaa8 | |||
| 7d64618701 | |||
| 4151c65251 | |||
| ccb48f92cd | |||
| 8fce478a1f | |||
| 4f712ad4ba | |||
| 562fd44a30 | |||
| 603e897e2d | |||
| 6c122aad10 | |||
| b4ac601a82 | |||
| ce380a6b0d | |||
| 5b02801c04 | |||
| 29f95febe9 | |||
| 525372d7ac | |||
| e473a05250 | |||
| a55d604bf5 | |||
| 5075894ff5 | |||
| c392266a4c | |||
| 9b540936db | |||
| 7bf1036b68 | |||
| 1d82ec56d8 | |||
| 3ae5b59141 | |||
| 929b401f95 | |||
| d158deba77 | |||
| 5c77b48708 | |||
| 73d53653db | |||
| 74bedead20 | |||
| 1bec95d2a1 | |||
| 30137e285d | |||
| 5253d5ec51 | |||
| 7ed7329917 | |||
| 4abc0ae0ba | |||
| 47d8fcb21a | |||
| acef5a5652 | |||
| cb919a3af9 | |||
| 8e021a4419 | |||
| e5db7cb915 | |||
| 9bd03af3e9 | |||
| e8a026ef0b | |||
| 86755aa6ba | |||
| 77e0c6425e | |||
| 0373e2b02a | |||
| e8dd347186 | |||
| 5681920092 | |||
| edbf35cb34 | |||
| bc0f0cfc55 | |||
| 2661e4539e | |||
| 2fe5a5249c | |||
| c5b678d4e9 | |||
| 487f07ef35 | |||
| 6a63be63e9 | |||
| 552c99389e | |||
| 55502d210c | |||
| 66967ca88e | |||
| ba6d02b1db | |||
| ebc957c04d | |||
| 81c3ad74ca | |||
| 88f57e6d87 | |||
| 28a965e698 | |||
| 029c445805 | |||
| 358c95d541 | |||
| 0f7793a881 | |||
| 5478c23563 | |||
| 9f39e93e7b |
@@ -9,12 +9,14 @@ on:
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/lib/socks5-listener/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
- 'Cargo.toml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
@@ -23,12 +25,14 @@ on:
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/lib/socks5-listener/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
- 'Cargo.toml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
.idea
|
||||
target
|
||||
.env
|
||||
.env.dev
|
||||
/.vscode/settings.json
|
||||
validator/.vscode
|
||||
sample-configs/validator-config.toml
|
||||
|
||||
@@ -4,6 +4,18 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.19] (2023-05-16)
|
||||
|
||||
- nym-name-service endpoint in nym-api ([#3403])
|
||||
- Implement key storage for WASM client using IndexedDB (for browser) ([#3329])
|
||||
- Initial version of nym-name-service contract providing name aliases for nym-addresses ([#3274])
|
||||
- Update Cargo.lock ([#3410])
|
||||
|
||||
[#3403]: https://github.com/nymtech/nym/issues/3403
|
||||
[#3329]: https://github.com/nymtech/nym/issues/3329
|
||||
[#3274]: https://github.com/nymtech/nym/issues/3274
|
||||
[#3410]: https://github.com/nymtech/nym/pull/3410
|
||||
|
||||
## [v1.1.18] (2023-05-09)
|
||||
|
||||
- Implement heartbeat messages between socks5 proxy and network requester ([#3215])
|
||||
|
||||
Generated
+3462
-159
File diff suppressed because it is too large
Load Diff
+17
-10
@@ -78,6 +78,7 @@ members = [
|
||||
"gateway/gateway-requests",
|
||||
"integrations/bity",
|
||||
"mixnode",
|
||||
"sdk/lib/socks5-listener",
|
||||
"sdk/rust/nym-sdk",
|
||||
"service-providers/common",
|
||||
"service-providers/network-requester",
|
||||
@@ -112,19 +113,24 @@ license = "Apache-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
async-trait = "0.1.64"
|
||||
anyhow = "1.0.71"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
cfg-if = "1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-utils = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw2 = { version = "=0.13.4" }
|
||||
cw3 = { version = "=0.13.4" }
|
||||
cw3-fixed-multisig = { version = "=0.13.4" }
|
||||
cw4 = { version = "=0.13.4" }
|
||||
cosmwasm-derive = "=1.2.5"
|
||||
cosmwasm-schema = "=1.2.5"
|
||||
cosmwasm-std = "=1.2.5"
|
||||
cosmwasm-storage = "=1.2.5"
|
||||
cosmrs = "=0.8.0"
|
||||
cw-utils = "=1.0.1"
|
||||
cw-storage-plus = "=1.0.1"
|
||||
cw2 = { version = "=1.0.1" }
|
||||
cw3 = { version = "=1.0.1" }
|
||||
cw3-fixed-multisig = { version = "=1.0.1" }
|
||||
cw4 = { version = "=1.0.1" }
|
||||
cw-controllers = { version = "=1.0.1" }
|
||||
dotenvy = "0.15.6"
|
||||
generic-array = "0.14.7"
|
||||
k256 = "0.11"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
once_cell = "1.7.2"
|
||||
@@ -135,3 +141,4 @@ tap = "1.0.1"
|
||||
thiserror = "1.0.38"
|
||||
tokio = "1.24.1"
|
||||
url = "2.2"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
@@ -32,7 +32,7 @@ For Typescript components, please see [ts-packages](./ts-packages).
|
||||
|
||||
### Developer chat
|
||||
|
||||
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
|
||||
You can chat with us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes place in the **#dev** channel. Node operators should be in the **#node-operators** channel.
|
||||
|
||||
### Rewards
|
||||
|
||||
@@ -46,7 +46,7 @@ Node, node operator and delegator rewards are determined according to the princi
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitiveness gets for a Sybil attacker.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
|
||||
@@ -70,7 +70,7 @@ Operator of node `i` is credited with the following amount:
|
||||
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
Delegate with stake `s` recieves:
|
||||
Delegate with stake `s` receives:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.18"
|
||||
version = "1.1.19"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -7,84 +7,53 @@ use crate::websocket;
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core::client::base_client::non_wasm_helpers::create_bandwidth_controller;
|
||||
use nym_client_core::client::base_client::storage::OnDiskPersistent;
|
||||
use nym_client_core::client::base_client::{
|
||||
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
|
||||
};
|
||||
use nym_client_core::client::inbound_messages::InputMessage;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::client::received_buffer::{
|
||||
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
|
||||
};
|
||||
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskManager;
|
||||
use nym_validator_client::nyxd::QueryNyxdClient;
|
||||
use nym_validator_client::Client;
|
||||
use std::error::Error;
|
||||
use tokio::sync::watch::error::SendError;
|
||||
|
||||
pub use nym_client_core::client::key_manager::KeyManager;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
pub use nym_sphinx::addressing::clients::Recipient;
|
||||
pub use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_validator_client::Client;
|
||||
|
||||
pub mod config;
|
||||
|
||||
type NativeClientBuilder<'a> = BaseClientBuilder<'a, Client<QueryNyxdClient>, OnDiskPersistent>;
|
||||
|
||||
pub struct SocketClient {
|
||||
/// Client configuration options, including, among other things, packet sending rates,
|
||||
/// key filepaths, etc.
|
||||
config: Config,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
}
|
||||
|
||||
impl SocketClient {
|
||||
pub fn new(config: Config) -> Self {
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
|
||||
|
||||
SocketClient {
|
||||
config,
|
||||
key_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_keys(config: Config, key_manager: KeyManager) -> Self {
|
||||
SocketClient {
|
||||
config,
|
||||
key_manager,
|
||||
}
|
||||
SocketClient { config }
|
||||
}
|
||||
|
||||
async fn create_bandwidth_controller(
|
||||
config: &Config,
|
||||
) -> BandwidthController<Client<QueryNyxdClient>, PersistentStorage> {
|
||||
let details = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let mut client_config =
|
||||
nym_validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
let nyxd_url = config
|
||||
.get_base()
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nyxd validator endpoint provided");
|
||||
let api_url = config
|
||||
.get_base()
|
||||
.get_nym_api_endpoints()
|
||||
.pop()
|
||||
.expect("No validator api endpoint provided");
|
||||
// overwrite env configuration with config URLs
|
||||
client_config = client_config.with_urls(nyxd_url, api_url);
|
||||
let client = nym_validator_client::Client::new_query(client_config)
|
||||
.expect("Could not construct query client");
|
||||
BandwidthController::new(
|
||||
nym_credential_storage::initialise_persistent_storage(
|
||||
config.get_base().get_database_path(),
|
||||
)
|
||||
.await,
|
||||
client,
|
||||
let storage = nym_credential_storage::initialise_persistent_storage(
|
||||
config.get_base().get_database_path(),
|
||||
)
|
||||
.await;
|
||||
|
||||
create_bandwidth_controller(config.get_base(), storage)
|
||||
}
|
||||
|
||||
fn start_websocket_listener(
|
||||
@@ -134,11 +103,13 @@ impl SocketClient {
|
||||
res
|
||||
}
|
||||
|
||||
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
|
||||
if !self.config.get_socket_type().is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
fn key_store(&self) -> OnDiskKeys {
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(self.config.get_base());
|
||||
OnDiskKeys::new(pathfinder)
|
||||
}
|
||||
|
||||
// TODO: see if this could also be shared with socks5 client / nym-sdk maybe
|
||||
async fn create_base_client_builder(&self) -> Result<NativeClientBuilder, ClientError> {
|
||||
// don't create bandwidth controller if credentials are disabled
|
||||
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
|
||||
None
|
||||
@@ -146,19 +117,28 @@ impl SocketClient {
|
||||
Some(Self::create_bandwidth_controller(&self.config).await)
|
||||
};
|
||||
|
||||
let base_builder = BaseClientBuilder::new_from_base_config(
|
||||
let base_client = BaseClientBuilder::new_from_base_config(
|
||||
self.config.get_base(),
|
||||
self.key_manager,
|
||||
self.key_store(),
|
||||
bandwidth_controller,
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
Some(self.config.get_base().get_reply_surb_database_path()),
|
||||
self.config.get_debug_settings(),
|
||||
self.config.get_base().get_reply_surb_database_path(),
|
||||
&self.config.get_debug_settings().reply_surbs,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let self_address = base_builder.as_mix_recipient();
|
||||
Ok(base_client)
|
||||
}
|
||||
|
||||
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
|
||||
if !self.config.get_socket_type().is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
|
||||
let base_builder = self.create_base_client_builder().await?;
|
||||
let mut started_client = base_builder.start_base().await?;
|
||||
let self_address = started_client.address;
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
let client_state = started_client.client_state;
|
||||
@@ -173,7 +153,7 @@ impl SocketClient {
|
||||
);
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self_address);
|
||||
info!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(started_client.task_manager)
|
||||
}
|
||||
@@ -183,27 +163,9 @@ impl SocketClient {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
|
||||
// don't create bandwidth controller if credentials are disabled
|
||||
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
|
||||
None
|
||||
} else {
|
||||
Some(Self::create_bandwidth_controller(&self.config).await)
|
||||
};
|
||||
|
||||
let base_client = BaseClientBuilder::new_from_base_config(
|
||||
self.config.get_base(),
|
||||
self.key_manager,
|
||||
bandwidth_controller,
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
Some(self.config.get_base().get_reply_surb_database_path()),
|
||||
self.config.get_debug_settings(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let address = base_client.as_mix_recipient();
|
||||
|
||||
let mut started_client = base_client.start_base().await?;
|
||||
let base_builder = self.create_base_client_builder().await?;
|
||||
let mut started_client = base_builder.start_base().await?;
|
||||
let address = started_client.address;
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::{
|
||||
};
|
||||
use clap::Args;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_config::NymConfig;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
@@ -152,7 +152,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
|
||||
let key_store = OnDiskKeys::from_config(config.get_base());
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>(
|
||||
&key_store,
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
@@ -169,7 +171,8 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let address =
|
||||
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{}", args.output.format(&init_results));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.18"
|
||||
version = "1.1.19"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::{
|
||||
};
|
||||
use clap::Args;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_config::NymConfig;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_socks5_client_core::config::Config;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
@@ -158,7 +158,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
|
||||
let key_store = OnDiskKeys::from_config(config.get_base());
|
||||
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>(
|
||||
&key_store,
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
@@ -177,7 +179,8 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let address =
|
||||
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{}", args.output.format(&init_results));
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
use clap::Args;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_client_core::client::base_client::storage::OnDiskPersistent;
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_socks5_client_core::{config::Config, NymClient};
|
||||
@@ -138,5 +139,6 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error
|
||||
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await
|
||||
let storage = OnDiskPersistent::from_config(config.get_base()).await?;
|
||||
NymClient::new(config, storage).run_forever().await
|
||||
}
|
||||
|
||||
Generated
+5287
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ default = ["console_error_panic_hook"]
|
||||
offline-test = []
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.68"
|
||||
bs58 = "0.4.0"
|
||||
futures = "0.3"
|
||||
js-sys = "0.3"
|
||||
@@ -24,12 +25,13 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
serde-wasm-bindgen = "0.4"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
tokio = { version = "1.24.1", features = ["sync"] }
|
||||
url = "2.2"
|
||||
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
thiserror = "1.0.40"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"}
|
||||
|
||||
@@ -40,13 +42,14 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-crypto = { path = "../../common/crypto" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
wasm-utils = { path = "../../common/wasm-utils" }
|
||||
nym-task = { path = "../../common/task" }
|
||||
wasm-utils = { path = "../../common/wasm-utils", features = ["storage"], default-features = false }
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
|
||||
@@ -28,7 +28,10 @@ const {
|
||||
set_panic_hook,
|
||||
Config,
|
||||
GatewayEndpointConfig,
|
||||
ClientStorage,
|
||||
current_network_topology,
|
||||
make_key,
|
||||
make_key2
|
||||
} = wasm_bindgen;
|
||||
|
||||
let client = null;
|
||||
@@ -103,29 +106,25 @@ function printAndDisplayTestResult(result) {
|
||||
});
|
||||
}
|
||||
|
||||
function dummyGatewayConfig() {
|
||||
return new GatewayEndpointConfig(
|
||||
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
|
||||
'n1rqqw8km7a0rvf8lr6k8dsdqvvkyn2mglj7xxfm',
|
||||
'ws://85.159.212.96:9000',
|
||||
)
|
||||
}
|
||||
|
||||
async function testWithTester() {
|
||||
const gatewayConfig = dummyGatewayConfig();
|
||||
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
|
||||
// A) construct with hardcoded topology
|
||||
const topology = dummyTopology()
|
||||
const nodeTester = await new NymNodeTester(gatewayConfig, topology);
|
||||
const nodeTester = await new NymNodeTester(topology, dummyGateway);
|
||||
|
||||
// B) first get topology directly from nym-api
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const topology = await current_network_topology(validator)
|
||||
// const nodeTester = await new NymNodeTester(gatewayConfig, topology);
|
||||
// const nodeTester = await new NymNodeTester(topology, dummyGateway);
|
||||
//
|
||||
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const nodeTester = await NymNodeTester.new_with_api(gatewayConfig, validator)
|
||||
// const nodeTester = await NymNodeTester.new_with_api(validator, dummyGateway)
|
||||
|
||||
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
|
||||
// const topology = dummyTopology()
|
||||
// const nodeTester = await new NymNodeTester(topology);
|
||||
|
||||
self.onmessage = async event => {
|
||||
if (event.data && event.data.kind) {
|
||||
@@ -143,7 +142,7 @@ async function testWithTester() {
|
||||
}
|
||||
|
||||
async function testWithNymClient() {
|
||||
const gatewayConfig = dummyGatewayConfig();
|
||||
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
const topology = dummyTopology()
|
||||
|
||||
let received = 0
|
||||
@@ -165,7 +164,7 @@ async function testWithNymClient() {
|
||||
|
||||
console.log('Instantiating WASM client...');
|
||||
|
||||
let clientBuilder = NymClientBuilder.new_tester(gatewayConfig, topology, onMessageHandler)
|
||||
let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, dummyGateway)
|
||||
console.log('Web worker creating WASM client...');
|
||||
let local_client = await clientBuilder.start_client();
|
||||
console.log('WASM client running!');
|
||||
@@ -223,10 +222,10 @@ async function normalNymClientUsage() {
|
||||
|
||||
debug.topology_refresh_rate_ms = BigInt(60000)
|
||||
|
||||
const gatewayConfig = dummyGatewayConfig();
|
||||
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
|
||||
const config = new Config('my-awesome-wasm-client', validator, gatewayConfig, debug);
|
||||
const config = new Config('my-awesome-wasm-client', validator, dummyGateway, debug);
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
console.log(message);
|
||||
@@ -270,6 +269,57 @@ async function normalNymClientUsage() {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function messWithStorage() {
|
||||
self.onmessage = async event => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case 'TestPacket': {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("button clicked...", mixnodeIdentity);
|
||||
|
||||
let id1 = "one";
|
||||
let id2 = "two";
|
||||
|
||||
console.log("making store1 NO-ENC");
|
||||
let _storage1 = await ClientStorage.new_unencrypted(id1);
|
||||
|
||||
console.log("making store2 ENC")
|
||||
let _storage2 = await new ClientStorage(id2, "my-secret-password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store1 WITH PASSWORD")
|
||||
// let _storage1_alt = await new ClientStorage(id1, "password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 WITHOUT PASSWORD")
|
||||
// let _storage2_alt = await ClientStorage.new_unencrypted(id2);
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 with WRONG PASSWORD")
|
||||
// let _storage2_bad = await new ClientStorage(id2, "bad-password")
|
||||
|
||||
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
//
|
||||
// console.log("store1: ", await storage1.store("FOOMP"));
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function main() {
|
||||
@@ -281,13 +331,13 @@ async function main() {
|
||||
set_panic_hook();
|
||||
|
||||
// run test on simplified and dedicated tester:
|
||||
await testWithTester()
|
||||
// await testWithTester()
|
||||
|
||||
// hook-up the whole client for testing
|
||||
// await testWithNymClient()
|
||||
|
||||
// 'Normal' client setup (to send 'normal' messages)
|
||||
// await normalNymClientUsage()
|
||||
await normalNymClientUsage()
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
|
||||
@@ -9,10 +9,10 @@
|
||||
use nym_client_core::config::{
|
||||
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic,
|
||||
DebugConfig as ConfigDebug, GatewayConnection as ConfigGatewayConnection,
|
||||
GatewayEndpointConfig, ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology,
|
||||
Traffic as ConfigTraffic,
|
||||
ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology, Traffic as ConfigTraffic,
|
||||
};
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
@@ -29,8 +29,9 @@ pub struct Config {
|
||||
|
||||
pub(crate) disabled_credentials_mode: bool,
|
||||
|
||||
/// Information regarding how the client should send data to gateway.
|
||||
pub(crate) gateway_endpoint: GatewayEndpointConfig,
|
||||
/// Information regarding how the client should choose gateway.
|
||||
/// If unspecified, the client will attempt to load the config from the storage.
|
||||
pub(crate) gateway: Option<IdentityKey>,
|
||||
|
||||
pub(crate) debug: ConfigDebug,
|
||||
}
|
||||
@@ -41,7 +42,7 @@ impl Config {
|
||||
pub fn new(
|
||||
id: String,
|
||||
validator_server: String,
|
||||
gateway_endpoint: GatewayEndpointConfig,
|
||||
gateway: Option<IdentityKey>,
|
||||
debug: Option<Debug>,
|
||||
) -> Self {
|
||||
Config {
|
||||
@@ -52,7 +53,7 @@ impl Config {
|
||||
.expect("provided url was malformed"),
|
||||
),
|
||||
disabled_credentials_mode: true,
|
||||
gateway_endpoint,
|
||||
gateway,
|
||||
debug: debug.map(Into::into).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
use self::config::Config;
|
||||
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt};
|
||||
use crate::client::response_pusher::ResponsePusher;
|
||||
use crate::constants::NODE_TESTER_CLIENT_ID;
|
||||
use crate::error::WasmClientError;
|
||||
use crate::helpers::{
|
||||
parse_recipient, parse_sender_tag, setup_new_key_manager, setup_reply_surb_storage_backend,
|
||||
choose_gateway, gateway_from_topology, parse_recipient, parse_sender_tag,
|
||||
setup_reply_surb_storage_backend,
|
||||
};
|
||||
use crate::storage::traits::FullWasmClientStorage;
|
||||
use crate::storage::ClientStorage;
|
||||
use crate::topology::WasmNymTopology;
|
||||
use js_sys::Promise;
|
||||
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
|
||||
@@ -15,18 +19,17 @@ use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core::client::base_client::{
|
||||
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle,
|
||||
};
|
||||
use nym_client_core::client::inbound_messages::InputMessage;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
|
||||
use nym_client_core::config::{
|
||||
CoverTraffic, DebugConfig, GatewayEndpointConfig, Topology, Traffic,
|
||||
};
|
||||
use nym_client_core::config::{CoverTraffic, DebugConfig, Topology, Traffic};
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskManager;
|
||||
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
@@ -56,9 +59,7 @@ pub struct NymClientBuilder {
|
||||
config: Config,
|
||||
custom_topology: Option<NymTopology>,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
|
||||
storage_passphrase: Option<String>,
|
||||
reply_surb_storage_backend: browser_backend::Backend,
|
||||
|
||||
on_message: js_sys::Function,
|
||||
@@ -72,13 +73,16 @@ pub struct NymClientBuilder {
|
||||
#[wasm_bindgen]
|
||||
impl NymClientBuilder {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(config: Config, on_message: js_sys::Function) -> Self {
|
||||
//, key_manager: Option<KeyManager>) {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
on_message: js_sys::Function,
|
||||
storage_passphrase: Option<String>,
|
||||
) -> Self {
|
||||
NymClientBuilder {
|
||||
reply_surb_storage_backend: setup_reply_surb_storage_backend(config.debug.reply_surbs),
|
||||
config,
|
||||
custom_topology: None,
|
||||
key_manager: setup_new_key_manager(),
|
||||
storage_passphrase,
|
||||
on_message,
|
||||
bandwidth_controller: None,
|
||||
disabled_credentials: true,
|
||||
@@ -90,19 +94,21 @@ impl NymClientBuilder {
|
||||
// hardcoded topology
|
||||
// NOTE: you most likely want to use `[NymNodeTester]` instead.
|
||||
pub fn new_tester(
|
||||
gateway_config: GatewayEndpointConfig,
|
||||
topology: WasmNymTopology,
|
||||
on_message: js_sys::Function,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> Self {
|
||||
if !topology.ensure_contains(&gateway_config) {
|
||||
panic!("the specified topology does not contain the gateway used by the client")
|
||||
if let Some(gateway_id) = &gateway {
|
||||
if !topology.ensure_contains_gateway_id(gateway_id) {
|
||||
panic!("the specified topology does not contain the gateway used by the client")
|
||||
}
|
||||
}
|
||||
|
||||
let full_config = Config {
|
||||
id: "ephemeral-id".to_string(),
|
||||
id: NODE_TESTER_CLIENT_ID.to_string(),
|
||||
nym_api_url: None,
|
||||
disabled_credentials_mode: true,
|
||||
gateway_endpoint: gateway_config,
|
||||
gateway,
|
||||
debug: DebugConfig {
|
||||
traffic: Traffic {
|
||||
disable_main_poisson_packet_distribution: true,
|
||||
@@ -126,12 +132,10 @@ impl NymClientBuilder {
|
||||
),
|
||||
config: full_config,
|
||||
custom_topology: Some(topology.into()),
|
||||
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
|
||||
// a prior shared keypair between the client and the gateway
|
||||
key_manager: setup_new_key_manager(),
|
||||
on_message,
|
||||
bandwidth_controller: None,
|
||||
disabled_credentials: true,
|
||||
storage_passphrase: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +143,7 @@ impl NymClientBuilder {
|
||||
ResponsePusher::new(client_output, on_message).start()
|
||||
}
|
||||
|
||||
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider>> {
|
||||
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider + Send + Sync>> {
|
||||
if let Some(hardcoded_topology) = self.custom_topology.take() {
|
||||
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology)))
|
||||
} else {
|
||||
@@ -150,22 +154,45 @@ impl NymClientBuilder {
|
||||
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
|
||||
console_log!("Starting the wasm client");
|
||||
|
||||
let maybe_topology_provider = self.topology_provider();
|
||||
|
||||
let disabled_credentials = if self.disabled_credentials {
|
||||
CredentialsToggle::Disabled
|
||||
} else {
|
||||
CredentialsToggle::Enabled
|
||||
};
|
||||
|
||||
let nym_api_endpoints = match self.config.nym_api_url {
|
||||
Some(endpoint) => vec![endpoint],
|
||||
let nym_api_endpoints = match &self.config.nym_api_url {
|
||||
Some(endpoint) => vec![endpoint.clone()],
|
||||
None => Vec::new(),
|
||||
};
|
||||
let mut base_builder = BaseClientBuilder::new(
|
||||
&self.config.gateway_endpoint,
|
||||
|
||||
// TODO: this will have to be re-used for surbs. but this is a problem for another PR.
|
||||
let client_store =
|
||||
ClientStorage::new_async(&self.config.id, self.storage_passphrase.take()).await?;
|
||||
|
||||
// if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way
|
||||
let gateway_endpoint = if let Some(topology) = &self.custom_topology {
|
||||
gateway_from_topology(
|
||||
&mut thread_rng(),
|
||||
self.config.gateway.as_deref(),
|
||||
topology,
|
||||
&client_store,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
choose_gateway(
|
||||
&client_store,
|
||||
self.config.gateway.clone(),
|
||||
&nym_api_endpoints,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let maybe_topology_provider = self.topology_provider();
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = BaseClientBuilder::new(
|
||||
&gateway_endpoint,
|
||||
&self.config.debug,
|
||||
self.key_manager,
|
||||
client_store,
|
||||
self.bandwidth_controller,
|
||||
self.reply_surb_storage_backend,
|
||||
disabled_credentials,
|
||||
@@ -175,8 +202,8 @@ impl NymClientBuilder {
|
||||
base_builder = base_builder.with_topology_provider(topology_provider);
|
||||
}
|
||||
|
||||
let self_address = base_builder.as_mix_recipient().to_string();
|
||||
let mut started_client = base_builder.start_base().await?;
|
||||
let self_address = started_client.address.to_string();
|
||||
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
@@ -202,16 +229,25 @@ impl NymClient {
|
||||
async fn _new(
|
||||
config: Config,
|
||||
on_message: js_sys::Function,
|
||||
storage_passphrase: Option<String>,
|
||||
) -> Result<NymClient, WasmClientError> {
|
||||
NymClientBuilder::new(config, on_message)
|
||||
NymClientBuilder::new(config, on_message, storage_passphrase)
|
||||
.start_client_async()
|
||||
.await
|
||||
}
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(config: Config, on_message: js_sys::Function) -> Promise {
|
||||
future_to_promise(async move { Self::_new(config, on_message).await.into_promise_result() })
|
||||
pub fn new(
|
||||
config: Config,
|
||||
on_message: js_sys::Function,
|
||||
storage_passphrase: Option<String>,
|
||||
) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::_new(config, on_message, storage_passphrase)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn self_address(&self) -> String {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
|
||||
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
|
||||
@@ -1,14 +1,17 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::errors::ClientStorageError;
|
||||
use crate::topology::WasmTopologyError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_node_tester_utils::error::NetworkTestingError;
|
||||
use nym_sphinx::addressing::clients::RecipientFormattingError;
|
||||
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
|
||||
use nym_topology::NymTopologyError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
@@ -43,12 +46,18 @@ pub enum WasmClientError {
|
||||
source: ValidatorClientError,
|
||||
},
|
||||
|
||||
#[error("The provided topology was invalid: {source}")]
|
||||
#[error("The provided wasm topology was invalid: {source}")]
|
||||
WasmTopologyError {
|
||||
#[from]
|
||||
source: WasmTopologyError,
|
||||
},
|
||||
|
||||
#[error("The provided nym topology was invalid: {source}")]
|
||||
TopologyError {
|
||||
#[from]
|
||||
source: NymTopologyError,
|
||||
},
|
||||
|
||||
#[error("failed to test the node: {source}")]
|
||||
NodeTestingFailure {
|
||||
#[from]
|
||||
@@ -67,6 +76,9 @@ pub enum WasmClientError {
|
||||
#[error("Mixnode {mixnode_identity} is not present in the current network topology")]
|
||||
NonExistentMixnode { mixnode_identity: String },
|
||||
|
||||
#[error("Gateway {gateway_identity} is not present in the current network topology")]
|
||||
NonExistentGateway { gateway_identity: String },
|
||||
|
||||
#[error("{raw} is not a valid Nym network recipient: {source}")]
|
||||
MalformedRecipient {
|
||||
raw: String,
|
||||
@@ -78,6 +90,17 @@ pub enum WasmClientError {
|
||||
raw: String,
|
||||
source: InvalidAnonymousSenderTagRepresentation,
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: ClientStorageError,
|
||||
},
|
||||
|
||||
#[error("this client has already registered with a gateway: {gateway_config:?}")]
|
||||
AlreadyRegistered {
|
||||
gateway_config: GatewayEndpointConfig,
|
||||
},
|
||||
}
|
||||
|
||||
impl WasmClientError {
|
||||
|
||||
@@ -2,27 +2,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::WasmClientError;
|
||||
use crate::storage::ClientStorage;
|
||||
use crate::topology::WasmNymTopology;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::client::key_manager::KeyManager;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_client_core::config;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::client::{IdentityKey, IdentityKeyRef};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::{console_log, PromisableResult};
|
||||
|
||||
pub(crate) fn setup_new_key_manager() -> KeyManager {
|
||||
let mut rng = OsRng;
|
||||
console_log!("generated new set of keys");
|
||||
KeyManager::new(&mut rng)
|
||||
}
|
||||
|
||||
// don't get too excited about the name, under the hood it's just a big fat placeholder
|
||||
// with no persistence
|
||||
pub(crate) fn setup_reply_surb_storage_backend(
|
||||
@@ -80,3 +78,88 @@ pub fn current_network_topology(nym_api_url: String) -> Promise {
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn choose_gateway(
|
||||
client_store: &ClientStorage,
|
||||
chosen_gateway: Option<IdentityKey>,
|
||||
nym_apis: &[Url],
|
||||
) -> Result<GatewayEndpointConfig, WasmClientError> {
|
||||
let existing_gateway_config = client_store.read_gateway_config().await?;
|
||||
|
||||
console_log!("loaded: {:?}", existing_gateway_config);
|
||||
|
||||
if let Some(existing) = existing_gateway_config {
|
||||
if let Some(provided) = &chosen_gateway {
|
||||
if provided != &existing.gateway_id {
|
||||
return Err(WasmClientError::AlreadyRegistered {
|
||||
gateway_config: existing,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Ok(existing);
|
||||
};
|
||||
|
||||
// if NOTHING is specified nor available, choose gateway randomly.
|
||||
let setup = GatewaySetup::new(None, chosen_gateway, None);
|
||||
let config = setup.try_get_gateway_details(nym_apis).await?;
|
||||
|
||||
// perform registration + persist the new gateway info
|
||||
// TODO: this is actually quite bad. we shouldn't be persisting gateway info here since we did not have persisted
|
||||
// the shared key yet. this will only happen when we start the base client itself.
|
||||
// but unfortunately, we can't do much more until we do a bit more refactoring.
|
||||
client_store.store_gateway_config(&config).await?;
|
||||
|
||||
console_log!("stored: {:?}", config);
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub(crate) async fn gateway_from_topology<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
explicit_gateway: Option<IdentityKeyRef<'_>>,
|
||||
topology: &NymTopology,
|
||||
client_store: &ClientStorage,
|
||||
) -> Result<GatewayEndpointConfig, WasmClientError> {
|
||||
let existing_gateway_config = client_store.read_gateway_config().await?;
|
||||
console_log!("loaded: {:?}", existing_gateway_config);
|
||||
|
||||
let new_gateway: GatewayEndpointConfig = if let Some(provided) = explicit_gateway {
|
||||
if let Some(existing) = existing_gateway_config {
|
||||
// we have stored gateway info and explicitly provided identity key
|
||||
//
|
||||
// check if they match, otherwise return an error
|
||||
return if provided != existing.gateway_id {
|
||||
Err(WasmClientError::AlreadyRegistered {
|
||||
gateway_config: existing,
|
||||
})
|
||||
} else {
|
||||
Ok(existing)
|
||||
};
|
||||
} else {
|
||||
// we have explicitly provided identity key and didn't have any prior stored data
|
||||
//
|
||||
// try to grab details from the topology
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(provided)
|
||||
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
|
||||
if let Some(gateway) = topology.get_gateway(&gateway_identity) {
|
||||
gateway.clone().into()
|
||||
} else {
|
||||
return Err(WasmClientError::NonExistentGateway {
|
||||
gateway_identity: gateway_identity.to_base58_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if let Some(existing) = existing_gateway_config {
|
||||
// we have stored data and didn't provide anything separately - use what's stored!
|
||||
return Ok(existing);
|
||||
} else {
|
||||
// we don't have anything stored nor we have provided anything
|
||||
//
|
||||
// just grab random gateway from our topology
|
||||
topology.random_gateway(rng)?.clone().into()
|
||||
};
|
||||
|
||||
console_log!("storing: {:?}", new_gateway);
|
||||
client_store.store_gateway_config(&new_gateway).await?;
|
||||
Ok(new_gateway)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
@@ -12,6 +12,8 @@ pub mod error;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod gateway_selector;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod storage;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod tester;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod topology;
|
||||
@@ -21,6 +23,8 @@ pub mod validation;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod helpers;
|
||||
|
||||
mod constants;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::simple_js_error;
|
||||
use wasm_utils::storage::error::StorageError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientStorageError {
|
||||
#[error("failed to use the storage: {source}")]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error("{typ} cryptographic key is not available in storage")]
|
||||
CryptoKeyNotInStorage { typ: String },
|
||||
}
|
||||
|
||||
impl From<ClientStorageError> for JsValue {
|
||||
fn from(value: ClientStorageError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::errors::ClientStorageError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
|
||||
use wasm_utils::PromisableResult;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub(crate) mod errors;
|
||||
pub(crate) mod traits;
|
||||
|
||||
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
|
||||
const STORAGE_VERSION: u32 = 1;
|
||||
|
||||
// v1 tables
|
||||
mod v1 {
|
||||
// stores
|
||||
pub const KEYS_STORE: &str = "keys";
|
||||
pub const CORE_STORE: &str = "core";
|
||||
|
||||
// keys
|
||||
// TODO: to replace with FULL config
|
||||
pub const GATEWAY_CONFIG: &str = "gateway_config";
|
||||
|
||||
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
|
||||
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
|
||||
|
||||
// TODO: for those we could actually use the subtle crypto storage
|
||||
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
|
||||
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClientStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) name: String,
|
||||
pub(crate) inner: Arc<WasmStorage>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ClientStorage {
|
||||
fn db_name(client_id: &str) -> String {
|
||||
format!("{STORAGE_NAME_PREFIX}-{client_id}")
|
||||
}
|
||||
|
||||
pub(crate) async fn new_async(
|
||||
client_id: &str,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<Self, ClientStorageError> {
|
||||
let name = Self::db_name(client_id);
|
||||
|
||||
// make sure the password is zeroized when no longer used, especially if we error out.
|
||||
// special care must be taken on JS side to ensure it's correctly used there.
|
||||
let passphrase = Zeroizing::new(passphrase);
|
||||
|
||||
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let old_version = evt.old_version() as u32;
|
||||
|
||||
if old_version < 1 {
|
||||
// migrating to version 1
|
||||
let db = evt.db();
|
||||
|
||||
db.create_object_store(v1::KEYS_STORE)?;
|
||||
db.create_object_store(v1::CORE_STORE)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let inner = WasmStorage::new(
|
||||
&name,
|
||||
STORAGE_VERSION,
|
||||
migrate_fn,
|
||||
passphrase.as_ref().map(|p| p.as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(ClientStorage {
|
||||
inner: Arc::new(inner),
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(client_id: String, passphrase: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, Some(passphrase))
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_unencrypted(client_id: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, None)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn read_gateway_config(
|
||||
&self,
|
||||
) -> Result<Option<GatewayEndpointConfig>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_CONFIG))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_identity_keypair(
|
||||
&self,
|
||||
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<Option<encryption::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, ClientStorageError> {
|
||||
self.may_read_identity_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<encryption::KeyPair, ClientStorageError> {
|
||||
self.may_read_encryption_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_ack_key(&self) -> Result<AckKey, ClientStorageError> {
|
||||
self.may_read_ack_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_ACK_KEY.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, ClientStorageError> {
|
||||
self.may_read_gateway_shared_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn store_identity_keypair(
|
||||
&self,
|
||||
keypair: &identity::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_encryption_keypair(
|
||||
&self,
|
||||
keypair: &encryption::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_ack_key(&self, key: &AckKey) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_ACK_KEY),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn store_gateway_config(
|
||||
&self,
|
||||
gateway_endpoint: &GatewayEndpointConfig,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::CORE_STORE,
|
||||
JsValue::from_str(v1::GATEWAY_CONFIG),
|
||||
gateway_endpoint,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::errors::ClientStorageError;
|
||||
use crate::storage::ClientStorage;
|
||||
use async_trait::async_trait;
|
||||
use nym_client_core::client::base_client::storage::MixnetClientStorage;
|
||||
use nym_client_core::client::key_manager::persistence::KeyStore;
|
||||
use nym_client_core::client::key_manager::KeyManager;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use wasm_utils::console_log;
|
||||
|
||||
// temporary until other variants are properly implemented (probably it should get changed into `ClientStorage`
|
||||
// implementing all traits and everything getting combined
|
||||
pub struct FullWasmClientStorage {
|
||||
key_store: ClientStorage,
|
||||
reply_storage: browser_backend::Backend,
|
||||
credential_storage: EphemeralCredentialStorage,
|
||||
}
|
||||
|
||||
impl MixnetClientStorage for FullWasmClientStorage {
|
||||
type KeyStore = ClientStorage;
|
||||
type ReplyStore = browser_backend::Backend;
|
||||
type CredentialStore = EphemeralCredentialStorage;
|
||||
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
|
||||
(self.key_store, self.reply_storage, self.credential_storage)
|
||||
}
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore {
|
||||
&self.key_store
|
||||
}
|
||||
|
||||
fn reply_store(&self) -> &Self::ReplyStore {
|
||||
&self.reply_storage
|
||||
}
|
||||
|
||||
fn credential_store(&self) -> &Self::CredentialStore {
|
||||
&self.credential_storage
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl KeyStore for ClientStorage {
|
||||
type StorageError = ClientStorageError;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
|
||||
console_log!("attempting to load cryptographic keys...");
|
||||
|
||||
// all keys implement `ZeroizeOnDrop`, so if we return an Error, whatever was already loaded will be cleared
|
||||
let identity_keypair = self.must_read_identity_keypair().await?;
|
||||
let encryption_keypair = self.must_read_encryption_keypair().await?;
|
||||
let ack_keypair = self.must_read_ack_key().await?;
|
||||
let gateway_shared_key = self.must_read_gateway_shared_key().await?;
|
||||
|
||||
Ok(KeyManager::from_keys(
|
||||
identity_keypair,
|
||||
encryption_keypair,
|
||||
gateway_shared_key,
|
||||
ack_keypair,
|
||||
))
|
||||
}
|
||||
|
||||
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
|
||||
console_log!("attempting to store cryptographic keys...");
|
||||
|
||||
self.store_identity_keypair(&keys.identity_keypair())
|
||||
.await?;
|
||||
self.store_encryption_keypair(&keys.encryption_keypair())
|
||||
.await?;
|
||||
self.store_ack_key(&keys.ack_key()).await?;
|
||||
self.store_gateway_shared_key(&keys.gateway_shared_key())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::constants::NODE_TESTER_ID;
|
||||
use crate::error::WasmClientError;
|
||||
use crate::helpers::{current_network_topology_async, setup_new_key_manager};
|
||||
use crate::helpers::{current_network_topology_async, gateway_from_topology};
|
||||
use crate::storage::ClientStorage;
|
||||
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
|
||||
use crate::tester::helpers::{
|
||||
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
|
||||
@@ -12,10 +14,9 @@ use futures::channel::mpsc;
|
||||
use js_sys::Promise;
|
||||
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core::client::key_manager::KeyManager;
|
||||
use nym_client_core::client::key_manager::ManagedKeys;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
|
||||
use nym_node_tester_utils::{NodeTester, TestMessage};
|
||||
@@ -25,7 +26,9 @@ use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_task::TaskManager;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Mutex as SyncMutex};
|
||||
@@ -33,7 +36,7 @@ use std::time::Duration;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::{check_promise_result, console_log, console_warn, PromisableResult};
|
||||
use wasm_utils::{check_promise_result, console_log, PromisableResult};
|
||||
|
||||
mod ephemeral_receiver;
|
||||
pub(crate) mod helpers;
|
||||
@@ -70,22 +73,19 @@ pub struct NymNodeTester {
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymNodeTesterBuilder {
|
||||
gateway_config: GatewayEndpointConfig,
|
||||
gateway: Option<IdentityKey>,
|
||||
|
||||
base_topology: NymTopology,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
|
||||
// unimplemented
|
||||
bandwidth_controller:
|
||||
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
|
||||
}
|
||||
|
||||
fn address(keys: &KeyManager, gateway_identity: NodeIdentity) -> Recipient {
|
||||
fn address(keys: &ManagedKeys, gateway_identity: NodeIdentity) -> Recipient {
|
||||
Recipient::new(
|
||||
*keys.identity_keypair().public_key(),
|
||||
*keys.encryption_keypair().public_key(),
|
||||
*keys.identity_public_key(),
|
||||
*keys.encryption_public_key(),
|
||||
gateway_identity,
|
||||
)
|
||||
}
|
||||
@@ -94,57 +94,64 @@ fn address(keys: &KeyManager, gateway_identity: NodeIdentity) -> Recipient {
|
||||
impl NymNodeTesterBuilder {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
gateway_config: GatewayEndpointConfig,
|
||||
base_topology: WasmNymTopology,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> NymNodeTesterBuilder {
|
||||
NymNodeTesterBuilder {
|
||||
gateway_config,
|
||||
gateway,
|
||||
base_topology: base_topology.into(),
|
||||
key_manager: setup_new_key_manager(),
|
||||
bandwidth_controller: None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn _new_with_api(
|
||||
gateway_config: GatewayEndpointConfig,
|
||||
api_url: String,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> Result<Self, WasmClientError> {
|
||||
let topology = current_network_topology_async(api_url).await?;
|
||||
Ok(NymNodeTesterBuilder::new(gateway_config, topology))
|
||||
Ok(NymNodeTesterBuilder::new(topology, gateway))
|
||||
}
|
||||
|
||||
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
|
||||
pub fn new_with_api(gateway: Option<IdentityKey>, api_url: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::_new_with_api(gateway_config, api_url)
|
||||
Self::_new_with_api(api_url, gateway)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
async fn gateway_info<R: Rng + CryptoRng>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
client_store: &ClientStorage,
|
||||
) -> Result<GatewayEndpointConfig, WasmClientError> {
|
||||
gateway_from_topology(
|
||||
rng,
|
||||
self.gateway.as_deref(),
|
||||
&self.base_topology,
|
||||
client_store,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
|
||||
let rng = OsRng;
|
||||
let mut rng = OsRng;
|
||||
let task_manager = TaskManager::default();
|
||||
|
||||
let gateway_identity =
|
||||
identity::PublicKey::from_base58_string(self.gateway_config.gateway_id)
|
||||
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
|
||||
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
|
||||
|
||||
// we **REALLY** need persistence...
|
||||
let shared_key = if self.key_manager.is_gateway_key_set() {
|
||||
Some(self.key_manager.gateway_shared_key())
|
||||
} else {
|
||||
console_warn!("Gateway key not set - will derive a fresh one.");
|
||||
None
|
||||
};
|
||||
let gateway_endpoint = self.gateway_info(&mut rng, &client_store).await?;
|
||||
let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?;
|
||||
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &client_store).await;
|
||||
|
||||
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
self.gateway_config.gateway_listener,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_endpoint.gateway_listener,
|
||||
managed_keys.identity_keypair(),
|
||||
gateway_identity,
|
||||
shared_key,
|
||||
managed_keys.gateway_shared_key(),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
Duration::from_secs(10),
|
||||
@@ -154,26 +161,26 @@ impl NymNodeTesterBuilder {
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(true);
|
||||
let shared_keys = gateway_client.authenticate_and_start().await?;
|
||||
|
||||
// currently pointless but might as well do it for the future ¯\_(ツ)_/¯
|
||||
self.key_manager.insert_gateway_shared_key(shared_keys);
|
||||
managed_keys
|
||||
.deal_with_gateway_key(shared_keys, &client_store)
|
||||
.await?;
|
||||
|
||||
// TODO: make those values configurable later
|
||||
let tester = NodeTester::new(
|
||||
rng,
|
||||
self.base_topology,
|
||||
Some(address(&self.key_manager, gateway_identity)),
|
||||
Some(address(&managed_keys, gateway_identity)),
|
||||
PacketSize::default(),
|
||||
Duration::from_millis(5),
|
||||
Duration::from_millis(5),
|
||||
self.key_manager.ack_key(),
|
||||
managed_keys.ack_key(),
|
||||
);
|
||||
|
||||
let (processed_sender, processed_receiver) = mpsc::unbounded();
|
||||
|
||||
let mut receiver = SimpleMessageReceiver::new_sphinx_receiver(
|
||||
self.key_manager.encryption_keypair(),
|
||||
self.key_manager.ack_key(),
|
||||
managed_keys.encryption_keypair(),
|
||||
managed_keys.ack_key(),
|
||||
mixnet_message_receiver,
|
||||
ack_receiver,
|
||||
processed_sender,
|
||||
@@ -234,24 +241,24 @@ async fn test_mixnode(
|
||||
impl NymNodeTester {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(gateway_config: GatewayEndpointConfig, topology: WasmNymTopology) -> Promise {
|
||||
pub fn new(topology: WasmNymTopology, gateway: Option<IdentityKey>) -> Promise {
|
||||
console_log!("constructing node tester!");
|
||||
NymNodeTesterBuilder::new(gateway_config, topology).setup_client()
|
||||
NymNodeTesterBuilder::new(topology, gateway).setup_client()
|
||||
}
|
||||
|
||||
async fn _new_with_api(
|
||||
gateway_config: GatewayEndpointConfig,
|
||||
api_url: String,
|
||||
gateway: Option<IdentityKey>,
|
||||
) -> Result<Self, WasmClientError> {
|
||||
NymNodeTesterBuilder::_new_with_api(gateway_config, api_url)
|
||||
NymNodeTesterBuilder::_new_with_api(api_url, gateway)
|
||||
.await?
|
||||
._setup_client()
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
|
||||
pub fn new_with_api(api_url: String, gateway: Option<IdentityKey>) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::_new_with_api(gateway_config, api_url)
|
||||
Self::_new_with_api(api_url, gateway)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::mix::{Layer, MixnodeConversionError};
|
||||
use nym_topology::{gateway, mix, MixLayer, NymTopology};
|
||||
use nym_validator_client::client::MixId;
|
||||
use nym_validator_client::client::{IdentityKeyRef, MixId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use thiserror::Error;
|
||||
@@ -82,11 +82,16 @@ impl WasmNymTopology {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
|
||||
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
|
||||
self.inner
|
||||
.gateways()
|
||||
.iter()
|
||||
.any(|g| g.identity_key.to_base58_string() == gateway_config.gateway_id)
|
||||
.any(|g| g.identity_key.to_base58_string() == gateway_id)
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
|
||||
@@ -55,11 +55,16 @@ where
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub async fn get_credential<C: DkgQueryClient + Send + Sync, St: Storage>(
|
||||
pub async fn get_credential<C, St>(
|
||||
state: &State,
|
||||
client: &C,
|
||||
storage: &St,
|
||||
) -> Result<(), BandwidthControllerError> {
|
||||
) -> Result<(), BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: Storage,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let epoch_id = client.get_current_epoch().await?.epoch_id;
|
||||
let threshold = client
|
||||
.get_current_epoch_threshold()
|
||||
@@ -83,7 +88,6 @@ pub async fn get_credential<C: DkgQueryClient + Send + Sync, St: Storage>(
|
||||
signature.to_bs58(),
|
||||
epoch_id.to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ pub enum BandwidthControllerError {
|
||||
Nyxd(#[from] nym_validator_client::nyxd::error::NyxdError),
|
||||
|
||||
#[error("There was a credential storage error - {0}")]
|
||||
CredentialStorageError(#[from] StorageError),
|
||||
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
// this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now
|
||||
#[error(transparent)]
|
||||
StorageError(#[from] StorageError),
|
||||
|
||||
#[error("Coconut error - {0}")]
|
||||
CoconutError(#[from] CoconutError),
|
||||
|
||||
@@ -26,7 +26,7 @@ pub mod error;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm_mockups;
|
||||
|
||||
pub struct BandwidthController<C, St: Storage> {
|
||||
pub struct BandwidthController<C, St> {
|
||||
storage: St,
|
||||
client: C,
|
||||
}
|
||||
@@ -45,8 +45,13 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
|
||||
let bandwidth_credential = self
|
||||
.storage
|
||||
.get_next_coconut_credential()
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
|
||||
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
|
||||
.map_err(|_| StorageError::InconsistentData)?;
|
||||
let voucher_info = bandwidth_credential.voucher_info.clone();
|
||||
@@ -82,10 +87,16 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError> {
|
||||
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
// JS: shouldn't we send some contract/validator/gateway message here to actually, you know,
|
||||
// consume it?
|
||||
Ok(self.storage.consume_coconut_credential(id).await?)
|
||||
self.storage
|
||||
.consume_coconut_credential(id)
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ url = { version ="2.2", features = ["serde"] }
|
||||
tungstenite = { version = "0.13.0", default-features = false }
|
||||
tokio = { version = "1.24.1", features = ["macros"]}
|
||||
time = "0.3.17"
|
||||
zeroize = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
||||
@@ -38,6 +39,7 @@ nym-topology = { path = "../topology" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client]
|
||||
path = "../client-libs/validator-client"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::{client::replies::reply_storage, config::DebugConfig};
|
||||
|
||||
pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty {
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::key_manager::KeyManager;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ManagedKeys;
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use crate::client::real_messages_control;
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
@@ -26,6 +28,7 @@ use crate::{config, spawn_future};
|
||||
use futures::channel::mpsc;
|
||||
use log::{debug, info};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
@@ -38,21 +41,22 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use url::Url;
|
||||
|
||||
use nym_credential_storage::storage::Storage;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_validator_client::nyxd::traits::DkgQueryClient;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_validator_client::nyxd::traits::DkgQueryClient;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub mod non_wasm_helpers;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod storage;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientInput {
|
||||
@@ -151,31 +155,32 @@ impl From<bool> for CredentialsToggle {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BaseClientBuilder<'a, B, C, St: Storage> {
|
||||
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
// due to wasm limitations I had to split it like this : (
|
||||
gateway_config: &'a GatewayEndpointConfig,
|
||||
debug_config: &'a DebugConfig,
|
||||
disabled_credentials: bool,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
reply_storage_backend: B,
|
||||
reply_storage_backend: S::ReplyStore,
|
||||
key_store: S::KeyStore,
|
||||
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
key_manager: KeyManager,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
managed_keys: ManagedKeys,
|
||||
}
|
||||
|
||||
impl<'a, B, C, St> BaseClientBuilder<'a, B, C, St>
|
||||
impl<'a, C, S> BaseClientBuilder<'a, C, S>
|
||||
where
|
||||
B: ReplyStorageBackend + Send + Sync + 'static,
|
||||
C: DkgQueryClient + Sync + Send + 'static,
|
||||
St: Storage + 'static,
|
||||
S: MixnetClientStorage + 'static,
|
||||
C: DkgQueryClient + Send + Sync + 'static,
|
||||
{
|
||||
// TODO: combine all storages
|
||||
pub fn new_from_base_config<T>(
|
||||
base_config: &'a Config<T>,
|
||||
key_manager: KeyManager,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
reply_storage_backend: B,
|
||||
) -> BaseClientBuilder<'a, B, C, St> {
|
||||
key_store: S::KeyStore,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
reply_storage_backend: S::ReplyStore,
|
||||
) -> BaseClientBuilder<'a, C, S> {
|
||||
BaseClientBuilder {
|
||||
gateway_config: base_config.get_gateway_endpoint_config(),
|
||||
debug_config: base_config.get_debug_config(),
|
||||
@@ -183,20 +188,22 @@ where
|
||||
nym_api_endpoints: base_config.get_nym_api_endpoints(),
|
||||
bandwidth_controller,
|
||||
reply_storage_backend,
|
||||
key_manager,
|
||||
key_store,
|
||||
managed_keys: ManagedKeys::Invalidated,
|
||||
custom_topology_provider: None,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: combine all storages
|
||||
pub fn new(
|
||||
gateway_config: &'a GatewayEndpointConfig,
|
||||
debug_config: &'a DebugConfig,
|
||||
key_manager: KeyManager,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
reply_storage_backend: B,
|
||||
key_store: S::KeyStore,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
reply_storage_backend: S::ReplyStore,
|
||||
credentials_toggle: CredentialsToggle,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
) -> BaseClientBuilder<'a, B, C, St> {
|
||||
) -> BaseClientBuilder<'a, C, S> {
|
||||
BaseClientBuilder {
|
||||
gateway_config,
|
||||
debug_config,
|
||||
@@ -205,19 +212,25 @@ where
|
||||
reply_storage_backend,
|
||||
custom_topology_provider: None,
|
||||
bandwidth_controller,
|
||||
key_manager,
|
||||
key_store,
|
||||
managed_keys: ManagedKeys::Invalidated,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
|
||||
pub fn with_topology_provider(
|
||||
mut self,
|
||||
provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Self {
|
||||
self.custom_topology_provider = Some(provider);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_mix_recipient(&self) -> Recipient {
|
||||
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
|
||||
// because it relies on the crypto keys being already loaded
|
||||
fn as_mix_recipient(&self) -> Recipient {
|
||||
Recipient::new(
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
*self.key_manager.encryption_keypair().public_key(),
|
||||
*self.managed_keys.identity_public_key(),
|
||||
*self.managed_keys.encryption_public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(),
|
||||
@@ -307,7 +320,11 @@ where
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient<C, St>, ClientCoreError> {
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let gateway_id = self.gateway_config.gateway_id.clone();
|
||||
if gateway_id.is_empty() {
|
||||
return Err(ClientCoreError::GatewayIdUnknown);
|
||||
@@ -320,19 +337,11 @@ where
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
|
||||
let shared_key = if self.key_manager.is_gateway_key_set() {
|
||||
Some(self.key_manager.gateway_shared_key())
|
||||
} else {
|
||||
log::info!("Gateway key not set! Will proceed anyway.");
|
||||
None
|
||||
};
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
self.managed_keys.identity_keypair(),
|
||||
gateway_identity,
|
||||
shared_key,
|
||||
self.managed_keys.gateway_shared_key(),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.debug_config
|
||||
@@ -344,19 +353,27 @@ where
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
|
||||
|
||||
gateway_client
|
||||
let shared_key = gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.tap_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}")
|
||||
})?;
|
||||
|
||||
self.managed_keys
|
||||
.deal_with_gateway_key(shared_key, &self.key_store)
|
||||
.await
|
||||
.map_err(|source| ClientCoreError::KeyStoreError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
|
||||
Ok(gateway_client)
|
||||
}
|
||||
|
||||
fn setup_topology_provider(
|
||||
custom_provider: Option<Box<dyn TopologyProvider>>,
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
nym_api_urls: Vec<Url>,
|
||||
) -> Box<dyn TopologyProvider> {
|
||||
) -> Box<dyn TopologyProvider + Send + Sync> {
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| {
|
||||
Box::new(NymApiTopologyProvider::new(
|
||||
@@ -369,7 +386,7 @@ where
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
async fn start_topology_refresher(
|
||||
topology_provider: Box<dyn TopologyProvider>,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
topology_config: config::Topology,
|
||||
topology_accessor: TopologyAccessor,
|
||||
mut shutdown: TaskClient,
|
||||
@@ -414,55 +431,62 @@ where
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_client: GatewayClient<C, St>,
|
||||
gateway_client: GatewayClient<C, S::CredentialStore>,
|
||||
shutdown: TaskClient,
|
||||
) -> BatchMixMessageSender {
|
||||
) -> BatchMixMessageSender
|
||||
where
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
|
||||
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
|
||||
async fn setup_persistent_reply_storage(
|
||||
backend: B,
|
||||
backend: S::ReplyStore,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<CombinedReplyStorage, ClientCoreError>
|
||||
where
|
||||
<B as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
S::ReplyStore: Send + Sync,
|
||||
{
|
||||
if backend.is_active() {
|
||||
log::trace!("Setup persistent reply storage");
|
||||
let persistent_storage = PersistentReplyStorage::new(backend);
|
||||
let mem_store = persistent_storage
|
||||
.load_state_from_backend()
|
||||
log::trace!("Setup persistent reply storage");
|
||||
let persistent_storage = PersistentReplyStorage::new(backend);
|
||||
let mem_store = persistent_storage
|
||||
.load_state_from_backend()
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
let store_clone = mem_store.clone();
|
||||
spawn_future(async move {
|
||||
persistent_storage
|
||||
.flush_on_shutdown(store_clone, shutdown)
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
});
|
||||
|
||||
let store_clone = mem_store.clone();
|
||||
spawn_future(async move {
|
||||
persistent_storage
|
||||
.flush_on_shutdown(store_clone, shutdown)
|
||||
.await
|
||||
});
|
||||
Ok(mem_store)
|
||||
}
|
||||
|
||||
Ok(mem_store)
|
||||
} else {
|
||||
log::trace!("Setup inactive reply storage");
|
||||
Ok(backend
|
||||
.get_inactive_storage()
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?)
|
||||
}
|
||||
async fn initial_key_setup(&mut self) {
|
||||
assert!(!self.managed_keys.is_valid());
|
||||
let mut rng = OsRng;
|
||||
self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await;
|
||||
}
|
||||
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
|
||||
where
|
||||
<B as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
S::ReplyStore: Send + Sync,
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
info!("Starting nym client");
|
||||
self.initial_key_setup().await;
|
||||
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
// rather than creating them here, so say for example the buffer controller would create the request channels
|
||||
@@ -516,7 +540,7 @@ where
|
||||
.await?;
|
||||
|
||||
Self::start_received_messages_buffer_controller(
|
||||
self.key_manager.encryption_keypair(),
|
||||
self.managed_keys.encryption_keypair(),
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
reply_storage.key_storage(),
|
||||
@@ -541,7 +565,7 @@ where
|
||||
|
||||
let controller_config = real_messages_control::Config::new(
|
||||
self.debug_config,
|
||||
self.key_manager.ack_key(),
|
||||
self.managed_keys.ack_key(),
|
||||
self_address,
|
||||
);
|
||||
|
||||
@@ -566,7 +590,7 @@ where
|
||||
{
|
||||
Self::start_cover_traffic_stream(
|
||||
self.debug_config,
|
||||
self.key_manager.ack_key(),
|
||||
self.managed_keys.ack_key(),
|
||||
self_address,
|
||||
shared_topology_accessor.clone(),
|
||||
sphinx_message_sender,
|
||||
@@ -578,6 +602,7 @@ where
|
||||
debug!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(BaseClient {
|
||||
address: self_address,
|
||||
client_input: ClientInputStatus::AwaitingProducer {
|
||||
client_input: ClientInput {
|
||||
connection_command_sender: client_connection_tx,
|
||||
@@ -600,6 +625,7 @@ where
|
||||
}
|
||||
|
||||
pub struct BaseClient {
|
||||
pub address: Recipient,
|
||||
pub client_input: ClientInputStatus,
|
||||
pub client_output: ClientOutputStatus,
|
||||
pub client_state: ClientState,
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::{
|
||||
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
|
||||
};
|
||||
use crate::config::DebugConfig;
|
||||
use crate::config;
|
||||
use crate::config::Config;
|
||||
use crate::error::ClientCoreError;
|
||||
use log::{error, info};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_validator_client::nyxd::QueryNyxdClient;
|
||||
use nym_validator_client::Client;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
db_path: P,
|
||||
debug_config: &DebugConfig,
|
||||
surb_config: &config::ReplySurbs,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
info!("creating fresh surb database");
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
@@ -30,12 +36,8 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
// it will only be happening on the very first run and in practice won't incur huge
|
||||
// costs since the storage is going to be empty
|
||||
let mem_store = CombinedReplyStorage::new(
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
surb_config.minimum_reply_surb_storage_threshold,
|
||||
surb_config.maximum_reply_surb_storage_threshold,
|
||||
);
|
||||
storage_backend
|
||||
.init_fresh(&mem_store)
|
||||
@@ -47,17 +49,13 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
Ok(storage_backend)
|
||||
}
|
||||
|
||||
fn setup_inactive_backend(debug_config: &DebugConfig) -> fs_backend::Backend {
|
||||
info!("creating inactive surb database");
|
||||
fs_backend::Backend::new_inactive(
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
)
|
||||
}
|
||||
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
|
||||
// info!("creating inactive surb database");
|
||||
// fs_backend::Backend::new_inactive(
|
||||
// surb_config.minimum_reply_surb_storage_threshold,
|
||||
// surb_config.maximum_reply_surb_storage_threshold,
|
||||
// )
|
||||
// }
|
||||
|
||||
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
let db_path = db_path.as_ref();
|
||||
@@ -81,28 +79,56 @@ fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
}
|
||||
|
||||
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
db_path: Option<P>,
|
||||
debug_config: &DebugConfig,
|
||||
db_path: P,
|
||||
surb_config: &config::ReplySurbs,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
if let Some(db_path) = db_path {
|
||||
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load
|
||||
// the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load
|
||||
// the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
archive_corrupted_database(db_path)?;
|
||||
setup_fresh_backend(db_path, debug_config).await
|
||||
}
|
||||
archive_corrupted_database(db_path)?;
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
} else {
|
||||
setup_fresh_backend(db_path, debug_config).await
|
||||
}
|
||||
} else {
|
||||
Ok(setup_inactive_backend(debug_config))
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_bandwidth_controller<T, St: CredentialStorage>(
|
||||
config: &Config<T>,
|
||||
storage: St,
|
||||
) -> BandwidthController<Client<QueryNyxdClient>, St> {
|
||||
let nyxd_url = config
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nyxd validator endpoint provided");
|
||||
let api_url = config
|
||||
.get_nym_api_endpoints()
|
||||
.pop()
|
||||
.expect("No validator api endpoint provided");
|
||||
|
||||
create_bandwidth_controller_with_urls(nyxd_url, api_url, storage)
|
||||
}
|
||||
|
||||
pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
|
||||
nyxd_url: Url,
|
||||
nym_api_url: Url,
|
||||
storage: St,
|
||||
) -> BandwidthController<Client<QueryNyxdClient>, St> {
|
||||
let details = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let mut client_config = nym_validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
// overwrite env configuration with config URLs
|
||||
client_config = client_config.with_urls(nyxd_url, nym_api_url);
|
||||
let client = nym_validator_client::Client::new_query(client_config)
|
||||
.expect("Could not construct query client");
|
||||
|
||||
BandwidthController::new(storage, client)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: combine those more closely. Perhaps into a single underlying store.
|
||||
// Like for persistent, on-disk, storage, what's the point of having 3 different databases?
|
||||
|
||||
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
|
||||
use crate::client::replies::reply_storage;
|
||||
use crate::client::replies::reply_storage::ReplyStorageBackend;
|
||||
use nym_credential_storage::ephemeral_storage::{
|
||||
EphemeralStorage as EphemeralCredentialStorage, EphemeralStorage,
|
||||
};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::client::base_client::non_wasm_helpers;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::client::key_manager::persistence::OnDiskKeys;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::config::{persistence::key_pathfinder::ClientKeyPathfinder, Config};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::error::ClientCoreError;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use crate::client::replies::reply_storage::fs_backend;
|
||||
|
||||
pub trait MixnetClientStorage {
|
||||
type KeyStore: KeyStore;
|
||||
type ReplyStore: ReplyStorageBackend;
|
||||
type CredentialStore: CredentialStorage;
|
||||
|
||||
// this is a TERRIBLE name...
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore);
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore;
|
||||
fn reply_store(&self) -> &Self::ReplyStore;
|
||||
fn credential_store(&self) -> &Self::CredentialStore;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ephemeral {
|
||||
key_store: InMemEphemeralKeys,
|
||||
reply_store: reply_storage::Empty,
|
||||
credential_store: EphemeralStorage,
|
||||
}
|
||||
|
||||
impl Ephemeral {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnetClientStorage for Ephemeral {
|
||||
type KeyStore = InMemEphemeralKeys;
|
||||
type ReplyStore = reply_storage::Empty;
|
||||
type CredentialStore = EphemeralCredentialStorage;
|
||||
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
|
||||
(self.key_store, self.reply_store, self.credential_store)
|
||||
}
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore {
|
||||
&self.key_store
|
||||
}
|
||||
|
||||
fn reply_store(&self) -> &Self::ReplyStore {
|
||||
&self.reply_store
|
||||
}
|
||||
|
||||
fn credential_store(&self) -> &Self::CredentialStore {
|
||||
&self.credential_store
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct OnDiskPersistent {
|
||||
pub(crate) key_store: OnDiskKeys,
|
||||
pub(crate) reply_store: fs_backend::Backend,
|
||||
pub(crate) credential_store: PersistentCredentialStorage,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl OnDiskPersistent {
|
||||
pub fn new(
|
||||
key_store: OnDiskKeys,
|
||||
reply_store: fs_backend::Backend,
|
||||
credential_store: PersistentCredentialStorage,
|
||||
) -> Self {
|
||||
Self {
|
||||
key_store,
|
||||
reply_store,
|
||||
credential_store,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_config<T>(config: &Config<T>) -> Result<Self, ClientCoreError> {
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
let key_store = OnDiskKeys::new(pathfinder);
|
||||
|
||||
let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
config.get_reply_surb_database_path(),
|
||||
&config.get_debug_config().reply_surbs,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let credential_store =
|
||||
nym_credential_storage::initialise_persistent_storage(config.get_database_path()).await;
|
||||
|
||||
Ok(OnDiskPersistent {
|
||||
key_store,
|
||||
reply_store,
|
||||
credential_store,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl MixnetClientStorage for OnDiskPersistent {
|
||||
type KeyStore = OnDiskKeys;
|
||||
type ReplyStore = fs_backend::Backend;
|
||||
type CredentialStore = PersistentCredentialStorage;
|
||||
|
||||
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
|
||||
(self.key_store, self.reply_store, self.credential_store)
|
||||
}
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore {
|
||||
&self.key_store
|
||||
}
|
||||
|
||||
fn reply_store(&self) -> &Self::ReplyStore {
|
||||
&self.reply_store
|
||||
}
|
||||
|
||||
fn credential_store(&self) -> &Self::CredentialStore {
|
||||
&self.credential_store
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use log::*;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
||||
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
||||
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
|
||||
// And I guess there will need to be some mechanism for a grace period when you can still
|
||||
// use the old key after new one was issued.
|
||||
|
||||
// Remember that Arc<T> has Deref implementation for T
|
||||
#[derive(Clone)]
|
||||
pub struct KeyManager {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// encryption key associated with the client instance.
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// shared key derived with the gateway during "registration handshake"
|
||||
gateway_shared_key: Option<Arc<SharedKeys>>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
}
|
||||
|
||||
// The expected flow of a KeyManager "lifetime" is as follows:
|
||||
/*
|
||||
1. ::new() is called during client-init
|
||||
2. after gateway registration is completed [in init] ::insert_gateway_shared_key() is called
|
||||
3. ::store_keys() is called before init finishes execution.
|
||||
4. ::load_keys() is called at the beginning of each subsequent client-run
|
||||
5. [not implemented] ::rotate_keys() is called periodically during client-run I presume?
|
||||
*/
|
||||
|
||||
impl KeyManager {
|
||||
/// Creates new instance of a [`KeyManager`]
|
||||
pub fn new<R>(rng: &mut R) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
KeyManager {
|
||||
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
|
||||
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
|
||||
gateway_shared_key: None,
|
||||
ack_key: Arc::new(AckKey::new(rng)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_keys(
|
||||
id_keypair: identity::KeyPair,
|
||||
enc_keypair: encryption::KeyPair,
|
||||
gateway_shared_key: SharedKeys,
|
||||
ack_key: AckKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
identity_keypair: Arc::new(id_keypair),
|
||||
encryption_keypair: Arc::new(enc_keypair),
|
||||
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
|
||||
ack_key: Arc::new(ack_key),
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads previously stored client keys from the disk.
|
||||
fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_identity_key().to_owned(),
|
||||
client_pathfinder.public_identity_key().to_owned(),
|
||||
))?;
|
||||
let encryption_keypair: encryption::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_encryption_key().to_owned(),
|
||||
client_pathfinder.public_encryption_key().to_owned(),
|
||||
))?;
|
||||
|
||||
let ack_key: AckKey = nym_pemstore::load_key(client_pathfinder.ack_key())?;
|
||||
|
||||
Ok(KeyManager {
|
||||
identity_keypair: Arc::new(identity_keypair),
|
||||
encryption_keypair: Arc::new(encryption_keypair),
|
||||
gateway_shared_key: None,
|
||||
ack_key: Arc::new(ack_key),
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads previously stored keys from the disk. Fails if not all, including the shared gateway
|
||||
/// key, is available.
|
||||
pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
|
||||
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
|
||||
|
||||
let gateway_shared_key: SharedKeys =
|
||||
nym_pemstore::load_key(client_pathfinder.gateway_shared_key())?;
|
||||
|
||||
key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key));
|
||||
|
||||
Ok(key_manager)
|
||||
}
|
||||
|
||||
/// Loads previously stored keys from the disk. Fails if client keys are not availabe, but the
|
||||
/// shared gateway key is optional.
|
||||
pub fn load_keys_but_gateway_is_optional(
|
||||
client_pathfinder: &ClientKeyPathfinder,
|
||||
) -> io::Result<Self> {
|
||||
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
|
||||
|
||||
let gateway_shared_key: Result<SharedKeys, io::Error> =
|
||||
nym_pemstore::load_key(client_pathfinder.gateway_shared_key());
|
||||
|
||||
// It's ok if the gateway key was not found
|
||||
let gateway_shared_key = match gateway_shared_key {
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
Ok(key) => Ok(Some(key)),
|
||||
}?;
|
||||
|
||||
key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new);
|
||||
|
||||
Ok(key_manager)
|
||||
}
|
||||
|
||||
/// Stores all available keys on the disk.
|
||||
// While perhaps there is no much point in storing the `AckKey` on the disk,
|
||||
// it is done so for the consistency sake so that you wouldn't require an rng instance
|
||||
// during `load_keys` to generate the said key.
|
||||
pub fn store_keys(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
|
||||
nym_pemstore::store_keypair(
|
||||
self.identity_keypair.as_ref(),
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_identity_key().to_owned(),
|
||||
client_pathfinder.public_identity_key().to_owned(),
|
||||
),
|
||||
)?;
|
||||
nym_pemstore::store_keypair(
|
||||
self.encryption_keypair.as_ref(),
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
client_pathfinder.private_encryption_key().to_owned(),
|
||||
client_pathfinder.public_encryption_key().to_owned(),
|
||||
),
|
||||
)?;
|
||||
|
||||
nym_pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?;
|
||||
|
||||
match self.gateway_shared_key.as_ref() {
|
||||
None => debug!("No gateway shared key available to store!"),
|
||||
Some(gate_key) => {
|
||||
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
|
||||
match self.gateway_shared_key.as_ref() {
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"trying to store a non-existing key",
|
||||
))
|
||||
}
|
||||
Some(gate_key) => {
|
||||
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Overwrite the existing identity keypair
|
||||
pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) {
|
||||
self.identity_keypair = Arc::new(id_keypair);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
/// Overwrite the existing encryption keypair
|
||||
pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) {
|
||||
self.encryption_keypair = Arc::new(enc_keypair);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
|
||||
/// Overwrite the existing ack key
|
||||
pub fn set_ack_key(&mut self, ack_key: AckKey) {
|
||||
self.ack_key = Arc::new(ack_key);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`AckKey`].
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
|
||||
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
|
||||
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
|
||||
self.gateway_shared_key = Some(gateway_shared_key)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`SharedKey`].
|
||||
// since this function is not fully public, it is not expected to be used externally and
|
||||
// hence it's up to us to ensure it's called in correct context
|
||||
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
|
||||
Arc::clone(
|
||||
self.gateway_shared_key
|
||||
.as_ref()
|
||||
.expect("tried to unwrap empty gateway key!"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_gateway_key_set(&self) -> bool {
|
||||
self.gateway_shared_key.is_some()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
pub mod persistence;
|
||||
|
||||
pub enum ManagedKeys {
|
||||
Initial(KeyManagerBuilder),
|
||||
FullyDerived(KeyManager),
|
||||
|
||||
// I really hate the existence of this variant, but I couldn't come up with a better way to handle
|
||||
// `Self::deal_with_gateway_key` otherwise.
|
||||
Invalidated,
|
||||
}
|
||||
|
||||
impl From<KeyManagerBuilder> for ManagedKeys {
|
||||
fn from(value: KeyManagerBuilder) -> Self {
|
||||
ManagedKeys::Initial(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyManager> for ManagedKeys {
|
||||
fn from(value: KeyManager) -> Self {
|
||||
ManagedKeys::FullyDerived(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagedKeys {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!matches!(self, ManagedKeys::Invalidated)
|
||||
}
|
||||
|
||||
pub async fn try_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
|
||||
Ok(ManagedKeys::FullyDerived(
|
||||
KeyManager::load_keys(key_store).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn generate_new<R>(rng: &mut R) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
|
||||
}
|
||||
|
||||
pub async fn load_or_generate<R, S>(rng: &mut R, key_store: &S) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
S: KeyStore,
|
||||
{
|
||||
Self::try_load(key_store)
|
||||
.await
|
||||
.unwrap_or_else(|_| Self::generate_new(rng))
|
||||
}
|
||||
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.identity_keypair(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.identity_keypair(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.encryption_keypair(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.ack_key(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.ack_key(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
|
||||
match self {
|
||||
ManagedKeys::Initial(_) => None,
|
||||
ManagedKeys::FullyDerived(keys) => Some(keys.gateway_shared_key()),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity_public_key(&self) -> &identity::PublicKey {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.identity_keypair.public_key(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.identity_keypair.public_key(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encryption_public_key(&self) -> &encryption::PublicKey {
|
||||
match self {
|
||||
ManagedKeys::Initial(keys) => keys.encryption_keypair.public_key(),
|
||||
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair.public_key(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn deal_with_gateway_key<S: KeyStore>(
|
||||
&mut self,
|
||||
gateway_shared_key: Arc<SharedKeys>,
|
||||
key_store: &S,
|
||||
) -> Result<(), S::StorageError> {
|
||||
let key_manager = match std::mem::replace(self, ManagedKeys::Invalidated) {
|
||||
ManagedKeys::Initial(keys) => {
|
||||
let key_manager = keys.insert_gateway_shared_key(gateway_shared_key);
|
||||
key_manager.persist_keys(key_store).await?;
|
||||
key_manager
|
||||
}
|
||||
ManagedKeys::FullyDerived(key_manager) => {
|
||||
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|
||||
|| key_manager.gateway_shared_key != gateway_shared_key
|
||||
{
|
||||
// this should NEVER happen thus panic here
|
||||
panic!("derived fresh gateway shared key whilst already holding one!")
|
||||
}
|
||||
key_manager
|
||||
}
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
};
|
||||
|
||||
*self = ManagedKeys::FullyDerived(key_manager);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// all of the keys really shouldn't be wrapped in `Arc`, but due to how the gateway client is currently
|
||||
// constructed, changing that would require more work than what it's worth
|
||||
pub struct KeyManagerBuilder {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// encryption key associated with the client instance.
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
}
|
||||
|
||||
impl KeyManagerBuilder {
|
||||
/// Creates new instance of a [`KeyManager`]
|
||||
pub fn new<R>(rng: &mut R) -> Self
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
KeyManagerBuilder {
|
||||
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
|
||||
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
|
||||
ack_key: Arc::new(AckKey::new(rng)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_gateway_shared_key(self, gateway_shared_key: Arc<SharedKeys>) -> KeyManager {
|
||||
KeyManager {
|
||||
identity_keypair: self.identity_keypair,
|
||||
encryption_keypair: self.encryption_keypair,
|
||||
gateway_shared_key,
|
||||
ack_key: self.ack_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
||||
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
||||
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
|
||||
// And I guess there will need to be some mechanism for a grace period when you can still
|
||||
// use the old key after new one was issued.
|
||||
|
||||
// Remember that Arc<T> has Deref implementation for T
|
||||
#[derive(Clone)]
|
||||
pub struct KeyManager {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// encryption key associated with the client instance.
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// shared key derived with the gateway during "registration handshake"
|
||||
gateway_shared_key: Arc<SharedKeys>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
}
|
||||
|
||||
impl KeyManager {
|
||||
pub fn from_keys(
|
||||
id_keypair: identity::KeyPair,
|
||||
enc_keypair: encryption::KeyPair,
|
||||
gateway_shared_key: SharedKeys,
|
||||
ack_key: AckKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
identity_keypair: Arc::new(id_keypair),
|
||||
encryption_keypair: Arc::new(enc_keypair),
|
||||
gateway_shared_key: Arc::new(gateway_shared_key),
|
||||
ack_key: Arc::new(ack_key),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_keys<S: KeyStore>(store: &S) -> Result<Self, S::StorageError> {
|
||||
store.load_keys().await
|
||||
}
|
||||
|
||||
pub async fn persist_keys<S: KeyStore>(&self, store: &S) -> Result<(), S::StorageError> {
|
||||
store.store_keys(self).await
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
/// Gets an atomically reference counted pointer to [`AckKey`].
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`SharedKey`].
|
||||
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
|
||||
Arc::clone(&self.gateway_shared_key)
|
||||
}
|
||||
|
||||
pub fn remove_gateway_key(self) -> KeyManagerBuilder {
|
||||
if Arc::strong_count(&self.gateway_shared_key) > 1 {
|
||||
panic!("attempted to remove gateway key whilst still holding multiple references!")
|
||||
}
|
||||
KeyManagerBuilder {
|
||||
identity_keypair: self.identity_keypair,
|
||||
encryption_keypair: self.encryption_keypair,
|
||||
ack_key: self.ack_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _assert_keys_zeroize_on_drop() {
|
||||
fn _assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
_assert_zeroize_on_drop::<identity::KeyPair>();
|
||||
_assert_zeroize_on_drop::<encryption::KeyPair>();
|
||||
_assert_zeroize_on_drop::<AckKey>();
|
||||
_assert_zeroize_on_drop::<SharedKeys>();
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::KeyManager;
|
||||
use async_trait::async_trait;
|
||||
use std::error::Error;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::config::Config;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::KeyPairPath;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
|
||||
// we have to define it as an async trait since wasm storage is async
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait KeyStore {
|
||||
type StorageError: Error;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError>;
|
||||
|
||||
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum OnDiskKeysError {
|
||||
#[error("failed to load {keys} keys from {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
|
||||
KeyPairLoadFailure {
|
||||
keys: String,
|
||||
paths: nym_pemstore::KeyPairPath,
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to store {keys} keys to {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
|
||||
KeyPairStoreFailure {
|
||||
keys: String,
|
||||
paths: nym_pemstore::KeyPairPath,
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to load {key} key from {path}: {err}")]
|
||||
KeyLoadFailure {
|
||||
key: String,
|
||||
path: String,
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to store {key} key to {path}: {err}")]
|
||||
KeyStoreFailure {
|
||||
key: String,
|
||||
path: String,
|
||||
err: std::io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct OnDiskKeys {
|
||||
pathfinder: ClientKeyPathfinder,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl From<ClientKeyPathfinder> for OnDiskKeys {
|
||||
fn from(pathfinder: ClientKeyPathfinder) -> Self {
|
||||
OnDiskKeys { pathfinder }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl OnDiskKeys {
|
||||
pub fn new(pathfinder: ClientKeyPathfinder) -> Self {
|
||||
OnDiskKeys { pathfinder }
|
||||
}
|
||||
|
||||
pub fn from_config<T>(config: &Config<T>) -> Self {
|
||||
OnDiskKeys::new(ClientKeyPathfinder::new_from_config(config))
|
||||
}
|
||||
|
||||
fn load_key<T: PemStorableKey>(
|
||||
&self,
|
||||
path: &std::path::Path,
|
||||
name: impl Into<String>,
|
||||
) -> Result<T, OnDiskKeysError> {
|
||||
nym_pemstore::load_key(path).map_err(|err| OnDiskKeysError::KeyLoadFailure {
|
||||
key: name.into(),
|
||||
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_keypair<T: PemStorableKeyPair>(
|
||||
&self,
|
||||
paths: KeyPairPath,
|
||||
name: impl Into<String>,
|
||||
) -> Result<T, OnDiskKeysError> {
|
||||
nym_pemstore::load_keypair(&paths).map_err(|err| OnDiskKeysError::KeyPairLoadFailure {
|
||||
keys: name.into(),
|
||||
paths,
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
fn store_key<T: PemStorableKey>(
|
||||
&self,
|
||||
key: &T,
|
||||
path: &std::path::Path,
|
||||
name: impl Into<String>,
|
||||
) -> Result<(), OnDiskKeysError> {
|
||||
nym_pemstore::store_key(key, path).map_err(|err| OnDiskKeysError::KeyStoreFailure {
|
||||
key: name.into(),
|
||||
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
fn store_keypair<T: PemStorableKeyPair>(
|
||||
&self,
|
||||
keys: &T,
|
||||
paths: KeyPairPath,
|
||||
name: impl Into<String>,
|
||||
) -> Result<(), OnDiskKeysError> {
|
||||
nym_pemstore::store_keypair(keys, &paths).map_err(|err| {
|
||||
OnDiskKeysError::KeyPairStoreFailure {
|
||||
keys: name.into(),
|
||||
paths,
|
||||
err,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
|
||||
let identity_paths = self.pathfinder.identity_key_pair_path();
|
||||
let encryption_paths = self.pathfinder.encryption_key_pair_path();
|
||||
|
||||
let identity_keypair: identity::KeyPair =
|
||||
self.load_keypair(identity_paths, "identity keys")?;
|
||||
let encryption_keypair: encryption::KeyPair =
|
||||
self.load_keypair(encryption_paths, "encryption keys")?;
|
||||
|
||||
let ack_key: AckKey = self.load_key(self.pathfinder.ack_key(), "ack key")?;
|
||||
let gateway_shared_key: SharedKeys =
|
||||
self.load_key(self.pathfinder.gateway_shared_key(), "gateway shared keys")?;
|
||||
|
||||
Ok(KeyManager::from_keys(
|
||||
identity_keypair,
|
||||
encryption_keypair,
|
||||
gateway_shared_key,
|
||||
ack_key,
|
||||
))
|
||||
}
|
||||
|
||||
fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> {
|
||||
let identity_paths = self.pathfinder.identity_key_pair_path();
|
||||
let encryption_paths = self.pathfinder.encryption_key_pair_path();
|
||||
|
||||
self.store_keypair(
|
||||
keys.identity_keypair.as_ref(),
|
||||
identity_paths,
|
||||
"identity keys",
|
||||
)?;
|
||||
self.store_keypair(
|
||||
keys.encryption_keypair.as_ref(),
|
||||
encryption_paths,
|
||||
"encryption keys",
|
||||
)?;
|
||||
|
||||
self.store_key(keys.ack_key.as_ref(), self.pathfinder.ack_key(), "ack key")?;
|
||||
self.store_key(
|
||||
keys.gateway_shared_key.as_ref(),
|
||||
self.pathfinder.gateway_shared_key(),
|
||||
"gateway shared keys",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl KeyStore for OnDiskKeys {
|
||||
type StorageError = OnDiskKeysError;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
|
||||
self.load_keys()
|
||||
}
|
||||
|
||||
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
|
||||
self.store_keys(keys)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InMemEphemeralKeys;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("ephemeral keys can't be loaded from storage")]
|
||||
pub struct EphemeralKeysError;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl KeyStore for InMemEphemeralKeys {
|
||||
type StorageError = EphemeralKeysError;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
|
||||
Err(EphemeralKeysError)
|
||||
}
|
||||
|
||||
async fn store_keys(&self, _keys: &KeyManager) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ impl<C, St> MixTrafficController<C, St>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send + 'static,
|
||||
St: Storage + 'static,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
gateway_client: GatewayClient<C, St>,
|
||||
|
||||
@@ -5,8 +5,6 @@ use crate::client::replies::reply_storage::backend::Empty;
|
||||
use crate::client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
// well, right now we don't have the browser storage : (
|
||||
// so we keep everything in memory
|
||||
#[derive(Debug)]
|
||||
@@ -29,22 +27,6 @@ impl Backend {
|
||||
impl ReplyStorageBackend for Backend {
|
||||
type StorageError = <Empty as ReplyStorageBackend>::StorageError;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
_db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
Ok(Backend {
|
||||
empty: Empty {
|
||||
min_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
storage: &CombinedReplyStorage,
|
||||
@@ -59,8 +41,4 @@ impl ReplyStorageBackend for Backend {
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
self.empty.load_surb_storage().await
|
||||
}
|
||||
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
self.empty.get_inactive_storage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::base_client::non_wasm_helpers;
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::manager::StorageManager;
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
|
||||
@@ -23,49 +22,11 @@ mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum StorageManagerState {
|
||||
Storage(StorageManager),
|
||||
Inactive(InactiveMetadata),
|
||||
}
|
||||
|
||||
// When the storage backaed is initialized as inactive, it will still contain metadata parameters
|
||||
// that will be needed when the in-mem storage is fetched for use.
|
||||
#[derive(Debug)]
|
||||
struct InactiveMetadata {
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
}
|
||||
|
||||
impl StorageManagerState {
|
||||
fn get(&self) -> &StorageManager {
|
||||
match self {
|
||||
StorageManagerState::Storage(manager) => manager,
|
||||
StorageManagerState::Inactive(_) => {
|
||||
panic!("tried to get storage of an inactive backend")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut StorageManager {
|
||||
match self {
|
||||
StorageManagerState::Storage(manager) => manager,
|
||||
StorageManagerState::Inactive(_) => {
|
||||
panic!("tried to get storage of an inactive backend")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
matches!(self, StorageManagerState::Storage(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Backend {
|
||||
temporary_old_path: Option<PathBuf>,
|
||||
database_path: PathBuf,
|
||||
manager: StorageManagerState,
|
||||
manager: StorageManager,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
@@ -85,26 +46,12 @@ impl Backend {
|
||||
let backend = Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager: StorageManagerState::Storage(manager),
|
||||
manager,
|
||||
};
|
||||
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub fn new_inactive(
|
||||
minimum_reply_surb_storage_threshold: usize,
|
||||
maximum_reply_surb_storage_threshold: usize,
|
||||
) -> Self {
|
||||
Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: PathBuf::new(),
|
||||
manager: StorageManagerState::Inactive(InactiveMetadata {
|
||||
minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
@@ -176,12 +123,13 @@ impl Backend {
|
||||
Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager: StorageManagerState::Storage(manager),
|
||||
// manager: StorageManagerState::Storage(manager),
|
||||
manager,
|
||||
})
|
||||
}
|
||||
|
||||
async fn close_pool(&mut self) {
|
||||
self.manager.get_mut().connection_pool.close().await;
|
||||
self.manager.connection_pool.close().await;
|
||||
}
|
||||
|
||||
async fn rotate(&mut self) -> Result<(), StorageError> {
|
||||
@@ -200,9 +148,8 @@ impl Backend {
|
||||
|
||||
fs::rename(&self.database_path, &temp_old)
|
||||
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
|
||||
self.manager =
|
||||
StorageManagerState::Storage(StorageManager::init(&self.database_path, true).await?);
|
||||
self.manager.get_mut().create_status_table().await?;
|
||||
self.manager = StorageManager::init(&self.database_path, true).await?;
|
||||
self.manager.create_status_table().await?;
|
||||
|
||||
self.temporary_old_path = Some(temp_old);
|
||||
Ok(())
|
||||
@@ -219,27 +166,26 @@ impl Backend {
|
||||
}
|
||||
|
||||
async fn start_storage_flush(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.get().set_flush_status(true).await?)
|
||||
Ok(self.manager.set_flush_status(true).await?)
|
||||
}
|
||||
|
||||
async fn end_storage_flush(&self) -> Result<(), StorageError> {
|
||||
self.manager
|
||||
.get()
|
||||
.set_previous_flush_timestamp(OffsetDateTime::now_utc().unix_timestamp())
|
||||
.await?;
|
||||
Ok(self.manager.get().set_flush_status(false).await?)
|
||||
Ok(self.manager.set_flush_status(false).await?)
|
||||
}
|
||||
|
||||
async fn start_client_use(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.get().set_client_in_use_status(true).await?)
|
||||
Ok(self.manager.set_client_in_use_status(true).await?)
|
||||
}
|
||||
|
||||
async fn stop_client_use(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.get().set_client_in_use_status(false).await?)
|
||||
Ok(self.manager.set_client_in_use_status(false).await?)
|
||||
}
|
||||
|
||||
async fn get_stored_tags(&self) -> Result<UsedSenderTags, StorageError> {
|
||||
let stored = self.manager.get().get_tags().await?;
|
||||
let stored = self.manager.get_tags().await?;
|
||||
|
||||
// stop at the first instance of corruption. if even a single entry is malformed,
|
||||
// something weird has happened and we can't trust the rest of the data
|
||||
@@ -255,7 +201,6 @@ impl Backend {
|
||||
for map_ref in tags.as_raw_iter() {
|
||||
let (recipient, tag) = map_ref.pair();
|
||||
self.manager
|
||||
.get()
|
||||
.insert_tag(StoredSenderTag::new(*recipient, *tag))
|
||||
.await?;
|
||||
}
|
||||
@@ -263,7 +208,7 @@ impl Backend {
|
||||
}
|
||||
|
||||
async fn get_stored_reply_keys(&self) -> Result<SentReplyKeys, StorageError> {
|
||||
let stored = self.manager.get().get_reply_keys().await?;
|
||||
let stored = self.manager.get_reply_keys().await?;
|
||||
|
||||
// stop at the first instance of corruption. if even a single entry is malformed,
|
||||
// something weird has happened and we can't trust the rest of the data
|
||||
@@ -279,7 +224,6 @@ impl Backend {
|
||||
for map_ref in reply_keys.as_raw_iter() {
|
||||
let (digest, key) = map_ref.pair();
|
||||
self.manager
|
||||
.get()
|
||||
.insert_reply_key(StoredReplyKey::new(*digest, *key))
|
||||
.await?;
|
||||
}
|
||||
@@ -287,7 +231,7 @@ impl Backend {
|
||||
}
|
||||
|
||||
async fn get_stored_reply_surbs(&self) -> Result<ReceivedReplySurbsMap, StorageError> {
|
||||
let surb_senders = self.manager.get().get_surb_senders().await?;
|
||||
let surb_senders = self.manager.get_surb_senders().await?;
|
||||
|
||||
let metadata = self.get_reply_surb_storage_metadata().await?;
|
||||
let mut received_surbs = Vec::with_capacity(surb_senders.len());
|
||||
@@ -297,7 +241,6 @@ impl Backend {
|
||||
sender.try_into()?;
|
||||
let stored_surbs = self
|
||||
.manager
|
||||
.get()
|
||||
.get_reply_surbs(sender_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -325,7 +268,6 @@ impl Backend {
|
||||
let (tag, received_surbs) = map_ref.pair();
|
||||
let sender_id = self
|
||||
.manager
|
||||
.get()
|
||||
.insert_surb_sender(StoredSurbSender::new(
|
||||
*tag,
|
||||
received_surbs.surbs_last_received_at(),
|
||||
@@ -334,7 +276,6 @@ impl Backend {
|
||||
|
||||
for reply_surb in received_surbs.surbs_ref() {
|
||||
self.manager
|
||||
.get()
|
||||
.insert_reply_surb(StoredReplySurb::new(sender_id, reply_surb))
|
||||
.await?
|
||||
}
|
||||
@@ -346,7 +287,6 @@ impl Backend {
|
||||
&self,
|
||||
) -> Result<ReplySurbStorageMetadata, StorageError> {
|
||||
self.manager
|
||||
.get()
|
||||
.get_reply_surb_storage_metadata()
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
@@ -357,7 +297,6 @@ impl Backend {
|
||||
reply_surbs: &ReceivedReplySurbsMap,
|
||||
) -> Result<(), StorageError> {
|
||||
self.manager
|
||||
.get()
|
||||
.insert_reply_surb_storage_metadata(ReplySurbStorageMetadata::new(
|
||||
reply_surbs.min_surb_threshold(),
|
||||
reply_surbs.max_surb_threshold(),
|
||||
@@ -371,24 +310,6 @@ impl Backend {
|
||||
impl ReplyStorageBackend for Backend {
|
||||
type StorageError = error::StorageError;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(db_path, debug_config)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
log::error!("Failed to create storage: {err}");
|
||||
Self::StorageError::FailedToCreateStorage {
|
||||
source: Box::new(err),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
self.manager.is_active()
|
||||
}
|
||||
|
||||
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
|
||||
self.start_client_use().await
|
||||
}
|
||||
@@ -426,18 +347,6 @@ impl ReplyStorageBackend for Backend {
|
||||
Ok(CombinedReplyStorage::load(reply_keys, reply_surbs, tags))
|
||||
}
|
||||
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
match self.manager {
|
||||
StorageManagerState::Storage(_) => {
|
||||
panic!("tried to get inactive storage from an active storage backend")
|
||||
}
|
||||
StorageManagerState::Inactive(ref state) => Ok(CombinedReplyStorage::new(
|
||||
state.minimum_reply_surb_storage_threshold,
|
||||
state.maximum_reply_surb_storage_threshold,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||
self.stop_client_use().await
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use async_trait::async_trait;
|
||||
use std::{error::Error, path::PathBuf};
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -26,24 +26,19 @@ pub struct Empty {
|
||||
pub max_surb_threshold: usize,
|
||||
}
|
||||
|
||||
impl Default for Empty {
|
||||
fn default() -> Self {
|
||||
Empty {
|
||||
min_surb_threshold: 20,
|
||||
max_surb_threshold: 200,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReplyStorageBackend for Empty {
|
||||
type StorageError = UndefinedError;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
_db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
Ok(Self {
|
||||
min_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
})
|
||||
}
|
||||
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
_storage: &CombinedReplyStorage,
|
||||
@@ -64,28 +59,12 @@ impl ReplyStorageBackend for Empty {
|
||||
self.max_surb_threshold,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
Ok(CombinedReplyStorage::new(
|
||||
self.min_surb_threshold,
|
||||
self.max_surb_threshold,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ReplyStorageBackend: Sized {
|
||||
type StorageError: Error + 'static;
|
||||
|
||||
async fn new(
|
||||
debug_config: &crate::config::DebugConfig,
|
||||
db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError>;
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -103,11 +82,6 @@ pub trait ReplyStorageBackend: Sized {
|
||||
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError>;
|
||||
|
||||
/// In the case the storage backend is initialized in an inactive state (persisting data is
|
||||
/// disabled), we might still need to fetch the (in-mem) storage and the parameters it was
|
||||
/// created with.
|
||||
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError>;
|
||||
|
||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ impl TopologyRefresherConfig {
|
||||
}
|
||||
|
||||
pub struct TopologyRefresher {
|
||||
topology_provider: Box<dyn TopologyProvider>,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
topology_accessor: TopologyAccessor,
|
||||
|
||||
refresh_rate: Duration,
|
||||
@@ -37,7 +37,7 @@ impl TopologyRefresher {
|
||||
pub fn new(
|
||||
cfg: TopologyRefresherConfig,
|
||||
topology_accessor: TopologyAccessor,
|
||||
topology_provider: Box<dyn TopologyProvider>,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Self {
|
||||
TopologyRefresher {
|
||||
topology_provider,
|
||||
@@ -47,7 +47,7 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider>) {
|
||||
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider + Send + Sync>) {
|
||||
self.topology_provider = provider;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,19 +3,30 @@
|
||||
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use crate::error::ClientCoreError;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod old_config_v1_1_13;
|
||||
pub mod persistence;
|
||||
|
||||
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
|
||||
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
|
||||
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
|
||||
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
|
||||
pub const DEFAULT_GATEWAY_KEYS_FILENAME: &str = "gateway_shared.pem";
|
||||
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
|
||||
pub const DEFAULT_REPLY_STORE_FILENAME: &str = "persistent_reply_store.sqlite";
|
||||
pub const DEFAULT_CREDENTIAL_STORE_FILENAME: &str = CRED_DB_FILE_NAME;
|
||||
|
||||
pub const MISSING_VALUE: &str = "MISSING VALUE";
|
||||
|
||||
// 'DEBUG'
|
||||
@@ -107,6 +118,37 @@ impl<T> Config<T> {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[doc(hidden)]
|
||||
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
|
||||
pub fn reset_data_directory<P: AsRef<Path>>(mut self, dir: P) -> Self {
|
||||
self.client.private_identity_key_file =
|
||||
dir.as_ref().join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME);
|
||||
self.client.public_identity_key_file =
|
||||
dir.as_ref().join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME);
|
||||
self.client.private_encryption_key_file =
|
||||
dir.as_ref().join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME);
|
||||
self.client.public_encryption_key_file =
|
||||
dir.as_ref().join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME);
|
||||
self.client.gateway_shared_key_file = dir.as_ref().join(DEFAULT_GATEWAY_KEYS_FILENAME);
|
||||
self.client.ack_key_file = dir.as_ref().join(DEFAULT_ACK_KEY_FILENAME);
|
||||
self.client.reply_surb_database_path = dir.as_ref().join(DEFAULT_REPLY_STORE_FILENAME);
|
||||
self.client.database_path = dir.as_ref().join(DEFAULT_CREDENTIAL_STORE_FILENAME);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[doc(hidden)]
|
||||
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
|
||||
pub fn reset_nym_root_directory<P: AsRef<Path>>(mut self, dir: P) -> Self
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
self.client.nym_root_directory = dir.as_ref().to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_empty_fields_to_defaults(&mut self) -> bool
|
||||
where
|
||||
T: NymConfig,
|
||||
@@ -446,6 +488,14 @@ impl GatewayEndpointConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// separate block so it wouldn't be exported via wasm bindgen
|
||||
impl GatewayEndpointConfig {
|
||||
pub fn try_get_gateway_identity_key(&self) -> Result<identity::PublicKey, ClientCoreError> {
|
||||
identity::PublicKey::from_base58_string(&self.gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
|
||||
fn from(node: nym_topology::gateway::Node) -> GatewayEndpointConfig {
|
||||
let gateway_listener = node.clients_address();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::Config;
|
||||
use nym_config::NymConfig;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -29,7 +28,7 @@ impl ClientKeyPathfinder {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_config<T: NymConfig>(config: &Config<T>) -> Self {
|
||||
pub fn new_from_config<T>(config: &Config<T>) -> Self {
|
||||
ClientKeyPathfinder {
|
||||
identity_private_key: config.get_private_identity_key_file(),
|
||||
identity_public_key: config.get_public_identity_key_file(),
|
||||
@@ -40,6 +39,14 @@ impl ClientKeyPathfinder {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
|
||||
nym_pemstore::KeyPairPath::new(self.private_identity_key(), self.public_identity_key())
|
||||
}
|
||||
|
||||
pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
|
||||
nym_pemstore::KeyPairPath::new(self.private_encryption_key(), self.public_encryption_key())
|
||||
}
|
||||
|
||||
pub fn any_file_exists(&self) -> bool {
|
||||
matches!(self.identity_public_key.try_exists(), Ok(true))
|
||||
|| matches!(self.identity_private_key.try_exists(), Ok(true))
|
||||
|
||||
@@ -6,6 +6,7 @@ use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::NymTopologyError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreError {
|
||||
@@ -41,13 +42,18 @@ pub enum ClientCoreError {
|
||||
|
||||
#[error("experienced a failure with our reply surb persistent storage: {source}")]
|
||||
SurbStorageError {
|
||||
source: Box<dyn std::error::Error + Send + Sync>,
|
||||
source: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
|
||||
#[error("experienced a failure with our cryptographic keys persistent storage: {source}")]
|
||||
KeyStoreError {
|
||||
source: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
|
||||
#[error("The gateway id is invalid - {0}")]
|
||||
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
|
||||
|
||||
#[error("The identity of the gateway is unknwown - did you run init?")]
|
||||
#[error("The identity of the gateway is unknown - did you run init?")]
|
||||
GatewayIdUnknown,
|
||||
|
||||
#[error("The owner of the gateway is unknown - did you run init?")]
|
||||
@@ -86,6 +92,11 @@ pub enum ClientCoreError {
|
||||
|
||||
#[error("Unexpected exit")]
|
||||
UnexpectedExit,
|
||||
|
||||
#[error(
|
||||
"This operation would have resulted in clients keys being overwritten without permission"
|
||||
)]
|
||||
ForbiddenKeyOverwrite,
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
client::key_manager::KeyManager,
|
||||
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
|
||||
error::ClientCoreError,
|
||||
};
|
||||
use crate::config::GatewayEndpointConfig;
|
||||
use crate::error::ClientCoreError;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::{debug, info, trace, warn};
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_topology::{filter::VersionFilterable, gateway};
|
||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tap::TapFallible;
|
||||
use tungstenite::Message;
|
||||
@@ -29,10 +25,8 @@ use tokio::time::Instant;
|
||||
use tokio_tungstenite::connect_async;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use nym_bandwidth_controller::wasm_mockups::DirectSigningNyxdClient;
|
||||
@@ -61,9 +55,9 @@ impl GatewayWithLatency {
|
||||
}
|
||||
}
|
||||
|
||||
async fn current_gateways<R: Rng>(
|
||||
pub(super) async fn current_gateways<R: Rng>(
|
||||
rng: &mut R,
|
||||
nym_apis: Vec<Url>,
|
||||
nym_apis: &[Url],
|
||||
) -> Result<Vec<gateway::Node>, ClientCoreError> {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
@@ -160,7 +154,7 @@ async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, C
|
||||
Ok(GatewayWithLatency::new(gateway, avg))
|
||||
}
|
||||
|
||||
async fn choose_gateway_by_latency<R: Rng>(
|
||||
pub(super) async fn choose_gateway_by_latency<R: Rng>(
|
||||
rng: &mut R,
|
||||
gateways: Vec<gateway::Node>,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
@@ -193,7 +187,7 @@ async fn choose_gateway_by_latency<R: Rng>(
|
||||
Ok(chosen.gateway.clone())
|
||||
}
|
||||
|
||||
fn uniformly_random_gateway<R: Rng>(
|
||||
pub(super) fn uniformly_random_gateway<R: Rng>(
|
||||
rng: &mut R,
|
||||
gateways: Vec<gateway::Node>,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
@@ -203,35 +197,14 @@ fn uniformly_random_gateway<R: Rng>(
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(super) async fn query_gateway_details(
|
||||
validator_servers: Vec<Url>,
|
||||
chosen_gateway_id: Option<identity::PublicKey>,
|
||||
by_latency: bool,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = current_gateways(&mut rng, validator_servers).await?;
|
||||
|
||||
// if we set an explicit gateway, use that one and nothing else
|
||||
if let Some(explicitly_chosen) = chosen_gateway_id {
|
||||
gateways
|
||||
.into_iter()
|
||||
.find(|gateway| gateway.identity_key == explicitly_chosen)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string()))
|
||||
} else if by_latency {
|
||||
choose_gateway_by_latency(&mut rng, gateways).await
|
||||
} else {
|
||||
uniformly_random_gateway(&mut rng, gateways)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn register_with_gateway<St: Storage>(
|
||||
gateway: &gateway::Node,
|
||||
pub(super) async fn register_with_gateway(
|
||||
gateway: &GatewayEndpointConfig,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
) -> Result<Arc<SharedKeys>, ClientCoreError> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let mut gateway_client: GatewayClient<DirectSigningNyxdClient, St> = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
let mut gateway_client: GatewayClient<DirectSigningNyxdClient, _> = GatewayClient::new_init(
|
||||
gateway.gateway_listener.clone(),
|
||||
gateway.try_get_gateway_identity_key()?,
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
@@ -245,16 +218,3 @@ pub(super) async fn register_with_gateway<St: Storage>(
|
||||
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
|
||||
Ok(shared_keys)
|
||||
}
|
||||
|
||||
pub(super) fn store_keys<T>(
|
||||
key_manager: &KeyManager,
|
||||
config: &Config<T>,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
Ok(key_manager
|
||||
.store_keys(&pathfinder)
|
||||
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Collection of initialization steps used by client implementations
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
|
||||
use rand::rngs::OsRng;
|
||||
use serde::Serialize;
|
||||
use tap::TapFallible;
|
||||
|
||||
use nym_config::NymConfig;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use url::Url;
|
||||
|
||||
use crate::client::key_manager::KeyManager;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::{KeyManager, ManagedKeys};
|
||||
use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway};
|
||||
use crate::{
|
||||
config::{
|
||||
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
|
||||
@@ -23,9 +14,102 @@ use crate::{
|
||||
},
|
||||
error::ClientCoreError,
|
||||
};
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Debug, Display};
|
||||
use tap::TapFallible;
|
||||
use url::Url;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum GatewaySetup {
|
||||
/// Specifies usage of a new, random, gateway.
|
||||
New {
|
||||
/// Should the new gateway be selected based on latency.
|
||||
by_latency: bool,
|
||||
},
|
||||
Specified {
|
||||
/// Identity key of the gateway we want to try to use.
|
||||
gateway_identity: IdentityKey,
|
||||
},
|
||||
Predefined {
|
||||
/// Full gateway configuration
|
||||
config: GatewayEndpointConfig,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<GatewayEndpointConfig> for GatewaySetup {
|
||||
fn from(config: GatewayEndpointConfig) -> Self {
|
||||
GatewaySetup::Predefined { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentityKey> for GatewaySetup {
|
||||
fn from(gateway_identity: IdentityKey) -> Self {
|
||||
GatewaySetup::Specified { gateway_identity }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GatewaySetup {
|
||||
fn default() -> Self {
|
||||
GatewaySetup::New { by_latency: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewaySetup {
|
||||
pub fn new(
|
||||
full_config: Option<GatewayEndpointConfig>,
|
||||
gateway_identity: Option<IdentityKey>,
|
||||
latency_based_selection: Option<bool>,
|
||||
) -> Self {
|
||||
if let Some(config) = full_config {
|
||||
GatewaySetup::Predefined { config }
|
||||
} else if let Some(gateway_identity) = gateway_identity {
|
||||
GatewaySetup::Specified { gateway_identity }
|
||||
} else {
|
||||
GatewaySetup::New {
|
||||
by_latency: latency_based_selection.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_get_gateway_details(
|
||||
self,
|
||||
validator_servers: &[Url],
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
match self {
|
||||
GatewaySetup::New { by_latency } => {
|
||||
let mut rng = OsRng;
|
||||
let gateways = current_gateways(&mut rng, validator_servers).await?;
|
||||
if by_latency {
|
||||
choose_gateway_by_latency(&mut rng, gateways).await
|
||||
} else {
|
||||
uniformly_random_gateway(&mut rng, gateways)
|
||||
}
|
||||
}
|
||||
.map(Into::into),
|
||||
GatewaySetup::Specified { gateway_identity } => {
|
||||
let user_gateway = identity::PublicKey::from_base58_string(&gateway_identity)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
let mut rng = OsRng;
|
||||
let gateways = current_gateways(&mut rng, validator_servers).await?;
|
||||
gateways
|
||||
.into_iter()
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
|
||||
}
|
||||
.map(Into::into),
|
||||
GatewaySetup::Predefined { config } => Ok(config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct describing the results of the client initialization procedure.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
@@ -64,34 +148,55 @@ impl Display for InitResults {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new set of client keys.
|
||||
pub fn new_client_keys() -> KeyManager {
|
||||
/// Recovers the already present gateway information or attempts to register with new gateway
|
||||
/// and stores the newly obtained key
|
||||
pub async fn get_registered_gateway<S>(
|
||||
validator_servers: Vec<Url>,
|
||||
key_store: &S::KeyStore,
|
||||
setup: GatewaySetup,
|
||||
overwrite_keys: bool,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError>
|
||||
where
|
||||
S: MixnetClientStorage,
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
KeyManager::new(&mut rng)
|
||||
}
|
||||
|
||||
/// Authenticate and register with a gateway.
|
||||
/// Either pick one at random by querying the available gateways from the nym-api, or use the
|
||||
/// chosen one if it's among the available ones.
|
||||
/// The shared key is added to the supplied `KeyManager` and the endpoint details are returned.
|
||||
pub async fn register_with_gateway<St: Storage>(
|
||||
key_manager: &mut KeyManager,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
chosen_gateway_id: Option<identity::PublicKey>,
|
||||
by_latency: bool,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
// Get the gateway details of the gateway we will use
|
||||
let gateway =
|
||||
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
// try load keys
|
||||
let mut managed_keys = match ManagedKeys::try_load(key_store).await {
|
||||
Ok(_) => {
|
||||
// if we loaded something and we don't have full gateway details, check if we can overwrite the data
|
||||
if let GatewaySetup::Predefined { config } = setup {
|
||||
// we already have defined gateway details AND a shared key, so nothing more for us to do
|
||||
return Ok(config);
|
||||
} else if overwrite_keys {
|
||||
ManagedKeys::generate_new(&mut rng)
|
||||
} else {
|
||||
return Err(ClientCoreError::ForbiddenKeyOverwrite);
|
||||
}
|
||||
}
|
||||
Err(_) => ManagedKeys::generate_new(&mut rng),
|
||||
};
|
||||
|
||||
let our_identity = key_manager.identity_keypair();
|
||||
// choose gateway
|
||||
let gateway_details = setup.try_get_gateway_details(&validator_servers).await?;
|
||||
|
||||
// get our identity key
|
||||
let our_identity = managed_keys.identity_keypair();
|
||||
|
||||
// Establish connection, authenticate and generate keys for talking with the gateway
|
||||
let shared_keys = helpers::register_with_gateway::<St>(&gateway, our_identity).await?;
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
let shared_keys = helpers::register_with_gateway(&gateway_details, our_identity).await?;
|
||||
|
||||
Ok(gateway.into())
|
||||
managed_keys
|
||||
.deal_with_gateway_key(shared_keys, key_store)
|
||||
.await
|
||||
.map_err(|source| ClientCoreError::KeyStoreError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
|
||||
// TODO: here we should be probably persisting gateway details as opposed to returning them
|
||||
|
||||
Ok(gateway_details)
|
||||
}
|
||||
|
||||
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
|
||||
@@ -101,7 +206,8 @@ pub async fn register_with_gateway<St: Storage>(
|
||||
/// b. Create a new gateway configuration but keep existing keys. This assumes that the caller
|
||||
/// knows what they are doing and that the keys match the requested gateway.
|
||||
/// c. Create a new gateway configuration with a newly registered gateway and keys.
|
||||
pub async fn setup_gateway_from_config<C, T, St>(
|
||||
pub async fn setup_gateway_from_config<C, T, KSt>(
|
||||
key_store: &KSt,
|
||||
register_gateway: bool,
|
||||
user_chosen_gateway_id: Option<identity::PublicKey>,
|
||||
config: &Config<T>,
|
||||
@@ -110,7 +216,8 @@ pub async fn setup_gateway_from_config<C, T, St>(
|
||||
where
|
||||
C: NymConfig + ClientCoreConfigTrait,
|
||||
T: NymConfig,
|
||||
St: Storage,
|
||||
KSt: KeyStore,
|
||||
<KSt as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let id = config.get_id();
|
||||
|
||||
@@ -121,35 +228,42 @@ where
|
||||
return load_existing_gateway_config::<C>(&id);
|
||||
}
|
||||
|
||||
let gateway_setup = GatewaySetup::new(
|
||||
None,
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(by_latency),
|
||||
);
|
||||
// Else, we proceed by querying the nym-api
|
||||
let gateway = helpers::query_gateway_details(
|
||||
config.get_nym_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
by_latency,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
let gateway = gateway_setup
|
||||
.try_get_gateway_details(&config.get_nym_api_endpoints())
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {:?}", gateway);
|
||||
|
||||
// If we are not registering, just return this and assume the caller has the keys already and
|
||||
// wants to keep the,
|
||||
if !register_gateway && user_chosen_gateway_id.is_some() {
|
||||
eprintln!("Using gateway provided by user, keeping existing keys");
|
||||
return Ok(gateway.into());
|
||||
return Ok(gateway);
|
||||
}
|
||||
|
||||
let mut rng = OsRng;
|
||||
let mut managed_keys =
|
||||
crate::client::key_manager::ManagedKeys::load_or_generate(&mut rng, key_store).await;
|
||||
|
||||
// Create new keys and derive our identity
|
||||
let mut key_manager = new_client_keys();
|
||||
let our_identity = key_manager.identity_keypair();
|
||||
let our_identity = managed_keys.identity_keypair();
|
||||
|
||||
// Establish connection, authenticate and generate keys for talking with the gateway
|
||||
eprintln!("Registering with new gateway");
|
||||
let shared_keys = helpers::register_with_gateway::<St>(&gateway, our_identity).await?;
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?;
|
||||
managed_keys
|
||||
.deal_with_gateway_key(shared_keys, key_store)
|
||||
.await
|
||||
.map_err(|source| ClientCoreError::KeyStoreError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
|
||||
// Write all keys to storage and just return the gateway endpoint config. It is assumed that we
|
||||
// will load keys from storage when actually connecting.
|
||||
helpers::store_keys(&key_manager, config)?;
|
||||
Ok(gateway.into())
|
||||
Ok(gateway)
|
||||
}
|
||||
|
||||
/// Read and reuse the existing gateway configuration from a file that was generate earlier.
|
||||
@@ -186,7 +300,8 @@ pub fn get_client_address(
|
||||
}
|
||||
|
||||
/// Get the client address by loading the keys from stored files.
|
||||
pub fn get_client_address_from_stored_keys<T>(
|
||||
// TODO: rethink that sucker
|
||||
pub fn get_client_address_from_stored_ondisk_keys<T>(
|
||||
config: &Config<T>,
|
||||
) -> Result<Recipient, ClientCoreError>
|
||||
where
|
||||
@@ -196,11 +311,8 @@ where
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
@@ -208,11 +320,8 @@ where
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<encryption::KeyPair, ClientCoreError> {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||
nym_pemstore::load_keypair(&pathfinder.encryption_key_pair_path())
|
||||
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||
Ok(sphinx_keypair)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_coconut_interface::Credential;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
use nym_gateway_requests::iv::IV;
|
||||
@@ -26,7 +28,6 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tungstenite::protocol::Message;
|
||||
|
||||
use nym_credential_storage::storage::Storage;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_validator_client::nyxd::traits::DkgQueryClient;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -42,7 +43,7 @@ use wasm_utils::websocket::JSWebsocket;
|
||||
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
||||
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
|
||||
pub struct GatewayClient<C, St: Storage> {
|
||||
pub struct GatewayClient<C, St> {
|
||||
authenticated: bool,
|
||||
disabled_credentials_mode: bool,
|
||||
bandwidth_remaining: i64,
|
||||
@@ -68,17 +69,14 @@ pub struct GatewayClient<C, St: Storage> {
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl<C, St> GatewayClient<C, St>
|
||||
where
|
||||
C: Sync + Send,
|
||||
St: Storage,
|
||||
{
|
||||
impl<C, St> GatewayClient<C, St> {
|
||||
// TODO: put it all in a Config struct
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
gateway_address: String,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
gateway_identity: identity::PublicKey,
|
||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||
shared_key: Option<Arc<SharedKeys>>,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -122,40 +120,6 @@ where
|
||||
self.reconnection_backoff = backoff
|
||||
}
|
||||
|
||||
pub fn new_init(
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
response_timeout_duration: Duration,
|
||||
) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
|
||||
// note: this packet_router is completely invalid in normal circumstances, but "works"
|
||||
// perfectly fine here, because it's not meant to be used
|
||||
let (ack_tx, _) = mpsc::unbounded();
|
||||
let (mix_tx, _) = mpsc::unbounded();
|
||||
let shutdown = TaskClient::dummy();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
|
||||
|
||||
GatewayClient::<C, St> {
|
||||
authenticated: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
shared_key: None,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration,
|
||||
bandwidth_controller: None,
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.gateway_identity
|
||||
}
|
||||
@@ -567,7 +531,9 @@ where
|
||||
|
||||
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError>
|
||||
where
|
||||
C: DkgQueryClient,
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
@@ -771,7 +737,9 @@ where
|
||||
|
||||
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError>
|
||||
where
|
||||
C: DkgQueryClient,
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if !self.connection.is_established() {
|
||||
self.establish_connection().await?;
|
||||
@@ -790,3 +758,40 @@ where
|
||||
Ok(shared_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> GatewayClient<C, EphemeralCredentialStorage> {
|
||||
// for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic...
|
||||
pub fn new_init(
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
response_timeout_duration: Duration,
|
||||
) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
|
||||
// note: this packet_router is completely invalid in normal circumstances, but "works"
|
||||
// perfectly fine here, because it's not meant to be used
|
||||
let (ack_tx, _) = mpsc::unbounded();
|
||||
let (mix_tx, _) = mpsc::unbounded();
|
||||
let shutdown = TaskClient::dummy();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
|
||||
|
||||
GatewayClient::<C, EphemeralCredentialStorage> {
|
||||
authenticated: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
shared_key: None,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration,
|
||||
bandwidth_controller: None,
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
pub use client::GatewayClient;
|
||||
use log::warn;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_gateway_requests::BinaryResponse;
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod packet_router;
|
||||
|
||||
@@ -40,7 +40,7 @@ nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
||||
async-trait = { workspace = true, optional = true }
|
||||
bip39 = { workspace = true, features = ["rand"], optional = true }
|
||||
nym-config = { path = "../../config", optional = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
cosmrs = { workspace = true, features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
# note that this has the same version as used by cosmrs
|
||||
eyre = { version = "0.6", optional = true }
|
||||
cw3 = { workspace = true, optional = true }
|
||||
@@ -54,7 +54,7 @@ cosmwasm-std = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bip39 = { workspace = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32"] }
|
||||
cosmrs = { workspace = true, features = ["rpc", "bip32"] }
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ use nym_api_requests::models::{
|
||||
};
|
||||
use nym_coconut_dkg_common::types::NodeIndex;
|
||||
use nym_coconut_interface::VerificationKey;
|
||||
pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId};
|
||||
pub use nym_mixnet_contract_common::{
|
||||
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
@@ -31,7 +33,7 @@ use nym_mixnet_contract_common::{
|
||||
families::{Family, FamilyHead},
|
||||
mixnode::MixNodeBond,
|
||||
pending_events::{PendingEpochEvent, PendingIntervalEvent},
|
||||
Delegation, IdentityKey, RewardedSetNodeStatus, UnbondedMixnode,
|
||||
Delegation, RewardedSetNodeStatus, UnbondedMixnode,
|
||||
};
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Config as ClientConfig, NyxdClient, QueryNyxdClient};
|
||||
use crate::NymApiClient;
|
||||
use crate::{NymApiClient, ValidatorClientError};
|
||||
|
||||
use crate::nyxd::traits::MixnetQueryClient;
|
||||
use colored::Colorize;
|
||||
@@ -45,6 +45,23 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn test_nyxd_url_connection(
|
||||
network: NymNetworkDetails,
|
||||
nyxd_url: Url,
|
||||
address: cosmrs::AccountId,
|
||||
) -> Result<bool, ValidatorClientError> {
|
||||
let config = ClientConfig::try_from_nym_network_details(&network)
|
||||
.expect("failed to create valid nyxd client config");
|
||||
|
||||
let mut nyxd_client = NyxdClient::<QueryNyxdClient>::connect(config, nyxd_url.as_str())?;
|
||||
// possibly redundant, but lets just leave it here
|
||||
nyxd_client.set_mixnet_contract_address(address);
|
||||
match test_nyxd_connection(network, &nyxd_url, &nyxd_client).await {
|
||||
ConnectionResult::Nyxd(_, _, res) => Ok(res),
|
||||
_ => Ok(false), // ✶ not possible to happens
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_connection_tests<H: BuildHasher + 'static>(
|
||||
nyxd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
|
||||
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
|
||||
@@ -105,7 +122,7 @@ async fn test_nyxd_connection(
|
||||
{
|
||||
Ok(Err(NyxdError::TendermintError(e))) => {
|
||||
// If we get a tendermint-rpc error, we classify the node as not contactable
|
||||
log::debug!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
|
||||
log::warn!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
|
||||
false
|
||||
}
|
||||
Ok(Err(NyxdError::AbciError { code, log, .. })) => {
|
||||
@@ -118,12 +135,12 @@ async fn test_nyxd_connection(
|
||||
code == 18
|
||||
}
|
||||
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
|
||||
log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
|
||||
log::warn!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
|
||||
false
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
// For any other error, we're optimistic and just try anyway.
|
||||
log::debug!(
|
||||
log::warn!(
|
||||
"Checking: nyxd_url: {url}: {}, but with error: {e}",
|
||||
"success".green()
|
||||
);
|
||||
@@ -134,7 +151,7 @@ async fn test_nyxd_connection(
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
|
||||
log::warn!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,7 +15,8 @@ use nym_api_requests::models::{
|
||||
};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use nym_service_provider_directory_common::ServiceInfo;
|
||||
use nym_name_service_common::response::NamesListResponse;
|
||||
use nym_service_provider_directory_common::response::ServicesListResponse;
|
||||
use reqwest::{Response, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
@@ -484,10 +485,18 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_service_providers(&self) -> Result<Vec<ServiceInfo>, NymAPIError> {
|
||||
pub async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
|
||||
log::trace!("Getting service providers");
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
//pub async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
|
||||
pub async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
|
||||
log::trace!("Getting registered names");
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// utility function that should solve the double slash problem in validator API forever.
|
||||
|
||||
@@ -33,4 +33,5 @@ pub const AVG_UPTIME: &str = "avg_uptime";
|
||||
pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
|
||||
pub const SERVICE_PROVIDERS: &str = "service-providers";
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
pub const REGISTERED_NAMES: &str = "names";
|
||||
|
||||
@@ -127,7 +127,7 @@ impl GasAdjustable for Gas {
|
||||
mod sealed {
|
||||
use cosmrs::tx::{self, Gas};
|
||||
use cosmrs::Coin as CosmosCoin;
|
||||
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom};
|
||||
use cosmrs::{AccountId, Denom as CosmosDenom};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
|
||||
@@ -144,29 +144,11 @@ mod sealed {
|
||||
}
|
||||
}
|
||||
|
||||
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
|
||||
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
|
||||
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
|
||||
val.to_string().parse().unwrap()
|
||||
}
|
||||
|
||||
// at the time of writing it the current cosmrs' Decimal is extremely limited...
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "CosmosDecimal")]
|
||||
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
|
||||
|
||||
impl From<Decimal> for CosmosDecimal {
|
||||
fn from(val: Decimal) -> Self {
|
||||
val.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct Coin {
|
||||
#[serde(with = "Denom")]
|
||||
denom: CosmosDenom,
|
||||
#[serde(with = "Decimal")]
|
||||
amount: CosmosDecimal,
|
||||
amount: u128,
|
||||
}
|
||||
|
||||
impl From<Coin> for CosmosCoin {
|
||||
|
||||
@@ -16,7 +16,7 @@ use cosmrs::rpc::query::Query;
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmrs::tx::Msg;
|
||||
use log::debug;
|
||||
use log::{debug, trace};
|
||||
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
@@ -39,7 +39,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
|
||||
pub use cosmrs::tendermint::Time as TendermintTime;
|
||||
pub use cosmrs::tx::{self, Gas};
|
||||
pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
|
||||
pub use cosmrs::{bip32, AccountId, Denom};
|
||||
use cosmwasm_std::Addr;
|
||||
pub use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
@@ -79,8 +79,8 @@ impl Config {
|
||||
expected_prefix: &str,
|
||||
) -> Result<Option<AccountId>, NyxdError> {
|
||||
if let Some(address) = raw {
|
||||
debug!("Raw address:{:?}", raw);
|
||||
debug!("Expected prefix:{:?}", expected_prefix);
|
||||
trace!("Raw address:{:?}", raw);
|
||||
trace!("Expected prefix:{:?}", expected_prefix);
|
||||
let parsed: AccountId = address
|
||||
.parse()
|
||||
.map_err(|_| NyxdError::MalformedAccountAddress(address.clone()))?;
|
||||
|
||||
@@ -14,7 +14,7 @@ clap = { version = "4.0", features = ["derive"] }
|
||||
cw-utils = { workspace = true }
|
||||
handlebars = "3.0.1"
|
||||
humantime-serde = "1.0"
|
||||
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
|
||||
log = { workspace = true }
|
||||
rand = {version = "0.6", features = ["std"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -25,7 +25,7 @@ toml = "0.5.6"
|
||||
url = "2.2"
|
||||
tap = "1"
|
||||
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
nym-validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
|
||||
@@ -39,3 +39,4 @@ nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/co
|
||||
nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
|
||||
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
|
||||
|
||||
@@ -56,6 +56,8 @@ pub async fn generate(args: Args) {
|
||||
.expect("threshold can't be converted to Decimal"),
|
||||
},
|
||||
max_voting_period: Duration::Time(args.max_voting_period),
|
||||
executor: None,
|
||||
proposal_deposit: None,
|
||||
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
|
||||
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
|
||||
|
||||
pub mod gateway;
|
||||
pub mod mixnode;
|
||||
pub mod name;
|
||||
pub mod service;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
@@ -22,4 +23,6 @@ pub enum MixnetOperatorsCommands {
|
||||
Gateway(gateway::MixnetOperatorsGateway),
|
||||
/// Manage your service
|
||||
ServiceProvider(service::MixnetOperatorsService),
|
||||
/// Manage your registered name
|
||||
Name(name::MixnetOperatorsName),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
use nym_name_service_common::NameId;
|
||||
use nym_validator_client::nyxd::{error::NyxdError, traits::NameServiceSigningClient};
|
||||
use tap::TapFallible;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub id: NameId,
|
||||
}
|
||||
|
||||
pub async fn delete(args: Args, client: SigningClient) -> Result<(), NyxdError> {
|
||||
info!("Deleting registered name alias with id {}", args.id);
|
||||
|
||||
let res = client
|
||||
.delete_name_by_id(args.id, None)
|
||||
.await
|
||||
.tap_err(|err| error!("Failed to delete name: {err:#?}"))?;
|
||||
|
||||
info!("Deleted: {res:?}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod delete;
|
||||
pub mod register;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsName {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsNameCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsNameCommands {
|
||||
/// Register a name alias for a nym address
|
||||
Register(register::Args),
|
||||
/// Delete name alias for a nym address
|
||||
Delete(delete::Args),
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
use nym_name_service_common::{Address, Coin, NymName};
|
||||
use nym_validator_client::nyxd::{error::NyxdError, traits::NameServiceSigningClient};
|
||||
use tap::TapFallible;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Name alias
|
||||
#[clap(long)]
|
||||
pub name: String,
|
||||
|
||||
/// Nym address that the alias is pointing to
|
||||
#[clap(long)]
|
||||
pub nym_address: String,
|
||||
|
||||
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
|
||||
#[clap(long)]
|
||||
pub deposit: u128,
|
||||
}
|
||||
|
||||
pub async fn register(args: Args, client: SigningClient) -> Result<(), NyxdError> {
|
||||
info!(
|
||||
"Registering name alias '{}' for nym address '{}'",
|
||||
args.name, args.nym_address
|
||||
);
|
||||
|
||||
let name = NymName::new(&args.name).expect("invalid name");
|
||||
let address = Address::new(&args.nym_address);
|
||||
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
let deposit = Coin::new(args.deposit, denom);
|
||||
|
||||
let res = client
|
||||
.register_name(name, address, deposit.into(), None)
|
||||
.await
|
||||
.tap_err(|err| error!("Failed to register name: {err:#?}"))?;
|
||||
|
||||
info!("Registered name: {res:?}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
|
||||
|
||||
pub mod query_all_gateways;
|
||||
pub mod query_all_mixnodes;
|
||||
pub mod query_all_names;
|
||||
pub mod query_all_service_providers;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
@@ -22,4 +23,6 @@ pub enum MixnetQueryCommands {
|
||||
Gateways(query_all_gateways::Args),
|
||||
/// Query announced service-providers
|
||||
ServiceProviders(query_all_service_providers::Args),
|
||||
/// Query registed names
|
||||
Names(query_all_names::Args),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::show_error;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "Optionally, the registered name to display")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
log::trace!("Querying all registered names");
|
||||
|
||||
match client.nym_api.get_registered_names().await {
|
||||
Ok(res) => {
|
||||
if let Some(name) = args.name {
|
||||
let name = res.names.iter().find(|name_entry| {
|
||||
name_entry.name.name.to_string().eq_ignore_ascii_case(&name)
|
||||
});
|
||||
println!(
|
||||
"{}",
|
||||
::serde_json::to_string_pretty(&name).expect("json formatting error")
|
||||
);
|
||||
} else {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["Name Id", "Owner", "Nym Address", "Name"]);
|
||||
for name_entry in res.names {
|
||||
table.add_row(vec![
|
||||
name_entry.name_id.to_string(),
|
||||
name_entry.name.owner.to_string(),
|
||||
name_entry.name.address.to_string(),
|
||||
name_entry.name.name.to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
println!("The registered names in the directory are:");
|
||||
println!("{table}");
|
||||
}
|
||||
}
|
||||
Err(NymAPIError::NotFound) => {
|
||||
println!("nym-api reports no name endpoint available");
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
match client.nym_api.get_service_providers().await {
|
||||
Ok(res) => {
|
||||
if let Some(nym_address) = args.nym_address {
|
||||
let service = res.iter().find(|service| {
|
||||
let service = res.services.iter().find(|service| {
|
||||
service
|
||||
.service
|
||||
.nym_address
|
||||
@@ -33,8 +33,8 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
} else {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["Service Id", "Announcer", "Nym Address"]);
|
||||
for service in res {
|
||||
table.set_header(vec!["Service Id", "Announcer", "Type", "Nym Address"]);
|
||||
for service in res.services {
|
||||
table.add_row(vec![
|
||||
service.service_id.to_string(),
|
||||
service.service.announcer.to_string(),
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::any::type_name;
|
||||
use std::fmt::Debug;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::{fs, io};
|
||||
|
||||
@@ -31,17 +31,29 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
|
||||
// default, most probable, implementations; can be easily overridden where required
|
||||
fn default_config_directory(id: &str) -> PathBuf {
|
||||
Self::default_root_directory().join(id).join(CONFIG_DIR)
|
||||
Self::default_config_directory_with_root(Self::default_root_directory(), id)
|
||||
}
|
||||
|
||||
fn default_config_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
|
||||
root.as_ref().join(id).join(CONFIG_DIR)
|
||||
}
|
||||
|
||||
fn default_data_directory(id: &str) -> PathBuf {
|
||||
Self::default_root_directory().join(id).join(DATA_DIR)
|
||||
Self::default_data_directory_with_root(Self::default_root_directory(), id)
|
||||
}
|
||||
|
||||
fn default_data_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
|
||||
root.as_ref().join(id).join(DATA_DIR)
|
||||
}
|
||||
|
||||
fn default_config_file_path(id: &str) -> PathBuf {
|
||||
Self::default_config_directory(id).join(Self::config_file_name())
|
||||
}
|
||||
|
||||
fn default_config_file_path_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
|
||||
Self::default_config_directory_with_root(root, 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>;
|
||||
@@ -99,8 +111,12 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
|
||||
fn load_from_file(id: &str) -> io::Result<Self> {
|
||||
let file = Self::default_config_file_path(id);
|
||||
log::trace!("Loading from file: {:#?}", file);
|
||||
let config_contents = fs::read_to_string(file)?;
|
||||
Self::load_from_filepath(file)
|
||||
}
|
||||
|
||||
fn load_from_filepath<P: AsRef<Path>>(filepath: P) -> io::Result<Self> {
|
||||
log::trace!("Loading from file: {:#?}", filepath.as_ref().to_owned());
|
||||
let config_contents = fs::read_to_string(filepath)?;
|
||||
|
||||
toml::from_str(&config_contents)
|
||||
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct InstantiateMsg {
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
DepositFunds { data: DepositData },
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::msg::ExecuteMsg;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct SpendCredentialData {
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
@@ -43,7 +43,7 @@ pub enum SpendCredentialStatus {
|
||||
Spent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct SpendCredential {
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
@@ -74,7 +74,7 @@ impl SpendCredential {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedSpendCredentialResponse {
|
||||
pub spend_credentials: Vec<SpendCredential>,
|
||||
pub per_page: usize,
|
||||
@@ -95,7 +95,7 @@ impl PagedSpendCredentialResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct SpendCredentialResponse {
|
||||
pub spend_credential: Option<SpendCredential>,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub type Nonce = u32;
|
||||
|
||||
// define this type explicitly for [hopefully] better usability
|
||||
// (so you wouldn't need to worry about whether you should use bytes, bs58, etc.)
|
||||
#[derive(Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MessageSignature(Vec<u8>);
|
||||
|
||||
impl MessageSignature {
|
||||
|
||||
@@ -6,6 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cosmwasm_schema::{cw_serde, QueryResponses};
|
||||
use cw4::Member;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg {
|
||||
/// The admin is the only account that can update the group state.
|
||||
/// Omit it to make the group immutable.
|
||||
@@ -15,8 +9,7 @@ pub struct InstantiateMsg {
|
||||
pub members: Vec<Member>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
/// Change the admin
|
||||
UpdateAdmin { admin: Option<String> },
|
||||
@@ -32,23 +25,24 @@ pub enum ExecuteMsg {
|
||||
RemoveHook { addr: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
#[derive(QueryResponses)]
|
||||
pub enum QueryMsg {
|
||||
/// Return AdminResponse
|
||||
#[returns(cw_controllers::AdminResponse)]
|
||||
Admin {},
|
||||
/// Return TotalWeightResponse
|
||||
TotalWeight {},
|
||||
/// Returns MembersListResponse
|
||||
#[returns(cw4::TotalWeightResponse)]
|
||||
TotalWeight { at_height: Option<u64> },
|
||||
#[returns(cw4::MemberListResponse)]
|
||||
ListMembers {
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns MemberResponse
|
||||
#[returns(cw4::MemberResponse)]
|
||||
Member {
|
||||
addr: String,
|
||||
at_height: Option<u64>,
|
||||
},
|
||||
/// Shows all registered hooks. Returns HooksResponse.
|
||||
/// Shows all registered hooks.
|
||||
#[returns(cw_controllers::HooksResponse)]
|
||||
Hooks {},
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn generate_owner_storage_subkey(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct Delegation {
|
||||
/// Address of the owner of this delegation.
|
||||
pub owner: Addr,
|
||||
@@ -114,7 +114,7 @@ impl Delegation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixNodeDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<OwnerProxySubKey>,
|
||||
@@ -129,7 +129,7 @@ impl PagedMixNodeDelegationsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedDelegatorDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(MixId, OwnerProxySubKey)>,
|
||||
@@ -147,7 +147,7 @@ impl PagedDelegatorDelegationsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeDelegationResponse {
|
||||
pub delegation: Option<Delegation>,
|
||||
pub mixnode_still_bonded: bool,
|
||||
@@ -162,7 +162,7 @@ impl MixNodeDelegationResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<StorageKey>,
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct Gateway {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub pledge_amount: Coin,
|
||||
pub owner: Addr,
|
||||
@@ -132,7 +132,7 @@ impl GatewayConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedGatewayResponse {
|
||||
pub nodes: Vec<GatewayBond>,
|
||||
pub per_page: usize,
|
||||
@@ -153,13 +153,13 @@ impl PagedGatewayResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub gateway: Option<GatewayBond>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBondResponse {
|
||||
pub identity: IdentityKey,
|
||||
pub gateway: Option<GatewayBond>,
|
||||
|
||||
@@ -489,7 +489,7 @@ impl CurrentIntervalResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct PendingEpochEventsResponse {
|
||||
pub seconds_until_executable: i64,
|
||||
pub events: Vec<PendingEpochEvent>,
|
||||
@@ -510,7 +510,7 @@ impl PendingEpochEventsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct PendingIntervalEventsResponse {
|
||||
pub seconds_until_executable: i64,
|
||||
pub events: Vec<PendingIntervalEvent>,
|
||||
|
||||
@@ -33,7 +33,7 @@ impl RewardedSetNodeStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeDetails {
|
||||
pub bond_information: MixNodeBond,
|
||||
pub rewarding_details: MixNodeRewarding,
|
||||
@@ -86,7 +86,7 @@ impl MixNodeDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeRewarding {
|
||||
/// Information provided by the operator that influence the cost function.
|
||||
pub cost_params: MixNodeCostParams,
|
||||
@@ -465,7 +465,7 @@ impl MixNodeRewarding {
|
||||
}
|
||||
|
||||
// operator information + data assigned by the contract(s)
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
/// Unique id assigned to the bonded mixnode.
|
||||
pub mix_id: MixId,
|
||||
@@ -559,7 +559,7 @@ pub struct MixNode {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeCostParams {
|
||||
pub profit_margin_percent: Percent,
|
||||
|
||||
@@ -686,7 +686,7 @@ impl MixNodeConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixnodeBondsResponse {
|
||||
pub nodes: Vec<MixNodeBond>,
|
||||
pub per_page: usize,
|
||||
@@ -703,7 +703,7 @@ impl PagedMixnodeBondsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixnodesDetailsResponse {
|
||||
pub nodes: Vec<MixNodeDetails>,
|
||||
pub per_page: usize,
|
||||
@@ -745,19 +745,19 @@ impl PagedUnbondedMixnodesResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub mixnode_details: Option<MixNodeDetails>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixnodeDetailsResponse {
|
||||
pub mix_id: MixId,
|
||||
pub mixnode_details: Option<MixNodeDetails>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixnodeRewardingDetailsResponse {
|
||||
pub mix_id: MixId,
|
||||
pub rewarding_details: Option<MixNodeRewarding>,
|
||||
|
||||
@@ -7,19 +7,19 @@ use crate::{BlockHeight, EpochEventId, IntervalEventId, MixId};
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingEpochEvent {
|
||||
pub id: EpochEventId,
|
||||
pub event: PendingEpochEventData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingEpochEventData {
|
||||
pub created_at: BlockHeight,
|
||||
pub kind: PendingEpochEventKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PendingEpochEventKind {
|
||||
// can't just pass the `Delegation` struct here as it's impossible to determine
|
||||
// `cumulative_reward_ratio` ahead of time
|
||||
@@ -68,19 +68,19 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingIntervalEvent {
|
||||
pub id: IntervalEventId,
|
||||
pub event: PendingIntervalEventData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingIntervalEventData {
|
||||
pub created_at: BlockHeight,
|
||||
pub kind: PendingIntervalEventKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PendingIntervalEventKind {
|
||||
ChangeMixCostParams {
|
||||
mix_id: MixId,
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct RewardDistribution {
|
||||
pub delegates: Decimal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct PendingRewardResponse {
|
||||
pub amount_staked: Option<Coin>,
|
||||
pub amount_earned: Option<Coin>,
|
||||
@@ -46,7 +46,7 @@ pub struct PendingRewardResponse {
|
||||
pub mixnode_still_fully_bonded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct EstimatedCurrentEpochRewardResponse {
|
||||
pub original_stake: Option<Coin>,
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ pub type EpochEventId = u32;
|
||||
pub type IntervalEventId = u32;
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq, Eq)]
|
||||
pub struct LayerAssignment {
|
||||
mix_id: MixId,
|
||||
layer: Layer,
|
||||
@@ -119,7 +119,7 @@ impl Index<Layer> for LayerDistribution {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ContractState {
|
||||
pub owner: Addr, // only the owner account can update state
|
||||
pub rewarding_validator_address: Addr,
|
||||
@@ -131,7 +131,7 @@ pub struct ContractState {
|
||||
pub params: ContractStateParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ContractStateParams {
|
||||
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
|
||||
pub minimum_mixnode_delegation: Option<Coin>,
|
||||
|
||||
@@ -8,8 +8,9 @@ edition = "2021"
|
||||
[dependencies]
|
||||
cw-utils = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw3-fixed-multisig = { workspace = true, features = ["library"] }
|
||||
cw4 = { workspace= true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::StdError;
|
||||
use cw_utils::ThresholdError;
|
||||
use cw3::DepositError;
|
||||
use cw_utils::{PaymentError, ThresholdError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -17,9 +18,6 @@ pub enum ContractError {
|
||||
#[error("Group contract invalid address '{addr}'")]
|
||||
InvalidGroup { addr: String },
|
||||
|
||||
#[error("Coconut bandwidth contract address not found")]
|
||||
InvalidCoconutBandwidth {},
|
||||
|
||||
#[error("Unauthorized")]
|
||||
Unauthorized {},
|
||||
|
||||
@@ -43,4 +41,10 @@ pub enum ContractError {
|
||||
|
||||
#[error("Cannot close completed or passed proposals")]
|
||||
WrongCloseStatus {},
|
||||
|
||||
#[error("{0}")]
|
||||
Payment(#[from] PaymentError),
|
||||
|
||||
#[error("{0}")]
|
||||
Deposit(#[from] DepositError),
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod error;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cosmwasm_schema::{cw_serde, QueryResponses};
|
||||
use cosmwasm_std::{CosmosMsg, Empty};
|
||||
use cw3::Vote;
|
||||
use cw3::{UncheckedDepositInfo, Vote};
|
||||
use cw4::MemberChangedHookMsg;
|
||||
use cw_utils::{Duration, Expiration, Threshold};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
use crate::state::Executor;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg {
|
||||
// this is the group contract that contains the member list
|
||||
pub group_addr: String,
|
||||
@@ -17,11 +17,15 @@ pub struct InstantiateMsg {
|
||||
pub coconut_dkg_contract_address: String,
|
||||
pub threshold: Threshold,
|
||||
pub max_voting_period: Duration,
|
||||
// who is able to execute passed proposals
|
||||
// None means that anyone can execute
|
||||
pub executor: Option<Executor>,
|
||||
/// The cost of creating a proposal (if any).
|
||||
pub proposal_deposit: Option<UncheckedDepositInfo>,
|
||||
}
|
||||
|
||||
// TODO: add some T variants? Maybe good enough as fixed Empty for now
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
Propose {
|
||||
title: String,
|
||||
@@ -45,41 +49,44 @@ pub enum ExecuteMsg {
|
||||
}
|
||||
|
||||
// We can also add this as a cw3 extension
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
#[derive(QueryResponses)]
|
||||
pub enum QueryMsg {
|
||||
/// Return ThresholdResponse
|
||||
#[returns(cw_utils::ThresholdResponse)]
|
||||
Threshold {},
|
||||
/// Returns ProposalResponse
|
||||
#[returns(cw3::ProposalResponse)]
|
||||
Proposal { proposal_id: u64 },
|
||||
/// Returns ProposalListResponse
|
||||
#[returns(cw3::ProposalListResponse)]
|
||||
ListProposals {
|
||||
start_after: Option<u64>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns ProposalListResponse
|
||||
#[returns(cw3::ProposalListResponse)]
|
||||
ReverseProposals {
|
||||
start_before: Option<u64>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns VoteResponse
|
||||
#[returns(cw3::VoteResponse)]
|
||||
Vote { proposal_id: u64, voter: String },
|
||||
/// Returns VoteListResponse
|
||||
#[returns(cw3::VoteListResponse)]
|
||||
ListVotes {
|
||||
proposal_id: u64,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns VoterInfo
|
||||
#[returns(cw3::VoterResponse)]
|
||||
Voter { address: String },
|
||||
/// Returns VoterListResponse
|
||||
#[returns(cw3::VoterListResponse)]
|
||||
ListVoters {
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Gets the current configuration.
|
||||
#[returns(crate::state::Config)]
|
||||
Config {},
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
||||
#[cw_serde]
|
||||
pub struct MigrateMsg {
|
||||
pub coconut_bandwidth_address: String,
|
||||
pub coconut_dkg_address: String,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, QuerierWrapper};
|
||||
use cw3::DepositInfo;
|
||||
use cw4::Cw4Contract;
|
||||
use cw_storage_plus::Item;
|
||||
use cw_utils::{Duration, Threshold};
|
||||
|
||||
use crate::error::ContractError;
|
||||
|
||||
/// Defines who is able to execute proposals once passed
|
||||
#[cw_serde]
|
||||
pub enum Executor {
|
||||
/// Any member of the voting group, even with 0 points
|
||||
Member,
|
||||
/// Only the given address
|
||||
Only(Addr),
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct Config {
|
||||
pub threshold: Threshold,
|
||||
pub max_voting_period: Duration,
|
||||
// Total weight and voters are queried from this contract
|
||||
pub group_addr: Cw4Contract,
|
||||
pub coconut_bandwidth_addr: Addr,
|
||||
pub coconut_dkg_addr: Addr,
|
||||
// who is able to execute passed proposals
|
||||
// None means that anyone can execute
|
||||
pub executor: Option<Executor>,
|
||||
/// The price, if any, of creating a new proposal.
|
||||
pub proposal_deposit: Option<DepositInfo>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// Executor can be set in 3 ways:
|
||||
// - Member: any member of the voting group is authorized
|
||||
// - Only: only passed address is authorized
|
||||
// - None: Everyone are authorized
|
||||
pub fn authorize(&self, querier: &QuerierWrapper, sender: &Addr) -> Result<(), ContractError> {
|
||||
if let Some(executor) = &self.executor {
|
||||
match executor {
|
||||
Executor::Member => {
|
||||
self.group_addr
|
||||
.is_member(querier, sender, None)?
|
||||
.ok_or(ContractError::Unauthorized {})?;
|
||||
}
|
||||
Executor::Only(addr) => {
|
||||
if addr != sender {
|
||||
return Err(ContractError::Unauthorized {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// unique items
|
||||
pub const CONFIG: Item<Config> = Item::new("config");
|
||||
@@ -5,3 +5,5 @@ pub mod types;
|
||||
|
||||
// Re-export all types at the top-level
|
||||
pub use types::*;
|
||||
|
||||
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
|
||||
|
||||
@@ -28,7 +28,7 @@ pub enum Period {
|
||||
After,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct PledgeData {
|
||||
pub amount: Coin,
|
||||
pub block_time: Timestamp,
|
||||
@@ -49,7 +49,7 @@ impl PledgeData {
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub enum PledgeCap {
|
||||
Percent(Percent),
|
||||
Absolute(Uint128), // This has to be in unym
|
||||
@@ -77,7 +77,7 @@ impl Default for PledgeCap {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct OriginalVestingResponse {
|
||||
pub amount: Coin,
|
||||
pub number_of_periods: usize,
|
||||
|
||||
@@ -55,7 +55,7 @@ impl VestingSpecification {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
// Families
|
||||
|
||||
@@ -24,6 +24,8 @@ impl Default for EphemeralStorage {
|
||||
|
||||
#[async_trait]
|
||||
impl Storage for EphemeralStorage {
|
||||
type StorageError = StorageError;
|
||||
|
||||
async fn insert_coconut_credential(
|
||||
&self,
|
||||
voucher_value: String,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::persistent_storage::PersistentStorage;
|
||||
mod backends;
|
||||
pub mod ephemeral_storage;
|
||||
pub mod error;
|
||||
mod models;
|
||||
pub mod models;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod persistent_storage;
|
||||
pub mod storage;
|
||||
|
||||
@@ -56,6 +56,8 @@ impl PersistentStorage {
|
||||
|
||||
#[async_trait]
|
||||
impl Storage for PersistentStorage {
|
||||
type StorageError = StorageError;
|
||||
|
||||
async fn insert_coconut_credential(
|
||||
&self,
|
||||
voucher_value: String,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::error::StorageError;
|
||||
use crate::models::CoconutCredential;
|
||||
use async_trait::async_trait;
|
||||
use std::error::Error;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Storage: Send + Sync {
|
||||
type StorageError: Error;
|
||||
|
||||
/// Inserts provided signature into the database.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -26,15 +27,15 @@ pub trait Storage: Send + Sync {
|
||||
binding_number: String,
|
||||
signature: String,
|
||||
epoch_id: String,
|
||||
) -> Result<(), StorageError>;
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Tries to retrieve one of the stored, unused credentials.
|
||||
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError>;
|
||||
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, Self::StorageError>;
|
||||
|
||||
/// Marks as consumed in the database the specified credential.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id`: Id of the credential to be consumed.
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = { workspace = true }
|
||||
thiserror = "1.0"
|
||||
|
||||
# I guess temporarily until we get serde support in coconut up and running
|
||||
|
||||
@@ -13,7 +13,7 @@ bs58 = "0.4.0"
|
||||
blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true }
|
||||
ctr = { version = "0.9.1", optional = true }
|
||||
digest = { version = "0.10.3", optional = true }
|
||||
generic-array = { version = "0.14", optional = true }
|
||||
generic-array = { workspace = true, optional = true }
|
||||
hkdf = { version = "0.12.3", optional = true }
|
||||
hmac = { version = "0.12.1", optional = true }
|
||||
cipher = { version = "0.4.3", optional = true }
|
||||
@@ -21,10 +21,10 @@ x25519-dalek = { version = "1.1", optional = true }
|
||||
ed25519-dalek = { version = "1.0", optional = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"], optional = true }
|
||||
serde_bytes = { version = "0.11.6", optional = true }
|
||||
serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" }
|
||||
serde_crate = { version = "1.0", optional = true, default_features = false, features = ["derive"], package = "serde" }
|
||||
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
|
||||
thiserror = "1.0.37"
|
||||
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
|
||||
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
||||
|
||||
# internal
|
||||
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
|
||||
@@ -35,6 +35,6 @@ rand_chacha = "0.2"
|
||||
|
||||
[features]
|
||||
serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
|
||||
symmetric = ["aes", "ctr", "cipher", "generic-array"]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, RngCore};
|
||||
@@ -41,8 +42,14 @@ pub enum KeyRecoveryError {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
|
||||
pub struct KeyPair {
|
||||
pub(crate) private_key: PrivateKey,
|
||||
|
||||
// nothing secret about public key
|
||||
#[zeroize(skip)]
|
||||
pub(crate) public_key: PublicKey,
|
||||
}
|
||||
|
||||
@@ -178,6 +185,7 @@ impl PemStorableKey for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct PrivateKey(x25519_dalek::StaticSecret);
|
||||
|
||||
impl Display for PrivateKey {
|
||||
@@ -306,17 +314,24 @@ impl From<nym_sphinx_types::PrivateKey> for PrivateKey {
|
||||
#[cfg(test)]
|
||||
mod sphinx_key_conversion {
|
||||
use super::*;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
pub(super) fn test_rng() -> ChaCha20Rng {
|
||||
let dummy_seed = [42u8; 32];
|
||||
ChaCha20Rng::from_seed(dummy_seed)
|
||||
}
|
||||
|
||||
const NUM_ITERATIONS: usize = 100;
|
||||
|
||||
#[test]
|
||||
fn works_for_forward_conversion() {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let mut rng = test_rng();
|
||||
|
||||
for _ in 0..NUM_ITERATIONS {
|
||||
let keys = KeyPair::new(&mut rng);
|
||||
let private = keys.private_key;
|
||||
let public = keys.public_key;
|
||||
let private = &keys.private_key;
|
||||
let public = &keys.public_key;
|
||||
|
||||
let private_bytes = private.to_bytes();
|
||||
let public_bytes = public.to_bytes();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
|
||||
@@ -9,6 +9,7 @@ use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, RngCore};
|
||||
@@ -44,9 +45,14 @@ pub enum Ed25519RecoveryError {
|
||||
}
|
||||
|
||||
/// Keypair for usage in ed25519 EdDSA.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
|
||||
pub struct KeyPair {
|
||||
private_key: PrivateKey,
|
||||
|
||||
// nothing secret about public key
|
||||
#[zeroize(skip)]
|
||||
public_key: PublicKey,
|
||||
}
|
||||
|
||||
@@ -189,7 +195,7 @@ impl PemStorableKey for PublicKey {
|
||||
}
|
||||
|
||||
/// ed25519 EdDSA Private Key
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct PrivateKey(ed25519_dalek::SecretKey);
|
||||
|
||||
impl Display for PrivateKey {
|
||||
|
||||
@@ -6,8 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bip32 = "0.3.0"
|
||||
k256 = "0.10.4"
|
||||
bip32 = "0.4.0"
|
||||
k256 = { workspace = true }
|
||||
ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
thiserror = "1"
|
||||
@@ -9,7 +9,10 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
thiserror = "1.0.37"
|
||||
serde_crate = { version = "1.0", optional = true, default_features = false, features = ["derive"], package = "serde" }
|
||||
generic-array = { workspace = true, optional = true, features = ["serde"] }
|
||||
thiserror = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
|
||||
nym-sphinx-addressing = { path = "../addressing" }
|
||||
@@ -18,4 +21,5 @@ nym-sphinx-types = { path = "../types" }
|
||||
nym-pemstore = { path = "../../pemstore" }
|
||||
nym-topology = { path = "../../topology" }
|
||||
|
||||
|
||||
[features]
|
||||
serde = ["serde_crate", "generic-array"]
|
||||
@@ -6,7 +6,14 @@ use nym_pemstore::traits::PemStorableKey;
|
||||
use nym_sphinx_params::AckEncryptionAlgorithm;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde_crate::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct AckKey(CipherKey<AckEncryptionAlgorithm>);
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
+17
-14
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
@@ -9,16 +9,17 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
pub mod traits;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KeyPairPath {
|
||||
private_key_path: PathBuf,
|
||||
public_key_path: PathBuf,
|
||||
pub private_key_path: PathBuf,
|
||||
pub public_key_path: PathBuf,
|
||||
}
|
||||
|
||||
impl KeyPairPath {
|
||||
pub fn new(private_key_path: PathBuf, public_key_path: PathBuf) -> Self {
|
||||
pub fn new<P: AsRef<Path>>(private_key_path: P, public_key_path: P) -> Self {
|
||||
KeyPairPath {
|
||||
private_key_path,
|
||||
public_key_path,
|
||||
private_key_path: private_key_path.as_ref().to_owned(),
|
||||
public_key_path: public_key_path.as_ref().to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,8 +28,8 @@ pub fn load_keypair<T>(paths: &KeyPairPath) -> io::Result<T>
|
||||
where
|
||||
T: PemStorableKeyPair,
|
||||
{
|
||||
let private = load_key::<T::PrivatePemKey>(&paths.private_key_path)?;
|
||||
let public = load_key::<T::PublicPemKey>(&paths.public_key_path)?;
|
||||
let private: T::PrivatePemKey = load_key(&paths.private_key_path)?;
|
||||
let public: T::PublicPemKey = load_key(&paths.public_key_path)?;
|
||||
Ok(T::from_keys(private, public))
|
||||
}
|
||||
|
||||
@@ -40,9 +41,10 @@ where
|
||||
store_key(keypair.private_key(), &paths.private_key_path)
|
||||
}
|
||||
|
||||
pub fn load_key<T>(path: &Path) -> io::Result<T>
|
||||
pub fn load_key<T, P>(path: P) -> io::Result<T>
|
||||
where
|
||||
T: PemStorableKey,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let key_pem = read_pem_file(path)?;
|
||||
|
||||
@@ -61,23 +63,24 @@ where
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
pub fn store_key<T>(key: &T, path: &Path) -> io::Result<()>
|
||||
pub fn store_key<T, P>(key: &T, path: P) -> io::Result<()>
|
||||
where
|
||||
T: PemStorableKey,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
write_pem_file(path, key.to_bytes(), T::pem_type())
|
||||
}
|
||||
|
||||
fn read_pem_file(filepath: &Path) -> io::Result<Pem> {
|
||||
fn read_pem_file<P: AsRef<Path>>(filepath: P) -> io::Result<Pem> {
|
||||
let mut pem_bytes = File::open(filepath)?;
|
||||
let mut buf = Vec::new();
|
||||
pem_bytes.read_to_end(&mut buf)?;
|
||||
pem::parse(&buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
|
||||
fn write_pem_file(filepath: &Path, data: Vec<u8>, tag: &str) -> io::Result<()> {
|
||||
fn write_pem_file<P: AsRef<Path>>(filepath: P, data: Vec<u8>, tag: &str) -> io::Result<()> {
|
||||
// ensure the whole directory structure exists
|
||||
if let Some(parent_dir) = filepath.parent() {
|
||||
if let Some(parent_dir) = filepath.as_ref().parent() {
|
||||
std::fs::create_dir_all(parent_dir)?;
|
||||
}
|
||||
let pem = Pem {
|
||||
@@ -86,7 +89,7 @@ fn write_pem_file(filepath: &Path, data: Vec<u8>, tag: &str) -> io::Result<()> {
|
||||
};
|
||||
let key = pem::encode(&pem);
|
||||
|
||||
let mut file = File::create(filepath)?;
|
||||
let mut file = File::create(filepath.as_ref())?;
|
||||
file.write_all(key.as_bytes())?;
|
||||
|
||||
// note: this is only supported on unix (on different systems, like Windows, it will just
|
||||
|
||||
@@ -14,9 +14,9 @@ serde = { workspace = true, features = ["derive"] } # for config serialization/d
|
||||
thiserror = "1.0.34"
|
||||
tap = "1.0.1"
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
|
||||
futures = "0.3"
|
||||
|
||||
nym-client-core = { path = "../client-core", features = ["fs-surb-storage"] }
|
||||
futures = "0.3"
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-config = { path = "../config" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
|
||||
@@ -12,7 +12,7 @@ use nym_socks5_requests::Socks5ProtocolVersion;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub mod old_config_v1_1_13;
|
||||
@@ -36,9 +36,9 @@ impl NymConfig for Config {
|
||||
}
|
||||
|
||||
fn default_root_directory() -> PathBuf {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value");
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let base_dir = PathBuf::from("/tmp");
|
||||
|
||||
base_dir.join(".nym").join("socks5-clients")
|
||||
@@ -77,6 +77,14 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_root_directory<P: AsRef<Path>>(mut self, root_dir: P) -> Self {
|
||||
self.base = self.base.reset_nym_root_directory(root_dir);
|
||||
let data_dir = self.data_directory();
|
||||
self.base = self.base.reset_data_directory(data_dir);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> bool {
|
||||
// no other sections have explicit requirements (yet)
|
||||
self.base.validate()
|
||||
@@ -224,6 +232,10 @@ impl Socks5 {
|
||||
self.send_anonymously = anonymous_replies;
|
||||
}
|
||||
|
||||
pub fn get_raw_provider_mix_address(&self) -> String {
|
||||
self.provider_mix_address.clone()
|
||||
}
|
||||
|
||||
pub fn get_provider_mix_address(&self) -> Recipient {
|
||||
Recipient::try_from_base58_string(&self.provider_mix_address)
|
||||
.expect("malformed provider address")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user