Feature/gateway inbuilt nr (#3877)
* changed NymConfigTemplate trait to call 'template' by reference * network requester lib * introduced generic parameter to 'MixTrafficController' to allow non-remote gateways * allowing for custom gateway sender * types cleanup * minor gateway cmds refactor + initial NR work * wip * running a NR inside gateway note: this NR isnt tied to the gateway yet * rebase fixes * propagating same shutdown handle * wip * starting NR with appropriate local transceiver * fixed premature shutdown * wiring up PacketRouter * both ends wired together * actually working so much cleanup to do now * started removing dead code * wip * temp: hardcode gateway * further cleanup * fixed build of other binaries * setup-network-requester subcmd * overriding NR config in gateway init/run * wip making it wasm-compatible [again] * refactored 'GatewaySetup' * clippy and friends * removed debug code * rust 1.72 lints * ensuring local gateway is available + some comments * correctly putting network requester data in the same underlying details struct * improved gateway errors * changed 'network_requester_config' deserialization * missing clap annotation for 'enabled' flag in 'setup-network-requester' command * saving config file after 'setup-network-requester' * removed dead code * review comments * make embedded NR wait for gateway to come online (for at most 70min) * fixed shutdown on successful gateway wait * updated NR config override
This commit is contained in:
committed by
GitHub
parent
6dc8148372
commit
114d92f93f
Generated
+7
-15
@@ -2347,19 +2347,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "4.0.2"
|
||||
version = "5.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.14.0",
|
||||
@@ -6264,7 +6254,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.21.4",
|
||||
"cfg-if",
|
||||
"dashmap 5.5.0",
|
||||
"dashmap",
|
||||
"dirs 4.0.0",
|
||||
"futures",
|
||||
"gloo-timers",
|
||||
@@ -6562,7 +6552,7 @@ dependencies = [
|
||||
"bs58 0.4.0",
|
||||
"clap 4.3.21",
|
||||
"colored",
|
||||
"dashmap 4.0.2",
|
||||
"dashmap",
|
||||
"dirs 4.0.0",
|
||||
"dotenvy",
|
||||
"futures",
|
||||
@@ -6579,6 +6569,7 @@ dependencies = [
|
||||
"nym-mixnet-client",
|
||||
"nym-mixnode-common",
|
||||
"nym-network-defaults",
|
||||
"nym-network-requester",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx",
|
||||
"nym-statistics-common",
|
||||
@@ -7115,6 +7106,7 @@ dependencies = [
|
||||
name = "nym-socks5-client-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs 4.0.0",
|
||||
"futures",
|
||||
"log",
|
||||
@@ -7734,7 +7726,7 @@ checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"crossbeam-channel",
|
||||
"dashmap 5.5.0",
|
||||
"dashmap",
|
||||
"fnv",
|
||||
"futures-channel",
|
||||
"futures-executor",
|
||||
|
||||
@@ -146,6 +146,7 @@ cw2 = { version = "=1.1.0" }
|
||||
cw3 = { version = "=1.1.0" }
|
||||
cw4 = { version = "=1.1.0" }
|
||||
cw-controllers = { version = "=1.1.0" }
|
||||
dashmap = "5.5.3"
|
||||
dotenvy = "0.15.6"
|
||||
futures = "0.3.28"
|
||||
generic-array = "0.14.7"
|
||||
|
||||
@@ -67,7 +67,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct ClientPaths {
|
||||
impl ClientPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
ClientPaths {
|
||||
common_paths: CommonClientPaths::new_default(base_data_directory),
|
||||
common_paths: CommonClientPaths::new_base(base_data_directory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,28 +4,19 @@
|
||||
use crate::client::config::Config;
|
||||
use crate::error::ClientError;
|
||||
use crate::websocket;
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_client_core::client::base_client::non_wasm_helpers::default_query_dkg_client_from_config;
|
||||
use nym_client_core::client::base_client::storage::OnDiskPersistent;
|
||||
use nym_client_core::client::base_client::{
|
||||
BaseClientBuilder, ClientInput, ClientOutput, ClientState,
|
||||
};
|
||||
use nym_client_core::client::inbound_messages::InputMessage;
|
||||
use nym_client_core::client::received_buffer::{
|
||||
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
|
||||
};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskManager;
|
||||
use nym_task::TaskHandle;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::watch::error::SendError;
|
||||
|
||||
pub use nym_sphinx::addressing::clients::Recipient;
|
||||
pub use nym_sphinx::receiver::ReconstructedMessage;
|
||||
|
||||
pub mod config;
|
||||
|
||||
@@ -92,7 +83,7 @@ impl SocketClient {
|
||||
pub async fn run_socket_forever(self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let shutdown = self.start_socket().await?;
|
||||
|
||||
let res = shutdown.catch_interrupt().await;
|
||||
let res = shutdown.wait_for_shutdown().await;
|
||||
log::info!("Stopping nym-client");
|
||||
res
|
||||
}
|
||||
@@ -125,7 +116,7 @@ impl SocketClient {
|
||||
Ok(base_client)
|
||||
}
|
||||
|
||||
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
|
||||
pub async fn start_socket(self) -> Result<TaskHandle, ClientError> {
|
||||
if !self.config.socket.socket_type.is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
@@ -144,141 +135,13 @@ impl SocketClient {
|
||||
client_output,
|
||||
client_state,
|
||||
&self_address,
|
||||
started_client.task_manager.subscribe(),
|
||||
started_client.task_handle.get_handle(),
|
||||
packet_type,
|
||||
);
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(started_client.task_manager)
|
||||
}
|
||||
|
||||
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
|
||||
if self.config.socket.socket_type.is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
|
||||
let base_builder = self.create_base_client_builder().await?;
|
||||
let packet_type = self.config.base.debug.traffic.packet_type;
|
||||
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();
|
||||
|
||||
// register our receiver
|
||||
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
||||
|
||||
// tell the buffer to start sending stuff to us
|
||||
client_output
|
||||
.received_buffer_request_sender
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
Ok(DirectClient {
|
||||
client_input,
|
||||
_received_buffer_request_sender: client_output.received_buffer_request_sender,
|
||||
reconstructed_receiver,
|
||||
address,
|
||||
shutdown_notifier: started_client.task_manager,
|
||||
packet_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirectClient {
|
||||
client_input: ClientInput,
|
||||
// make sure to not drop the channel
|
||||
_received_buffer_request_sender: ReceivedBufferRequestSender,
|
||||
reconstructed_receiver: ReconstructedMessagesReceiver,
|
||||
address: Recipient,
|
||||
|
||||
// we need to keep reference to this guy otherwise things will start dropping
|
||||
shutdown_notifier: TaskManager,
|
||||
packet_type: PacketType,
|
||||
}
|
||||
|
||||
impl DirectClient {
|
||||
pub fn address(&self) -> &Recipient {
|
||||
&self.address
|
||||
}
|
||||
|
||||
pub fn signal_shutdown(&self) -> Result<(), SendError<()>> {
|
||||
self.shutdown_notifier.signal_shutdown()
|
||||
}
|
||||
|
||||
pub async fn wait_for_shutdown(&mut self) {
|
||||
self.shutdown_notifier.wait_for_shutdown().await
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub async fn send_anonymous_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_anonymous(
|
||||
recipient,
|
||||
message,
|
||||
reply_surbs,
|
||||
lane,
|
||||
Some(self.packet_type),
|
||||
);
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg =
|
||||
InputMessage::new_reply(recipient_tag, message, lane, Some(self.packet_type));
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
/// Note: it waits for the first occurrence of messages being sent to ourselves. If you expect multiple
|
||||
/// messages, you might have to call this function repeatedly.
|
||||
// TODO: I guess this should really return something that `impl Stream<Item=ReconstructedMessage>`
|
||||
pub async fn wait_for_messages(&mut self) -> Vec<ReconstructedMessage> {
|
||||
use futures::StreamExt;
|
||||
|
||||
self.reconstructed_receiver
|
||||
.next()
|
||||
.await
|
||||
.expect("buffer controller seems to have somehow died!")
|
||||
Ok(started_client.task_handle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@ use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_client_core::init::types::{GatewayDetails, GatewaySelectionSpecification, GatewaySetup};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::NymTopology;
|
||||
@@ -114,7 +115,7 @@ impl From<Init> for OverrideConfig {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: nym_client_core::init::InitResults,
|
||||
client_core: nym_client_core::init::types::InitResults,
|
||||
client_listening_port: u16,
|
||||
client_address: String,
|
||||
}
|
||||
@@ -122,7 +123,11 @@ pub struct InitResults {
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
|
||||
Self {
|
||||
client_core: nym_client_core::init::InitResults::new(&config.base, address, gateway),
|
||||
client_core: nym_client_core::init::types::InitResults::new(
|
||||
&config.base,
|
||||
address,
|
||||
gateway,
|
||||
),
|
||||
client_listening_port: config.socket.listening_port,
|
||||
client_address: address.to_string(),
|
||||
}
|
||||
@@ -162,7 +167,7 @@ pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
|
||||
// re-registering if wanted.
|
||||
let user_wants_force_register = args.force_register_gateway;
|
||||
if user_wants_force_register {
|
||||
eprintln!("Instructed to force registering gateway. This might overwrite keys!");
|
||||
eprintln!("Instructed to force registering gateway. This will overwrite keys!");
|
||||
}
|
||||
|
||||
// If the client was already initialized, don't generate new keys and don't re-register with
|
||||
@@ -172,7 +177,7 @@ pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = args.gateway;
|
||||
let gateway_setup = GatewaySetup::new_fresh(
|
||||
let selection_spec = GatewaySelectionSpecification::new(
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(args.latency_based_selection),
|
||||
);
|
||||
@@ -186,7 +191,7 @@ pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
|
||||
let details_store =
|
||||
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
|
||||
|
||||
let network_gateways = if let Some(hardcoded_topology) = args
|
||||
let available_gateways = if let Some(hardcoded_topology) = args
|
||||
.custom_mixnet
|
||||
.map(NymTopology::new_from_file)
|
||||
.transpose()?
|
||||
@@ -198,16 +203,16 @@ pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
|
||||
current_gateways(&mut rng, &config.base.client.nym_api_urls).await?
|
||||
};
|
||||
|
||||
let init_details = nym_client_core::init::setup_gateway_from(
|
||||
gateway_setup,
|
||||
&key_store,
|
||||
&details_store,
|
||||
register_gateway,
|
||||
Some(&network_gateways),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
|
||||
.details;
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
overwrite_data: register_gateway,
|
||||
};
|
||||
|
||||
let init_details =
|
||||
nym_client_core::init::setup_gateway(gateway_setup, &key_store, &details_store)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
let config_save_location = config.default_location();
|
||||
config.save_to_default_location().tap_err(|_| {
|
||||
@@ -222,7 +227,10 @@ pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
|
||||
|
||||
eprintln!("Client configuration completed.\n");
|
||||
|
||||
let init_results = InitResults::new(&config, &address, &init_details.gateway_details);
|
||||
let GatewayDetails::Configured(gateway_details) = init_details.gateway_details else {
|
||||
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
|
||||
};
|
||||
let init_results = InitResults::new(&config, &address, &gateway_details);
|
||||
println!("{}", args.output.format(&init_results));
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -133,7 +133,7 @@ fn persist_gateway_details(
|
||||
source: Box::new(source),
|
||||
})
|
||||
})?;
|
||||
let persisted_details = PersistedGatewayDetails::new(details, &shared_keys);
|
||||
let persisted_details = PersistedGatewayDetails::new(details.into(), Some(&shared_keys))?;
|
||||
details_store
|
||||
.store_to_disk(&persisted_details)
|
||||
.map_err(|source| {
|
||||
|
||||
@@ -14,8 +14,9 @@ use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_client_core::init::types::{GatewayDetails, GatewaySelectionSpecification, GatewaySetup};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::NymTopology;
|
||||
@@ -113,7 +114,7 @@ impl From<Init> for OverrideConfig {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: nym_client_core::init::InitResults,
|
||||
client_core: nym_client_core::init::types::InitResults,
|
||||
socks5_listening_port: u16,
|
||||
client_address: String,
|
||||
}
|
||||
@@ -121,7 +122,7 @@ pub struct InitResults {
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
|
||||
Self {
|
||||
client_core: nym_client_core::init::InitResults::new(
|
||||
client_core: nym_client_core::init::types::InitResults::new(
|
||||
&config.core.base,
|
||||
address,
|
||||
gateway,
|
||||
@@ -176,7 +177,7 @@ pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = args.gateway;
|
||||
let gateway_setup = GatewaySetup::new_fresh(
|
||||
let selection_spec = GatewaySelectionSpecification::new(
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(args.latency_based_selection),
|
||||
);
|
||||
@@ -193,7 +194,7 @@ pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
|
||||
let details_store =
|
||||
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
|
||||
|
||||
let network_gateways = if let Some(hardcoded_topology) = args
|
||||
let available_gateways = if let Some(hardcoded_topology) = args
|
||||
.custom_mixnet
|
||||
.map(NymTopology::new_from_file)
|
||||
.transpose()?
|
||||
@@ -205,16 +206,16 @@ pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
|
||||
current_gateways(&mut rng, &config.core.base.client.nym_api_urls).await?
|
||||
};
|
||||
|
||||
let init_details = nym_client_core::init::setup_gateway_from(
|
||||
gateway_setup,
|
||||
&key_store,
|
||||
&details_store,
|
||||
register_gateway,
|
||||
Some(&network_gateways),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
|
||||
.details;
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
overwrite_data: register_gateway,
|
||||
};
|
||||
|
||||
let init_details =
|
||||
nym_client_core::init::setup_gateway(gateway_setup, &key_store, &details_store)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
// TODO: ask the service provider we specified for its interface version and set it in the config
|
||||
|
||||
@@ -229,7 +230,10 @@ pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
|
||||
|
||||
let address = init_details.client_address()?;
|
||||
|
||||
let init_results = InitResults::new(&config, &address, &init_details.gateway_details);
|
||||
let GatewayDetails::Configured(gateway_details) = init_details.gateway_details else {
|
||||
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
|
||||
};
|
||||
let init_results = InitResults::new(&config, &address, &gateway_details);
|
||||
println!("{}", args.output.format(&init_results));
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -175,7 +175,7 @@ fn persist_gateway_details(
|
||||
source: Box::new(source),
|
||||
})
|
||||
})?;
|
||||
let persisted_details = PersistedGatewayDetails::new(details, &shared_keys);
|
||||
let persisted_details = PersistedGatewayDetails::new(details.into(), Some(&shared_keys))?;
|
||||
details_store
|
||||
.store_to_disk(&persisted_details)
|
||||
.map_err(|source| {
|
||||
|
||||
@@ -62,7 +62,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct SocksClientPaths {
|
||||
impl SocksClientPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
SocksClientPaths {
|
||||
common_paths: CommonClientPaths::new_default(base_data_directory),
|
||||
common_paths: CommonClientPaths::new_base(base_data_directory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,14 @@ impl OutputFormat {
|
||||
OutputFormat::Json => serde_json::to_string(data).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "output_format")]
|
||||
pub fn to_stdout<T: serde::Serialize + ToString>(&self, data: &T) {
|
||||
println!("{}", self.format(data))
|
||||
}
|
||||
|
||||
#[cfg(feature = "output_format")]
|
||||
pub fn to_stderr<T: serde::Serialize + ToString>(&self, data: &T) {
|
||||
eprintln!("{}", self.format(data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ rust-version = "1.66"
|
||||
async-trait = { workspace = true }
|
||||
base64 = "0.21.2"
|
||||
cfg-if = "1.0.0"
|
||||
dashmap = "5.4.0"
|
||||
dashmap = { workspace = true }
|
||||
dirs = "4.0"
|
||||
futures = { workspace = true }
|
||||
humantime-serde = "1.0"
|
||||
@@ -35,7 +35,6 @@ nym-config = { path = "../config" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
|
||||
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
|
||||
@@ -8,6 +8,7 @@ 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::persistence::KeyStore;
|
||||
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use crate::client::real_messages_control;
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
@@ -25,16 +26,18 @@ use crate::client::topology_control::{
|
||||
};
|
||||
use crate::config::{Config, DebugConfig};
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::{setup_gateway, GatewaySetup, InitialisationDetails, InitialisationResult};
|
||||
use crate::init::{
|
||||
setup_gateway,
|
||||
types::{GatewayDetails, GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use crate::{config, spawn_future};
|
||||
use futures::channel::mpsc;
|
||||
use log::{debug, info};
|
||||
use log::{debug, error, info};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver, PacketRouter,
|
||||
};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
@@ -42,10 +45,11 @@ use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
@@ -157,7 +161,12 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
config: &'a Config,
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
|
||||
wait_for_gateway: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
shutdown: Option<TaskClient>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
}
|
||||
|
||||
@@ -175,7 +184,10 @@ where
|
||||
config: base_config,
|
||||
client_store,
|
||||
dkg_query_client,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
shutdown: None,
|
||||
setup_method: GatewaySetup::MustLoad,
|
||||
}
|
||||
}
|
||||
@@ -186,6 +198,12 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_wait_for_gateway(mut self, wait_for_gateway: bool) -> Self {
|
||||
self.wait_for_gateway = wait_for_gateway;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_topology_provider(
|
||||
mut self,
|
||||
@@ -195,6 +213,18 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_gateway_transceiver(mut self, sender: Box<dyn GatewayTransceiver + Send>) -> Self {
|
||||
self.custom_gateway_transceiver = Some(sender);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_shutdown(mut self, shutdown: TaskClient) -> Self {
|
||||
self.shutdown = Some(shutdown);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_stored_topology<P: AsRef<Path>>(
|
||||
mut self,
|
||||
file: P,
|
||||
@@ -206,13 +236,13 @@ where
|
||||
|
||||
// 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 mix_address(details: &InitialisationDetails) -> Recipient {
|
||||
fn mix_address(details: &InitialisationResult) -> Recipient {
|
||||
Recipient::new(
|
||||
*details.managed_keys.identity_public_key(),
|
||||
*details.managed_keys.encryption_public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(&details.gateway_details.gateway_id).unwrap(),
|
||||
NodeIdentity::from_base58_string(details.gateway_details.gateway_id()).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -299,50 +329,37 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
packet_router: PacketRouter,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let managed_keys = initialisation_result.details.managed_keys;
|
||||
let managed_keys = initialisation_result.managed_keys;
|
||||
let GatewayDetails::Configured(gateway_config) = initialisation_result.gateway_details
|
||||
else {
|
||||
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails);
|
||||
};
|
||||
|
||||
let mut gateway_client =
|
||||
if let Some(existing_client) = initialisation_result.authenticated_ephemeral_client {
|
||||
existing_client.upgrade(
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
config.debug.gateway_connection.gateway_response_timeout,
|
||||
bandwidth_controller,
|
||||
shutdown,
|
||||
)
|
||||
existing_client.upgrade(packet_router, bandwidth_controller, shutdown)
|
||||
} else {
|
||||
let gateway_config = initialisation_result.details.gateway_details;
|
||||
|
||||
let gateway_address = gateway_config.gateway_listener.clone();
|
||||
let gateway_id = gateway_config.gateway_id;
|
||||
|
||||
// TODO: in theory, at this point, this should be infallible
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
let cfg = gateway_config.try_into()?;
|
||||
GatewayClient::new(
|
||||
gateway_address,
|
||||
cfg,
|
||||
managed_keys.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(managed_keys.must_get_gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
config.debug.gateway_connection.gateway_response_timeout,
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
shutdown,
|
||||
)
|
||||
.with_disabled_credentials_mode(config.client.disabled_credentials_mode)
|
||||
.with_response_timeout(config.debug.gateway_connection.gateway_response_timeout)
|
||||
};
|
||||
|
||||
let gateway_id = gateway_client.gateway_identity();
|
||||
gateway_client.set_disabled_credentials_mode(config.client.disabled_credentials_mode);
|
||||
|
||||
let shared_key = gateway_client
|
||||
.authenticate_and_start()
|
||||
@@ -355,11 +372,48 @@ where
|
||||
}
|
||||
})?;
|
||||
|
||||
managed_keys.ensure_gateway_key(shared_key);
|
||||
managed_keys.ensure_gateway_key(Some(shared_key));
|
||||
|
||||
Ok(gateway_client)
|
||||
}
|
||||
|
||||
async fn setup_gateway_transceiver(
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
packet_router: PacketRouter,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
// if we have setup custom gateway sender and persisted details agree with it, return it
|
||||
if let Some(mut custom_gateway_transceiver) = custom_gateway_transceiver {
|
||||
return if !initialisation_result.gateway_details.is_custom() {
|
||||
Err(ClientCoreError::CustomGatewaySelectionExpected)
|
||||
} else {
|
||||
// and make sure to invalidate the task client so we wouldn't cause premature shutdown
|
||||
shutdown.mark_as_success();
|
||||
custom_gateway_transceiver.set_packet_router(packet_router)?;
|
||||
Ok(custom_gateway_transceiver)
|
||||
};
|
||||
}
|
||||
|
||||
// otherwise, setup normal gateway client, etc
|
||||
let gateway_client = Self::start_gateway_client(
|
||||
config,
|
||||
initialisation_result,
|
||||
bandwidth_controller,
|
||||
packet_router,
|
||||
shutdown,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Box::new(RemoteGateway::new(gateway_client)))
|
||||
}
|
||||
|
||||
fn setup_topology_provider(
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
provider_from_config: config::TopologyStructure,
|
||||
@@ -387,6 +441,8 @@ where
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
topology_config: config::Topology,
|
||||
topology_accessor: TopologyAccessor,
|
||||
local_gateway: &NodeIdentity,
|
||||
wait_for_gateway: bool,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config =
|
||||
@@ -410,6 +466,32 @@ where
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology(err));
|
||||
}
|
||||
|
||||
let gateway_wait_timeout = if wait_for_gateway {
|
||||
Some(topology_config.max_startup_gateway_waiting_period)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Err(err) = topology_refresher
|
||||
.ensure_contains_gateway(local_gateway)
|
||||
.await
|
||||
{
|
||||
if let Some(waiting_timeout) = gateway_wait_timeout {
|
||||
if let Err(err) = topology_refresher
|
||||
.wait_for_gateway(local_gateway, waiting_timeout)
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"the gateway did not come back online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
} else {
|
||||
error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
|
||||
if topology_config.disable_refreshing {
|
||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||
info!("The topology refesher is not going to be started");
|
||||
@@ -424,19 +506,12 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// controller for sending packets to mixnet (either real traffic or cover traffic)
|
||||
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_client: GatewayClient<C, S::CredentialStore>,
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
) -> BatchMixMessageSender
|
||||
where
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
) -> BatchMixMessageSender {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_transceiver);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
@@ -473,21 +548,12 @@ where
|
||||
setup_method: GatewaySetup,
|
||||
key_store: &S::KeyStore,
|
||||
details_store: &S::GatewayDetailsStore,
|
||||
overwrite_data: bool,
|
||||
validator_servers: Option<&[Url]>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
||||
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
|
||||
{
|
||||
setup_gateway(
|
||||
setup_method,
|
||||
key_store,
|
||||
details_store,
|
||||
overwrite_data,
|
||||
validator_servers,
|
||||
)
|
||||
.await
|
||||
setup_gateway(setup_method, key_store, details_store).await
|
||||
}
|
||||
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
|
||||
@@ -505,17 +571,11 @@ where
|
||||
self.setup_method,
|
||||
self.client_store.key_store(),
|
||||
self.client_store.gateway_details_store(),
|
||||
false,
|
||||
Some(&self.config.client.nym_api_urls),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (reply_storage_backend, credential_store) = self.client_store.into_runtime_stores();
|
||||
|
||||
let bandwidth_controller = self
|
||||
.dkg_query_client
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
// 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
|
||||
@@ -536,31 +596,25 @@ where
|
||||
let shared_topology_accessor = TopologyAccessor::new();
|
||||
|
||||
// Shutdown notifier for signalling tasks to stop
|
||||
let task_manager = TaskManager::default();
|
||||
let shutdown = self
|
||||
.shutdown
|
||||
.map(Into::<TaskHandle>::into)
|
||||
.unwrap_or_default()
|
||||
.name_if_unnamed("BaseNymClient");
|
||||
|
||||
// channels responsible for dealing with reply-related fun
|
||||
let (reply_controller_sender, reply_controller_receiver) =
|
||||
reply_controller::requests::new_control_channels();
|
||||
|
||||
let self_address = Self::mix_address(&init_res.details);
|
||||
let ack_key = init_res.details.managed_keys.ack_key();
|
||||
let encryption_keys = init_res.details.managed_keys.encryption_keypair();
|
||||
let self_address = Self::mix_address(&init_res);
|
||||
let ack_key = init_res.managed_keys.ack_key();
|
||||
let encryption_keys = init_res.managed_keys.encryption_keypair();
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
let gateway_client = Self::start_gateway_client(
|
||||
self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
mixnet_messages_sender,
|
||||
ack_sender,
|
||||
task_manager.subscribe(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let reply_storage =
|
||||
Self::setup_persistent_reply_storage(reply_storage_backend, task_manager.subscribe())
|
||||
.await?;
|
||||
let bandwidth_controller = self
|
||||
.dkg_query_client
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let topology_provider = Self::setup_topology_provider(
|
||||
self.custom_topology_provider.take(),
|
||||
@@ -568,11 +622,36 @@ where
|
||||
self.config.get_nym_api_endpoints(),
|
||||
);
|
||||
|
||||
// needs to be started as the first thing to block if required waiting for the gateway
|
||||
Self::start_topology_refresher(
|
||||
topology_provider,
|
||||
self.config.debug.topology,
|
||||
shared_topology_accessor.clone(),
|
||||
task_manager.subscribe(),
|
||||
self_address.gateway(),
|
||||
self.wait_for_gateway,
|
||||
shutdown.fork("topology_refresher"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let gateway_packet_router = PacketRouter::new(
|
||||
ack_sender,
|
||||
mixnet_messages_sender,
|
||||
shutdown.get_handle().named("gateway-packet-router"),
|
||||
);
|
||||
|
||||
let gateway_transceiver = Self::setup_gateway_transceiver(
|
||||
self.custom_gateway_transceiver,
|
||||
self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
gateway_packet_router,
|
||||
shutdown.fork("gateway_transceiver"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let reply_storage = Self::setup_persistent_reply_storage(
|
||||
reply_storage_backend,
|
||||
shutdown.fork("persistent_reply_storage"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -582,15 +661,17 @@ where
|
||||
mixnet_messages_receiver,
|
||||
reply_storage.key_storage(),
|
||||
reply_controller_sender.clone(),
|
||||
task_manager.subscribe(),
|
||||
shutdown.fork("received_messages_buffer"),
|
||||
);
|
||||
|
||||
// The message_sender is the transmitter for any component generating sphinx packets
|
||||
// that are to be sent to the mixnet. They are used by cover traffic stream and real
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
let message_sender =
|
||||
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
|
||||
let message_sender = Self::start_mix_traffic_controller(
|
||||
gateway_transceiver,
|
||||
shutdown.fork("mix_traffic_controller"),
|
||||
);
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
// controller that connections are closed.
|
||||
@@ -617,7 +698,7 @@ where
|
||||
reply_controller_receiver,
|
||||
shared_lane_queue_lengths.clone(),
|
||||
client_connection_rx,
|
||||
task_manager.subscribe(),
|
||||
shutdown.fork("real_traffic_controller"),
|
||||
self.config.debug.traffic.packet_type,
|
||||
);
|
||||
|
||||
@@ -633,7 +714,7 @@ where
|
||||
self_address,
|
||||
shared_topology_accessor.clone(),
|
||||
message_sender,
|
||||
task_manager.subscribe(),
|
||||
shutdown.fork("cover_traffic_stream"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -658,7 +739,7 @@ where
|
||||
reply_controller_sender,
|
||||
topology_accessor: shared_topology_accessor,
|
||||
},
|
||||
task_manager,
|
||||
task_handle: shutdown,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -669,5 +750,5 @@ pub struct BaseClient {
|
||||
pub client_output: ClientOutputStatus,
|
||||
pub client_state: ClientState,
|
||||
|
||||
pub task_manager: TaskManager,
|
||||
pub task_handle: TaskHandle,
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::GatewayEndpointConfig;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::types::{EmptyCustomDetails, GatewayDetails};
|
||||
use async_trait::async_trait;
|
||||
use log::error;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::error::Error;
|
||||
@@ -13,19 +17,61 @@ use zeroize::Zeroizing;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait GatewayDetailsStore {
|
||||
pub trait GatewayDetailsStore<T = EmptyCustomDetails> {
|
||||
type StorageError: Error;
|
||||
|
||||
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError>;
|
||||
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails<T>, Self::StorageError>
|
||||
where
|
||||
T: DeserializeOwned + Send + Sync;
|
||||
|
||||
async fn store_gateway_details(
|
||||
&self,
|
||||
details: &PersistedGatewayDetails,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
details: &PersistedGatewayDetails<T>,
|
||||
) -> Result<(), Self::StorageError>
|
||||
where
|
||||
T: Serialize + Send + Sync;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PersistedGatewayDetails {
|
||||
#[serde(untagged)]
|
||||
pub enum PersistedGatewayDetails<T = EmptyCustomDetails> {
|
||||
/// Standard details of a remote gateway
|
||||
Default(PersistedGatewayConfig),
|
||||
|
||||
/// Custom gateway setup, such as for a client embedded inside gateway itself
|
||||
Custom(PersistedCustomGatewayDetails<T>),
|
||||
}
|
||||
|
||||
impl<T> PersistedGatewayDetails<T> {
|
||||
// TODO: this should probably allow for custom verification over T
|
||||
pub fn validate(&self, shared_key: Option<&SharedKeys>) -> Result<(), ClientCoreError> {
|
||||
match self {
|
||||
PersistedGatewayDetails::Default(details) => {
|
||||
if !details.verify(
|
||||
shared_key
|
||||
.ok_or(ClientCoreError::UnavailableSharedKey)?
|
||||
.deref(),
|
||||
) {
|
||||
Err(ClientCoreError::MismatchedGatewayDetails {
|
||||
gateway_id: details.details.gateway_id.clone(),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
PersistedGatewayDetails::Custom(_) => {
|
||||
if shared_key.is_some() {
|
||||
error!("using custom persisted gateway setup with shared key present - are you sure that's what you want?");
|
||||
// but technically we could still continue. just ignore the key
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PersistedGatewayConfig {
|
||||
// TODO: should we also verify correctness of the details themselves?
|
||||
// i.e. we could include a checksum or tag (via the shared keys)
|
||||
// counterargument: if we wanted to modify, say, the host information in the stored file on disk,
|
||||
@@ -38,13 +84,16 @@ pub struct PersistedGatewayDetails {
|
||||
pub(crate) details: GatewayEndpointConfig,
|
||||
}
|
||||
|
||||
impl From<PersistedGatewayDetails> for GatewayEndpointConfig {
|
||||
fn from(value: PersistedGatewayDetails) -> Self {
|
||||
value.details
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PersistedCustomGatewayDetails<T> {
|
||||
// whatever custom method is used, gateway's identity must be known
|
||||
pub gateway_id: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub additional_data: T,
|
||||
}
|
||||
|
||||
impl PersistedGatewayDetails {
|
||||
impl PersistedGatewayConfig {
|
||||
pub fn new(details: GatewayEndpointConfig, shared_key: &SharedKeys) -> Self {
|
||||
let key_bytes = Zeroizing::new(shared_key.to_bytes());
|
||||
|
||||
@@ -52,7 +101,7 @@ impl PersistedGatewayDetails {
|
||||
key_hasher.update(&key_bytes);
|
||||
let key_hash = key_hasher.finalize().to_vec();
|
||||
|
||||
PersistedGatewayDetails { key_hash, details }
|
||||
PersistedGatewayConfig { key_hash, details }
|
||||
}
|
||||
|
||||
pub fn verify(&self, shared_key: &SharedKeys) -> bool {
|
||||
@@ -66,6 +115,50 @@ impl PersistedGatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PersistedGatewayDetails<T> {
|
||||
pub fn new(
|
||||
details: GatewayDetails<T>,
|
||||
shared_key: Option<&SharedKeys>,
|
||||
) -> Result<Self, ClientCoreError> {
|
||||
match details {
|
||||
GatewayDetails::Configured(cfg) => {
|
||||
let shared_key = shared_key.ok_or(ClientCoreError::UnavailableSharedKey)?;
|
||||
Ok(PersistedGatewayDetails::Default(
|
||||
PersistedGatewayConfig::new(cfg, shared_key),
|
||||
))
|
||||
}
|
||||
GatewayDetails::Custom(custom) => Ok(PersistedGatewayDetails::Custom(custom.into())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_custom(&self) -> bool {
|
||||
matches!(self, PersistedGatewayDetails::Custom(..))
|
||||
}
|
||||
|
||||
pub fn matches(&self, other: &GatewayDetails<T>) -> bool
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
match self {
|
||||
PersistedGatewayDetails::Default(default) => {
|
||||
if let GatewayDetails::Configured(other_configured) = other {
|
||||
&default.details == other_configured
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
PersistedGatewayDetails::Custom(custom) => {
|
||||
if let GatewayDetails::Custom(other_custom) = other {
|
||||
custom.gateway_id == other_custom.gateway_id
|
||||
&& custom.additional_data == other_custom.additional_data
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper to make Vec<u8> serialization use base64 representation to make it human readable
|
||||
// so that it would be easier for users to copy contents from the disk if they wanted to use it elsewhere
|
||||
mod base64 {
|
||||
@@ -116,7 +209,10 @@ impl OnDiskGatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from_disk(&self) -> Result<PersistedGatewayDetails, OnDiskGatewayDetailsError> {
|
||||
pub fn load_from_disk<T>(&self) -> Result<PersistedGatewayDetails<T>, OnDiskGatewayDetailsError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let file = std::fs::File::open(&self.file_location).map_err(|err| {
|
||||
OnDiskGatewayDetailsError::LoadFailure {
|
||||
path: self.file_location.display().to_string(),
|
||||
@@ -127,10 +223,13 @@ impl OnDiskGatewayDetails {
|
||||
Ok(serde_json::from_reader(file)?)
|
||||
}
|
||||
|
||||
pub fn store_to_disk(
|
||||
pub fn store_to_disk<T>(
|
||||
&self,
|
||||
details: &PersistedGatewayDetails,
|
||||
) -> Result<(), OnDiskGatewayDetailsError> {
|
||||
details: &PersistedGatewayDetails<T>,
|
||||
) -> Result<(), OnDiskGatewayDetailsError>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
// ensure the whole directory structure exists
|
||||
if let Some(parent_dir) = &self.file_location.parent() {
|
||||
std::fs::create_dir_all(parent_dir).map_err(|err| {
|
||||
@@ -170,8 +269,8 @@ impl GatewayDetailsStore for OnDiskGatewayDetails {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InMemGatewayDetails {
|
||||
details: Mutex<Option<PersistedGatewayDetails>>,
|
||||
pub struct InMemGatewayDetails<T = EmptyCustomDetails> {
|
||||
details: Mutex<Option<PersistedGatewayDetails<T>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@@ -38,9 +38,6 @@ pub trait MixnetClientStorage {
|
||||
type CredentialStore: CredentialStorage;
|
||||
type GatewayDetailsStore: GatewayDetailsStore;
|
||||
|
||||
// this is a TERRIBLE name...
|
||||
// fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore, Self::GatewayDetailsStore);
|
||||
|
||||
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore);
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore;
|
||||
|
||||
@@ -103,7 +103,7 @@ impl ManagedKeys {
|
||||
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
|
||||
match self {
|
||||
ManagedKeys::Initial(_) => None,
|
||||
ManagedKeys::FullyDerived(keys) => Some(keys.gateway_shared_key()),
|
||||
ManagedKeys::FullyDerived(keys) => keys.gateway_shared_key(),
|
||||
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
|
||||
}
|
||||
}
|
||||
@@ -124,10 +124,26 @@ impl ManagedKeys {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_gateway_key(&self, gateway_shared_key: Arc<SharedKeys>) {
|
||||
pub fn ensure_gateway_key(&self, gateway_shared_key: Option<Arc<SharedKeys>>) {
|
||||
if let ManagedKeys::FullyDerived(key_manager) = &self {
|
||||
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|
||||
|| key_manager.gateway_shared_key != gateway_shared_key
|
||||
if self.gateway_shared_key().is_none() && gateway_shared_key.is_none() {
|
||||
// the key doesn't exist in either state
|
||||
return;
|
||||
}
|
||||
|
||||
if gateway_shared_key.is_some() && self.gateway_shared_key().is_none()
|
||||
|| gateway_shared_key.is_none() && self.gateway_shared_key().is_some()
|
||||
{
|
||||
// if one is provided whilst the other is not...
|
||||
// TODO: should this actually panic or return an error? would this branch be possible
|
||||
// under normal operation?
|
||||
panic!("inconsistent re-derived gateway key")
|
||||
}
|
||||
|
||||
// here we know both keys MUST exist
|
||||
let provided = gateway_shared_key.unwrap();
|
||||
if !Arc::ptr_eq(key_manager.must_get_gateway_shared_key(), &provided)
|
||||
|| *key_manager.must_get_gateway_shared_key() != provided
|
||||
{
|
||||
// this should NEVER happen thus panic here
|
||||
panic!("derived fresh gateway shared key whilst already holding one!")
|
||||
@@ -137,12 +153,12 @@ impl ManagedKeys {
|
||||
|
||||
pub async fn deal_with_gateway_key<S: KeyStore>(
|
||||
&mut self,
|
||||
gateway_shared_key: Arc<SharedKeys>,
|
||||
gateway_shared_key: Option<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);
|
||||
let key_manager = keys.insert_maybe_gateway_shared_key(gateway_shared_key);
|
||||
key_manager.persist_keys(key_store).await?;
|
||||
key_manager
|
||||
}
|
||||
@@ -184,7 +200,10 @@ impl KeyManagerBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_gateway_shared_key(self, gateway_shared_key: Arc<SharedKeys>) -> KeyManager {
|
||||
pub fn insert_maybe_gateway_shared_key(
|
||||
self,
|
||||
gateway_shared_key: Option<Arc<SharedKeys>>,
|
||||
) -> KeyManager {
|
||||
KeyManager {
|
||||
identity_keypair: self.identity_keypair,
|
||||
encryption_keypair: self.encryption_keypair,
|
||||
@@ -222,7 +241,11 @@ pub struct KeyManager {
|
||||
encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// shared key derived with the gateway during "registration handshake"
|
||||
gateway_shared_key: Arc<SharedKeys>,
|
||||
// I'm not a fan of how we broke the nice transition of `KeyManagerBuilder` -> `KeyManager`
|
||||
// by making this field optional.
|
||||
// However, it has to be optional for when we use embedded NR inside a gateway,
|
||||
// since it won't have a shared key (because why would it?)
|
||||
gateway_shared_key: Option<Arc<SharedKeys>>,
|
||||
|
||||
/// key used for producing and processing acknowledgement packets.
|
||||
ack_key: Arc<AckKey>,
|
||||
@@ -232,13 +255,13 @@ impl KeyManager {
|
||||
pub fn from_keys(
|
||||
id_keypair: identity::KeyPair,
|
||||
enc_keypair: encryption::KeyPair,
|
||||
gateway_shared_key: SharedKeys,
|
||||
gateway_shared_key: Option<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),
|
||||
gateway_shared_key: gateway_shared_key.map(Arc::new),
|
||||
ack_key: Arc::new(ack_key),
|
||||
}
|
||||
}
|
||||
@@ -265,13 +288,23 @@ impl KeyManager {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
|
||||
fn must_get_gateway_shared_key(&self) -> &Arc<SharedKeys> {
|
||||
self.gateway_shared_key
|
||||
.as_ref()
|
||||
.expect("gateway shared key is unavailable")
|
||||
}
|
||||
|
||||
pub fn uses_custom_gateway(&self) -> bool {
|
||||
self.gateway_shared_key.is_none()
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`SharedKey`].
|
||||
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
|
||||
Arc::clone(&self.gateway_shared_key)
|
||||
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
|
||||
self.gateway_shared_key.as_ref().map(Arc::clone)
|
||||
}
|
||||
|
||||
pub fn remove_gateway_key(self) -> KeyManagerBuilder {
|
||||
if Arc::strong_count(&self.gateway_shared_key) > 1 {
|
||||
if Arc::strong_count(self.must_get_gateway_shared_key()) > 1 {
|
||||
panic!("attempted to remove gateway key whilst still holding multiple references!")
|
||||
}
|
||||
KeyManagerBuilder {
|
||||
|
||||
@@ -88,20 +88,20 @@ impl OnDiskKeys {
|
||||
pub fn ephemeral_load_gateway_keys(
|
||||
&self,
|
||||
) -> Result<zeroize::Zeroizing<SharedKeys>, OnDiskKeysError> {
|
||||
self.load_key(self.paths.gateway_shared_key(), "gateway shared keys")
|
||||
self.load_key(self.paths.gateway_shared_key(), "gateway shared")
|
||||
.map(zeroize::Zeroizing::new)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn load_encryption_keypair(&self) -> Result<encryption::KeyPair, OnDiskKeysError> {
|
||||
let encryption_paths = self.paths.encryption_key_pair_path();
|
||||
self.load_keypair(encryption_paths, "encryption keys")
|
||||
self.load_keypair(encryption_paths, "encryption")
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn load_identity_keypair(&self) -> Result<identity::KeyPair, OnDiskKeysError> {
|
||||
let identity_paths = self.paths.identity_key_pair_path();
|
||||
self.load_keypair(identity_paths, "identity keys")
|
||||
self.load_keypair(identity_paths, "identity")
|
||||
}
|
||||
|
||||
fn load_key<T: PemStorableKey>(
|
||||
@@ -161,8 +161,9 @@ impl OnDiskKeys {
|
||||
let encryption_keypair = self.load_encryption_keypair()?;
|
||||
|
||||
let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?;
|
||||
let gateway_shared_key: SharedKeys =
|
||||
self.load_key(self.paths.gateway_shared_key(), "gateway shared keys")?;
|
||||
let gateway_shared_key: Option<SharedKeys> = self
|
||||
.load_key(self.paths.gateway_shared_key(), "gateway shared keys")
|
||||
.ok();
|
||||
|
||||
Ok(KeyManager::from_keys(
|
||||
identity_keypair,
|
||||
@@ -173,6 +174,8 @@ impl OnDiskKeys {
|
||||
}
|
||||
|
||||
fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> {
|
||||
use std::ops::Deref;
|
||||
|
||||
let identity_paths = self.paths.identity_key_pair_path();
|
||||
let encryption_paths = self.paths.encryption_key_pair_path();
|
||||
|
||||
@@ -188,11 +191,14 @@ impl OnDiskKeys {
|
||||
)?;
|
||||
|
||||
self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?;
|
||||
self.store_key(
|
||||
keys.gateway_shared_key.as_ref(),
|
||||
self.paths.gateway_shared_key(),
|
||||
"gateway shared keys",
|
||||
)?;
|
||||
|
||||
if let Some(shared_keys) = &keys.gateway_shared_key {
|
||||
self.store_key(
|
||||
shared_keys.deref(),
|
||||
self.paths.gateway_shared_key(),
|
||||
"gateway shared keys",
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+33
-20
@@ -1,24 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::spawn_future;
|
||||
use log::*;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
|
||||
|
||||
pub mod transceiver;
|
||||
|
||||
// We remind ourselves that 32 x 32kb = 1024kb, a reasonable size for a network buffer.
|
||||
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
|
||||
const MAX_FAILURE_COUNT: usize = 100;
|
||||
|
||||
pub struct MixTrafficController<C, St: Storage> {
|
||||
// TODO: most likely to be replaced by some higher level construct as
|
||||
// later on gateway_client will need to be accessible by other entities
|
||||
gateway_client: GatewayClient<C, St>,
|
||||
// that's also disgusting.
|
||||
pub struct Empty;
|
||||
|
||||
pub struct MixTrafficController {
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
|
||||
// TODO: this is temporary work-around.
|
||||
@@ -26,20 +28,31 @@ pub struct MixTrafficController<C, St: Storage> {
|
||||
consecutive_gateway_failure_count: usize,
|
||||
}
|
||||
|
||||
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>,
|
||||
) -> (MixTrafficController<C, St>, BatchMixMessageSender) {
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(gateway_transceiver: T) -> (MixTrafficController, BatchMixMessageSender)
|
||||
where
|
||||
T: GatewayTransceiver + Send + 'static,
|
||||
{
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_client,
|
||||
gateway_transceiver: Box::new(gateway_transceiver),
|
||||
mix_rx: message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
},
|
||||
message_sender,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
) -> (MixTrafficController, BatchMixMessageSender) {
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_transceiver,
|
||||
mix_rx: message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
},
|
||||
@@ -52,16 +65,16 @@ where
|
||||
|
||||
let result = if mix_packets.len() == 1 {
|
||||
let mix_packet = mix_packets.pop().unwrap();
|
||||
self.gateway_client.send_mix_packet(mix_packet).await
|
||||
self.gateway_transceiver.send_mix_packet(mix_packet).await
|
||||
} else {
|
||||
self.gateway_client
|
||||
self.gateway_transceiver
|
||||
.batch_send_mix_packets(mix_packets)
|
||||
.await
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("Failed to send sphinx packet(s) to the gateway! - {err}");
|
||||
error!("Failed to send sphinx packet(s) to the gateway: {err}");
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// todo: in the future this should initiate a 'graceful' shutdown or try
|
||||
@@ -0,0 +1,262 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
|
||||
// we need to type erase the error type since we can't have dynamic associated types alongside dynamic dispatch
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
pub struct ErasedGatewayError(Box<dyn std::error::Error + Send + Sync>);
|
||||
|
||||
fn erase_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> ErasedGatewayError {
|
||||
ErasedGatewayError(Box::new(err))
|
||||
}
|
||||
|
||||
/// This combines combines the functionalities of being able to send and receive mix packets.
|
||||
pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
|
||||
fn gateway_identity(&self) -> identity::PublicKey;
|
||||
}
|
||||
|
||||
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
|
||||
/// usually through a gateway.
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait GatewaySender {
|
||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError>;
|
||||
|
||||
async fn batch_send_mix_packets(
|
||||
&mut self,
|
||||
packets: Vec<MixPacket>,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
// allow for optimisation when sending multiple packets
|
||||
for packet in packets {
|
||||
self.send_mix_packet(packet).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// this trait defines the functionality of being able to correctly route
|
||||
/// packets received from the mixnet, i.e. acks and 'proper' messages.
|
||||
pub trait GatewayReceiver {
|
||||
// ughhhh I really dislike this method, but couldn't come up wih anything better
|
||||
// ideally this would have been an associated type, but heh. we can't.
|
||||
fn set_packet_router(
|
||||
&mut self,
|
||||
_packet_router: PacketRouter,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
debug!("no-op packet router setup");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// to allow for dynamic dispatch
|
||||
impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
#[inline]
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
(**self).gateway_identity()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<G: GatewaySender + ?Sized + Send> GatewaySender for Box<G> {
|
||||
#[inline]
|
||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
||||
(**self).send_mix_packet(packet).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn batch_send_mix_packets(
|
||||
&mut self,
|
||||
packets: Vec<MixPacket>,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
(**self).batch_send_mix_packets(packets).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: GatewayReceiver + ?Sized> GatewayReceiver for Box<G> {
|
||||
#[inline]
|
||||
fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> {
|
||||
(**self).set_packet_router(packet_router)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gateway to which the client is connected through a socket.
|
||||
/// Most likely through a websocket.
|
||||
pub struct RemoteGateway<C, St> {
|
||||
gateway_client: GatewayClient<C, St>,
|
||||
}
|
||||
|
||||
impl<C, St> RemoteGateway<C, St> {
|
||||
pub fn new(gateway_client: GatewayClient<C, St>) -> Self {
|
||||
Self { gateway_client }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, St> GatewayTransceiver for RemoteGateway<C, St>
|
||||
where
|
||||
C: Send,
|
||||
St: Send,
|
||||
{
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.gateway_client.gateway_identity()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C, St> GatewaySender for RemoteGateway<C, St>
|
||||
where
|
||||
C: Send,
|
||||
St: Send,
|
||||
{
|
||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
||||
self.gateway_client
|
||||
.send_mix_packet(packet)
|
||||
.await
|
||||
.map_err(erase_err)
|
||||
}
|
||||
|
||||
async fn batch_send_mix_packets(
|
||||
&mut self,
|
||||
packets: Vec<MixPacket>,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
self.gateway_client
|
||||
.batch_send_mix_packets(packets)
|
||||
.await
|
||||
.map_err(erase_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, St> GatewayReceiver for RemoteGateway<C, St> {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LocalGatewayError {
|
||||
#[error("attempted to set the packet router for the second time")]
|
||||
PacketRouterAlreadySet,
|
||||
|
||||
#[error("failed to setup packet router - has the receiver been dropped?")]
|
||||
FailedPacketRouterSetup,
|
||||
}
|
||||
|
||||
/// Gateway running within the same process.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct LocalGateway {
|
||||
/// Identity of the locally managed gateway
|
||||
local_identity: identity::PublicKey,
|
||||
|
||||
// 'sender' part
|
||||
/// Channel responsible for taking mix packets and forwarding them further into the further mixnet layers.
|
||||
packet_forwarder: mpsc::UnboundedSender<MixPacket>,
|
||||
|
||||
// 'receiver' part
|
||||
packet_router_tx: Option<oneshot::Sender<PacketRouter>>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl LocalGateway {
|
||||
pub fn new(
|
||||
local_identity: identity::PublicKey,
|
||||
packet_forwarder: mpsc::UnboundedSender<MixPacket>,
|
||||
packet_router_tx: oneshot::Sender<PacketRouter>,
|
||||
) -> Self {
|
||||
LocalGateway {
|
||||
local_identity,
|
||||
packet_forwarder,
|
||||
packet_router_tx: Some(packet_router_tx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod nonwasm_sealed {
|
||||
use super::*;
|
||||
|
||||
impl GatewayTransceiver for LocalGateway {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.local_identity
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GatewaySender for LocalGateway {
|
||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
||||
self.packet_forwarder
|
||||
.unbounded_send(packet)
|
||||
.map_err(|err| err.into_send_error())
|
||||
.map_err(erase_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayReceiver for LocalGateway {
|
||||
fn set_packet_router(
|
||||
&mut self,
|
||||
packet_router: PacketRouter,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
let Some(packet_routex_tx) = self.packet_router_tx.take() else {
|
||||
return Err(erase_err(LocalGatewayError::PacketRouterAlreadySet));
|
||||
};
|
||||
|
||||
packet_routex_tx
|
||||
.send(packet_router)
|
||||
.map_err(|_| erase_err(LocalGatewayError::FailedPacketRouterSetup))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we ever decided to start writing unit tests... : )
|
||||
pub struct MockGateway {
|
||||
dummy_identity: identity::PublicKey,
|
||||
packet_router: Option<PacketRouter>,
|
||||
sent: Vec<MixPacket>,
|
||||
}
|
||||
|
||||
impl Default for MockGateway {
|
||||
fn default() -> Self {
|
||||
MockGateway {
|
||||
dummy_identity: "3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
packet_router: None,
|
||||
sent: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("mock gateway error")]
|
||||
pub struct MockGatewayError;
|
||||
|
||||
impl GatewayReceiver for MockGateway {
|
||||
// TODO: that's frustrating. can't do anything about the behaviour here since all the routing is in the `PacketRouter`...
|
||||
fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> {
|
||||
self.packet_router = Some(packet_router);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl GatewaySender for MockGateway {
|
||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
||||
self.sent.push(packet);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayTransceiver for MockGateway {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.dummy_identity
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -71,7 +71,7 @@ impl AcknowledgementListener {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
acks = self.ack_receiver.next() => match acks {
|
||||
Some(acks) => {self.handle_ack_receiver_item(acks).await}
|
||||
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
||||
None => {
|
||||
log::trace!("AcknowledgementListener: Stopping since channel closed");
|
||||
break;
|
||||
|
||||
@@ -260,7 +260,7 @@ where
|
||||
let mut sent_notification_listener = self.sent_notification_listener;
|
||||
let mut action_controller = self.action_controller;
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
let shutdown_handle = shutdown.fork("acknowledgement_listener");
|
||||
spawn_future(async move {
|
||||
acknowledgement_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
@@ -268,7 +268,7 @@ where
|
||||
debug!("The acknowledgement listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
let shutdown_handle = shutdown.fork("input_message_listener");
|
||||
spawn_future(async move {
|
||||
input_message_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
@@ -276,7 +276,7 @@ where
|
||||
debug!("The input listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
let shutdown_handle = shutdown.fork("retransmission_request_listener");
|
||||
spawn_future(async move {
|
||||
retransmission_request_listener
|
||||
.run_with_shutdown(shutdown_handle, packet_type)
|
||||
@@ -284,7 +284,7 @@ where
|
||||
debug!("The retransmission request listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
let shutdown_handle = shutdown.fork("sent_notification_listener");
|
||||
spawn_future(async move {
|
||||
sent_notification_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
@@ -293,7 +293,9 @@ where
|
||||
});
|
||||
|
||||
spawn_future(async move {
|
||||
action_controller.run_with_shutdown(shutdown).await;
|
||||
action_controller
|
||||
.run_with_shutdown(shutdown.with_suffix("action_controller"))
|
||||
.await;
|
||||
debug!("The controller has finished execution!");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -213,17 +213,17 @@ impl RealMessagesController<OsRng> {
|
||||
let ack_control = self.ack_control;
|
||||
let mut reply_control = self.reply_control;
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
let shutdown_handle = shutdown.fork("out_queue_control");
|
||||
spawn_future(async move {
|
||||
out_queue_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The out queue controller has finished execution!");
|
||||
});
|
||||
let shutdown_handle = shutdown.clone();
|
||||
let shutdown_handle = shutdown.fork("reply_control");
|
||||
spawn_future(async move {
|
||||
reply_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The reply controller has finished execution!");
|
||||
});
|
||||
|
||||
ack_control.start_with_shutdown(shutdown, packet_type);
|
||||
ack_control.start_with_shutdown(shutdown.with_suffix("ack_control"), packet_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +500,7 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||
let mut request_receiver = self.request_receiver;
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
let shutdown_handle = shutdown.fork("fragmented_message_receiver");
|
||||
spawn_future(async move {
|
||||
match fragmented_message_receiver
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
@@ -511,7 +511,9 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
||||
}
|
||||
});
|
||||
spawn_future(async move {
|
||||
request_receiver.run_with_shutdown(shutdown).await;
|
||||
request_receiver
|
||||
.run_with_shutdown(shutdown.with_suffix("request_receiver"))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,17 @@ use crate::spawn_future;
|
||||
pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::NymTopologyError;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::sleep;
|
||||
|
||||
mod accessor;
|
||||
pub mod geo_aware_provider;
|
||||
pub(crate) mod nym_api_provider;
|
||||
@@ -86,6 +93,54 @@ impl TopologyRefresher {
|
||||
self.topology_accessor.ensure_is_routable().await
|
||||
}
|
||||
|
||||
pub async fn ensure_contains_gateway(
|
||||
&self,
|
||||
gateway: &NodeIdentity,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
let topology = self
|
||||
.topology_accessor
|
||||
.current_topology()
|
||||
.await
|
||||
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
|
||||
if !topology.gateway_exists(gateway) {
|
||||
return Err(NymTopologyError::NonExistentGatewayError {
|
||||
identity_key: gateway.to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn wait_for_gateway(
|
||||
&mut self,
|
||||
gateway: &NodeIdentity,
|
||||
timeout_duration: Duration,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
info!(
|
||||
"going to wait for at most {timeout_duration:?} for gateway '{gateway}' to come online"
|
||||
);
|
||||
|
||||
let deadline = sleep(timeout_duration);
|
||||
tokio::pin!(deadline);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut deadline => {
|
||||
return Err(NymTopologyError::TimedOutWaitingForGateway {
|
||||
identity_key: gateway.to_base58_string()
|
||||
})
|
||||
}
|
||||
_ = self.try_refresh() => {
|
||||
if self.ensure_contains_gateway(gateway).await.is_ok() {
|
||||
return Ok(())
|
||||
}
|
||||
info!("gateway '{gateway}' is still not online...");
|
||||
sleep(self.refresh_rate).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct ClientKeysPaths {
|
||||
}
|
||||
|
||||
impl ClientKeysPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
pub fn new_base<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
let base_dir = base_data_directory.as_ref();
|
||||
|
||||
ClientKeysPaths {
|
||||
|
||||
@@ -29,14 +29,14 @@ pub struct CommonClientPaths {
|
||||
}
|
||||
|
||||
impl CommonClientPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
pub fn new_base<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
let base_dir = base_data_directory.as_ref();
|
||||
|
||||
CommonClientPaths {
|
||||
credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME),
|
||||
reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME),
|
||||
gateway_details: base_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
|
||||
keys: ClientKeysPaths::new_default(base_data_directory),
|
||||
keys: ClientKeysPaths::new_base(base_data_directory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::client::GatewayConfig;
|
||||
use nym_sphinx::{
|
||||
addressing::clients::Recipient,
|
||||
params::{PacketSize, PacketType},
|
||||
@@ -29,6 +30,8 @@ const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20)
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
|
||||
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
|
||||
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
|
||||
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
|
||||
|
||||
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
// bandwidth bridging protocol, we can come back to a smaller timeout value
|
||||
@@ -241,6 +244,19 @@ pub struct GatewayEndpointConfig {
|
||||
pub gateway_listener: String,
|
||||
}
|
||||
|
||||
impl TryFrom<GatewayEndpointConfig> for GatewayConfig {
|
||||
type Error = ClientCoreError;
|
||||
|
||||
fn try_from(value: GatewayEndpointConfig) -> Result<Self, Self::Error> {
|
||||
Ok(GatewayConfig {
|
||||
gateway_identity: identity::PublicKey::from_base58_string(value.gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?,
|
||||
gateway_owner: Some(value.gateway_owner),
|
||||
gateway_listener: value.gateway_listener,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
|
||||
impl GatewayEndpointConfig {
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(constructor))]
|
||||
@@ -498,6 +514,11 @@ pub struct Topology {
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
pub disable_refreshing: bool,
|
||||
|
||||
/// Defines how long the client is going to wait on startup for its gateway to come online,
|
||||
/// before abandoning the procedure.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub max_startup_gateway_waiting_period: Duration,
|
||||
|
||||
/// Specifies the mixnode topology to be used for sending packets.
|
||||
pub topology_structure: TopologyStructure,
|
||||
}
|
||||
@@ -532,6 +553,7 @@ impl Default for Topology {
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_refreshing: false,
|
||||
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
|
||||
topology_structure: TopologyStructure::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ impl From<TopologyV1_1_20_2> for Topology {
|
||||
topology_refresh_rate: value.topology_refresh_rate,
|
||||
topology_resolution_timeout: value.topology_resolution_timeout,
|
||||
disable_refreshing: value.disable_refreshing,
|
||||
topology_structure: Default::default(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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::mix_traffic::transceiver::ErasedGatewayError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
@@ -19,6 +20,12 @@ pub enum ClientCoreError {
|
||||
source: GatewayClientError,
|
||||
},
|
||||
|
||||
#[error("Custom gateway client error: {source}")]
|
||||
ErasedGatewayClientError {
|
||||
#[from]
|
||||
source: ErasedGatewayError,
|
||||
},
|
||||
|
||||
#[error("Ed25519 error: {0}")]
|
||||
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||
|
||||
@@ -31,15 +38,9 @@ pub enum ClientCoreError {
|
||||
#[error("No gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
|
||||
#[error("Failed to setup gateway")]
|
||||
FailedToSetupGateway,
|
||||
|
||||
#[error("List of nym apis is empty")]
|
||||
ListOfNymApisIsEmpty,
|
||||
|
||||
#[error("Could not load existing gateway configuration: {0}")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
|
||||
#[error("The current network topology seem to be insufficient to route any packets through")]
|
||||
InsufficientNetworkTopology(#[from] NymTopologyError),
|
||||
|
||||
@@ -61,15 +62,6 @@ pub enum ClientCoreError {
|
||||
#[error("The gateway id is invalid - {0}")]
|
||||
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
|
||||
|
||||
#[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?")]
|
||||
GatewayOwnerUnknown,
|
||||
|
||||
#[error("The address of the gateway is unknown - did you run init?")]
|
||||
GatewayAddressUnknown,
|
||||
|
||||
#[error("The gateway is malformed: {source}")]
|
||||
MalformedGateway {
|
||||
#[from]
|
||||
@@ -122,6 +114,18 @@ pub enum ClientCoreError {
|
||||
|
||||
#[error("unable to upgrade config file to `{new_version}`")]
|
||||
UnableToUpgradeConfigFile { new_version: String },
|
||||
|
||||
#[error("the provided gateway details don't much the stored data")]
|
||||
MismatchedStoredGatewayDetails,
|
||||
|
||||
#[error("custom selection of gateway was expected")]
|
||||
CustomGatewaySelectionExpected,
|
||||
|
||||
#[error("the persisted gateway details were set for a custom setup")]
|
||||
UnexpectedPersistedCustomGatewayDetails,
|
||||
|
||||
#[error("this client has performed gateway initialisation in another session")]
|
||||
NoInitClientPresent,
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::config::GatewayEndpointConfig;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::RegistrationResult;
|
||||
use crate::init::types::RegistrationResult;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::{debug, info, trace, warn};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
@@ -24,6 +24,7 @@ use tokio_tungstenite::connect_async;
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
|
||||
@@ -197,17 +198,27 @@ pub(super) fn uniformly_random_gateway<R: Rng>(
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(super) fn get_specified_gateway(
|
||||
gateway_identity: IdentityKeyRef,
|
||||
gateways: &[gateway::Node],
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
gateways
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(super) async fn register_with_gateway(
|
||||
gateway: &GatewayEndpointConfig,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
) -> Result<RegistrationResult, ClientCoreError> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.gateway_listener.clone(),
|
||||
gateway.try_get_gateway_identity_key()?,
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
let mut gateway_client =
|
||||
GatewayClient::new_init(gateway.to_owned().try_into()?, our_identity.clone());
|
||||
|
||||
gateway_client.establish_connection().await.map_err(|err| {
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
ClientCoreError::GatewayClientError {
|
||||
@@ -230,6 +241,6 @@ pub(super) async fn register_with_gateway(
|
||||
})?;
|
||||
Ok(RegistrationResult {
|
||||
shared_keys,
|
||||
authenticated_ephemeral_client: Some(gateway_client),
|
||||
authenticated_ephemeral_client: gateway_client,
|
||||
})
|
||||
}
|
||||
|
||||
+136
-372
@@ -8,246 +8,34 @@ use crate::client::base_client::storage::gateway_details::{
|
||||
};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ManagedKeys;
|
||||
use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway};
|
||||
use crate::{
|
||||
config::{Config, GatewayEndpointConfig},
|
||||
error::ClientCoreError,
|
||||
use crate::config::GatewayEndpointConfig;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::helpers::{
|
||||
choose_gateway_by_latency, get_specified_gateway, uniformly_random_gateway,
|
||||
};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::client::InitOnly;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
|
||||
use crate::init::types::{
|
||||
CustomGatewayDetails, GatewayDetails, GatewaySelectionSpecification, GatewaySetup,
|
||||
InitialisationResult,
|
||||
};
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_topology::gateway;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
pub mod helpers;
|
||||
|
||||
pub struct RegistrationResult {
|
||||
pub shared_keys: Arc<SharedKeys>,
|
||||
pub authenticated_ephemeral_client: Option<GatewayClient<InitOnly>>,
|
||||
}
|
||||
|
||||
pub struct InitialisationResult {
|
||||
pub details: InitialisationDetails,
|
||||
pub authenticated_ephemeral_client: Option<GatewayClient<InitOnly>>,
|
||||
}
|
||||
|
||||
impl From<InitialisationDetails> for InitialisationResult {
|
||||
fn from(details: InitialisationDetails) -> Self {
|
||||
InitialisationResult {
|
||||
details,
|
||||
authenticated_ephemeral_client: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rename to something better...
|
||||
#[derive(Debug)]
|
||||
pub struct InitialisationDetails {
|
||||
pub gateway_details: GatewayEndpointConfig,
|
||||
pub managed_keys: ManagedKeys,
|
||||
}
|
||||
|
||||
impl InitialisationDetails {
|
||||
pub fn new(gateway_details: GatewayEndpointConfig, managed_keys: ManagedKeys) -> Self {
|
||||
InitialisationDetails {
|
||||
gateway_details,
|
||||
managed_keys,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_load<K, D>(key_store: &K, details_store: &D) -> Result<Self, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
D: GatewayDetailsStore,
|
||||
K::StorageError: Send + Sync + 'static,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let loaded_details = _load_gateway_details(details_store).await?;
|
||||
let loaded_keys = _load_managed_keys(key_store).await?;
|
||||
|
||||
if !loaded_details.verify(&loaded_keys.must_get_gateway_shared_key()) {
|
||||
return Err(ClientCoreError::MismatchedGatewayDetails {
|
||||
gateway_id: loaded_details.details.gateway_id,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(InitialisationDetails {
|
||||
gateway_details: loaded_details.into(),
|
||||
managed_keys: loaded_keys,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn client_address(&self) -> Result<Recipient, ClientCoreError> {
|
||||
let client_recipient = Recipient::new(
|
||||
*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_details.gateway_id)?,
|
||||
);
|
||||
|
||||
Ok(client_recipient)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GatewaySetup {
|
||||
/// The gateway specification MUST BE loaded from the underlying storage.
|
||||
MustLoad,
|
||||
|
||||
/// 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
|
||||
details: PersistedGatewayDetails,
|
||||
},
|
||||
|
||||
ReuseConnection {
|
||||
/// The authenticated ephemeral client that was created during `init`
|
||||
authenticated_ephemeral_client: GatewayClient<InitOnly>,
|
||||
|
||||
/// Details of this pre-initialised client
|
||||
details: InitialisationDetails,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<PersistedGatewayDetails> for GatewaySetup {
|
||||
fn from(details: PersistedGatewayDetails) -> Self {
|
||||
GatewaySetup::Predefined { details }
|
||||
}
|
||||
}
|
||||
|
||||
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_fresh(
|
||||
gateway_identity: Option<String>,
|
||||
latency_based_selection: Option<bool>,
|
||||
) -> Self {
|
||||
if let Some(gateway_identity) = gateway_identity {
|
||||
GatewaySetup::Specified { gateway_identity }
|
||||
} else {
|
||||
GatewaySetup::New {
|
||||
by_latency: latency_based_selection.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_must_load(&self) -> bool {
|
||||
matches!(self, GatewaySetup::MustLoad)
|
||||
}
|
||||
|
||||
pub fn has_full_details(&self) -> bool {
|
||||
matches!(self, GatewaySetup::Predefined { .. }) || self.is_must_load()
|
||||
}
|
||||
|
||||
pub async fn choose_gateway(
|
||||
&self,
|
||||
gateways: &[gateway::Node],
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
match self {
|
||||
GatewaySetup::New { by_latency } => {
|
||||
let mut rng = OsRng;
|
||||
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)?;
|
||||
|
||||
gateways
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
|
||||
.cloned()
|
||||
}
|
||||
.map(Into::into),
|
||||
_ => Err(ClientCoreError::UnexpectedGatewayDetails),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_get_new_gateway_details(
|
||||
&self,
|
||||
validator_servers: &[Url],
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
let mut rng = OsRng;
|
||||
let gateways = current_gateways(&mut rng, validator_servers).await?;
|
||||
self.choose_gateway(&gateways).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct describing the results of the client initialization procedure.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
version: String,
|
||||
id: String,
|
||||
identity_key: String,
|
||||
encryption_key: String,
|
||||
gateway_id: String,
|
||||
gateway_listener: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
pub fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
|
||||
Self {
|
||||
version: config.client.version.clone(),
|
||||
id: config.client.id.clone(),
|
||||
identity_key: address.identity().to_base58_string(),
|
||||
encryption_key: address.encryption_key().to_base58_string(),
|
||||
gateway_id: gateway.gateway_id.clone(),
|
||||
gateway_listener: gateway.gateway_listener.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Version: {}", self.version)?;
|
||||
writeln!(f, "ID: {}", self.id)?;
|
||||
writeln!(f, "Identity key: {}", self.identity_key)?;
|
||||
writeln!(f, "Encryption: {}", self.encryption_key)?;
|
||||
writeln!(f, "Gateway ID: {}", self.gateway_id)?;
|
||||
write!(f, "Gateway: {}", self.gateway_listener)
|
||||
}
|
||||
}
|
||||
pub mod types;
|
||||
|
||||
// helpers for error wrapping
|
||||
async fn _store_gateway_details<D>(
|
||||
async fn _store_gateway_details<T, D>(
|
||||
details_store: &D,
|
||||
details: &PersistedGatewayDetails,
|
||||
details: &PersistedGatewayDetails<T>,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
D: GatewayDetailsStore,
|
||||
D: GatewayDetailsStore<T>,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
T: Serialize + Send + Sync,
|
||||
{
|
||||
details_store
|
||||
.store_gateway_details(details)
|
||||
@@ -257,12 +45,13 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
async fn _load_gateway_details<D>(
|
||||
async fn _load_gateway_details<T, D>(
|
||||
details_store: &D,
|
||||
) -> Result<PersistedGatewayDetails, ClientCoreError>
|
||||
) -> Result<PersistedGatewayDetails<T>, ClientCoreError>
|
||||
where
|
||||
D: GatewayDetailsStore,
|
||||
D: GatewayDetailsStore<T>,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
T: DeserializeOwned + Send + Sync,
|
||||
{
|
||||
details_store
|
||||
.load_gateway_details()
|
||||
@@ -284,190 +73,165 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_valid_details(
|
||||
details: &PersistedGatewayDetails,
|
||||
fn ensure_valid_details<T>(
|
||||
details: &PersistedGatewayDetails<T>,
|
||||
loaded_keys: &ManagedKeys,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
if !details.verify(&loaded_keys.must_get_gateway_shared_key()) {
|
||||
Err(ClientCoreError::MismatchedGatewayDetails {
|
||||
gateway_id: details.details.gateway_id.clone(),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
details.validate(loaded_keys.gateway_shared_key().as_deref())
|
||||
}
|
||||
|
||||
pub async fn setup_gateway_from<K, D>(
|
||||
setup: GatewaySetup,
|
||||
async fn setup_new_gateway<T, K, D>(
|
||||
key_store: &K,
|
||||
details_store: &D,
|
||||
overwrite_data: bool,
|
||||
gateways: Option<&[gateway::Node]>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
selection_specification: GatewaySelectionSpecification<T>,
|
||||
available_gateways: Vec<gateway::Node>,
|
||||
) -> Result<InitialisationResult<T>, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
D: GatewayDetailsStore,
|
||||
D: GatewayDetailsStore<T>,
|
||||
K::StorageError: Send + Sync + 'static,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
T: DeserializeOwned + Serialize + Send + Sync,
|
||||
{
|
||||
// I don't like how we can't deal with this variant in the match below, but we need to take ownership of internal values.
|
||||
if let GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
details,
|
||||
} = setup
|
||||
{
|
||||
// if we have already performed the full setup, forward the details.
|
||||
// it's up to the caller to ensure persistence
|
||||
return Ok(InitialisationResult {
|
||||
details,
|
||||
authenticated_ephemeral_client: Some(authenticated_ephemeral_client),
|
||||
});
|
||||
// if we're setting up new gateway, failing to load existing information is fine.
|
||||
// as a matter of fact, it's only potentially a problem if we DO succeed
|
||||
if _load_gateway_details(details_store).await.is_ok() && !overwrite_data {
|
||||
return Err(ClientCoreError::ForbiddenKeyOverwrite);
|
||||
}
|
||||
if _load_managed_keys(key_store).await.is_ok() && !overwrite_data {
|
||||
return Err(ClientCoreError::ForbiddenKeyOverwrite);
|
||||
}
|
||||
|
||||
let mut rng = OsRng;
|
||||
let mut new_keys = ManagedKeys::generate_new(&mut rng);
|
||||
|
||||
// try load gateway details
|
||||
let loaded_details = _load_gateway_details(details_store).await;
|
||||
|
||||
// try load keys and decide what to do based on the GatewaySetup
|
||||
let mut managed_keys = match ManagedKeys::try_load(key_store).await {
|
||||
Ok(loaded_keys) => {
|
||||
match &setup {
|
||||
GatewaySetup::MustLoad => {
|
||||
// get EVERYTHING from the storage
|
||||
let details = loaded_details?;
|
||||
ensure_valid_details(&details, &loaded_keys)?;
|
||||
|
||||
// no need to persist anything as we got everything from the storage
|
||||
return Ok(InitialisationDetails::new(details.into(), loaded_keys).into());
|
||||
}
|
||||
GatewaySetup::Predefined { details } => {
|
||||
// we already have defined gateway details AND a shared key
|
||||
ensure_valid_details(details, &loaded_keys)?;
|
||||
|
||||
// if nothing was stored or we're allowed to overwrite what's there, just persist the passed data
|
||||
if overwrite_data || loaded_details.is_err() {
|
||||
_store_gateway_details(details_store, details).await?;
|
||||
}
|
||||
|
||||
return Ok(
|
||||
InitialisationDetails::new(details.clone().into(), loaded_keys).into(),
|
||||
);
|
||||
}
|
||||
GatewaySetup::Specified { gateway_identity } => {
|
||||
// if that data was already stored...
|
||||
if let Ok(existing_gateway) = loaded_details {
|
||||
ensure_valid_details(&existing_gateway, &loaded_keys)?;
|
||||
if &existing_gateway.details.gateway_id != gateway_identity
|
||||
&& !overwrite_data
|
||||
{
|
||||
// if our loaded details don't match requested value and we CANT overwrite it...
|
||||
return Err(ClientCoreError::UnexpectedGatewayDetails);
|
||||
} else if &existing_gateway.details.gateway_id == gateway_identity {
|
||||
// if they do match up, just return it
|
||||
return Ok(InitialisationDetails::new(
|
||||
existing_gateway.into(),
|
||||
loaded_keys,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
// we didn't get full details from the store and we have loaded some keys
|
||||
// so we can only continue if we're allowed to overwrite keys
|
||||
if overwrite_data {
|
||||
ManagedKeys::generate_new(&mut rng)
|
||||
} else {
|
||||
return Err(ClientCoreError::ForbiddenKeyOverwrite);
|
||||
}
|
||||
}
|
||||
GatewaySetup::New { .. } => {
|
||||
if let Ok(existing_gateway) = loaded_details {
|
||||
ensure_valid_details(&existing_gateway, &loaded_keys)?;
|
||||
return Ok(InitialisationDetails::new(
|
||||
existing_gateway.into(),
|
||||
loaded_keys,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
// we didn't get full details from the store and we have loaded some keys
|
||||
// so we can only continue if we're allowed to overwrite keys
|
||||
if overwrite_data {
|
||||
ManagedKeys::generate_new(&mut rng)
|
||||
} else {
|
||||
return Err(ClientCoreError::ForbiddenKeyOverwrite);
|
||||
}
|
||||
}
|
||||
GatewaySetup::ReuseConnection { .. } => {
|
||||
unreachable!("the reuse connection variant was already manually covered")
|
||||
}
|
||||
}
|
||||
let gateway_details = match selection_specification {
|
||||
GatewaySelectionSpecification::UniformRemote => {
|
||||
let gateway = uniformly_random_gateway(&mut rng, &available_gateways)?;
|
||||
GatewayDetails::Configured(GatewayEndpointConfig::from(gateway))
|
||||
}
|
||||
Err(_) => {
|
||||
// if we failed to load the keys, ensure we didn't provide gateway details in some form
|
||||
// (in that case we CAN'T generate new keys
|
||||
if setup.has_full_details() {
|
||||
return Err(ClientCoreError::UnavailableSharedKey);
|
||||
}
|
||||
ManagedKeys::generate_new(&mut rng)
|
||||
GatewaySelectionSpecification::RemoteByLatency => {
|
||||
let gateway = choose_gateway_by_latency(&mut rng, &available_gateways).await?;
|
||||
GatewayDetails::Configured(GatewayEndpointConfig::from(gateway))
|
||||
}
|
||||
GatewaySelectionSpecification::Specified { identity } => {
|
||||
let gateway = get_specified_gateway(&identity, &available_gateways)?;
|
||||
GatewayDetails::Configured(GatewayEndpointConfig::from(gateway))
|
||||
}
|
||||
GatewaySelectionSpecification::Custom {
|
||||
gateway_identity,
|
||||
additional_data,
|
||||
} => GatewayDetails::Custom(CustomGatewayDetails::new(gateway_identity, additional_data)),
|
||||
};
|
||||
|
||||
// choose gateway
|
||||
let gateway_details = setup.choose_gateway(gateways.unwrap_or_default()).await?;
|
||||
let registration_result = if let GatewayDetails::Configured(gateway_cfg) = &gateway_details {
|
||||
// if we're using a 'normal' gateway setup, do register
|
||||
let our_identity = new_keys.identity_keypair();
|
||||
Some(helpers::register_with_gateway(gateway_cfg, our_identity).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// get our identity key
|
||||
let our_identity = managed_keys.identity_keypair();
|
||||
let maybe_shared_keys = registration_result
|
||||
.as_ref()
|
||||
.map(|r| Arc::clone(&r.shared_keys));
|
||||
|
||||
// Establish connection, authenticate and generate keys for talking with the gateway
|
||||
let registration_result =
|
||||
helpers::register_with_gateway(&gateway_details, our_identity).await?;
|
||||
let shared_keys = registration_result.shared_keys;
|
||||
let persisted_details =
|
||||
PersistedGatewayDetails::new(gateway_details, maybe_shared_keys.as_deref())?;
|
||||
|
||||
let persisted_details = PersistedGatewayDetails::new(gateway_details, &shared_keys);
|
||||
|
||||
// persist gateway keys
|
||||
managed_keys
|
||||
.deal_with_gateway_key(shared_keys, key_store)
|
||||
// persist the keys
|
||||
new_keys
|
||||
.deal_with_gateway_key(maybe_shared_keys, key_store)
|
||||
.await
|
||||
.map_err(|source| ClientCoreError::KeyStoreError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
|
||||
// persist gateway config
|
||||
// persist gateway configs
|
||||
_store_gateway_details(details_store, &persisted_details).await?;
|
||||
|
||||
Ok(InitialisationResult {
|
||||
details: InitialisationDetails::new(persisted_details.into(), managed_keys),
|
||||
authenticated_ephemeral_client: registration_result.authenticated_ephemeral_client,
|
||||
gateway_details: persisted_details.into(),
|
||||
managed_keys: new_keys,
|
||||
authenticated_ephemeral_client: registration_result
|
||||
.map(|r| r.authenticated_ephemeral_client),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn setup_gateway<K, D>(
|
||||
setup: GatewaySetup,
|
||||
async fn use_loaded_gateway_details<T, K, D>(
|
||||
key_store: &K,
|
||||
details_store: &D,
|
||||
overwrite_data: bool,
|
||||
validator_servers: Option<&[Url]>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
) -> Result<InitialisationResult<T>, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
D: GatewayDetailsStore,
|
||||
D: GatewayDetailsStore<T>,
|
||||
K::StorageError: Send + Sync + 'static,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
T: DeserializeOwned + Send + Sync,
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
let gateways = current_gateways(&mut rng, validator_servers.unwrap_or_default()).await?;
|
||||
let loaded_details = _load_gateway_details(details_store).await?;
|
||||
let loaded_keys = _load_managed_keys(key_store).await?;
|
||||
|
||||
setup_gateway_from(
|
||||
setup,
|
||||
key_store,
|
||||
details_store,
|
||||
overwrite_data,
|
||||
Some(&gateways),
|
||||
)
|
||||
.await
|
||||
ensure_valid_details(&loaded_details, &loaded_keys)?;
|
||||
|
||||
// no need to persist anything as we got everything from the storage
|
||||
Ok(InitialisationResult::new_loaded(
|
||||
loaded_details.into(),
|
||||
loaded_keys,
|
||||
))
|
||||
}
|
||||
|
||||
fn reuse_gateway_connection<T>(
|
||||
authenticated_ephemeral_client: InitGatewayClient,
|
||||
gateway_details: GatewayDetails<T>,
|
||||
managed_keys: ManagedKeys,
|
||||
) -> InitialisationResult<T> {
|
||||
InitialisationResult {
|
||||
gateway_details,
|
||||
managed_keys,
|
||||
authenticated_ephemeral_client: Some(authenticated_ephemeral_client),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setup_gateway<T, K, D>(
|
||||
setup: GatewaySetup<T>,
|
||||
key_store: &K,
|
||||
details_store: &D,
|
||||
) -> Result<InitialisationResult<T>, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
D: GatewayDetailsStore<T>,
|
||||
K::StorageError: Send + Sync + 'static,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
T: DeserializeOwned + Serialize + Send + Sync,
|
||||
{
|
||||
match setup {
|
||||
GatewaySetup::MustLoad => use_loaded_gateway_details(key_store, details_store).await,
|
||||
GatewaySetup::New {
|
||||
specification,
|
||||
available_gateways,
|
||||
overwrite_data,
|
||||
} => {
|
||||
setup_new_gateway(
|
||||
key_store,
|
||||
details_store,
|
||||
overwrite_data,
|
||||
specification,
|
||||
available_gateways,
|
||||
)
|
||||
.await
|
||||
}
|
||||
GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
gateway_details,
|
||||
managed_keys,
|
||||
} => Ok(reuse_gateway_connection(
|
||||
authenticated_ephemeral_client,
|
||||
gateway_details,
|
||||
managed_keys,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) {
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::base_client::storage::gateway_details::{
|
||||
GatewayDetailsStore, PersistedCustomGatewayDetails, PersistedGatewayDetails,
|
||||
};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ManagedKeys;
|
||||
use crate::config::{Config, GatewayEndpointConfig};
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::{_load_gateway_details, _load_managed_keys, setup_gateway};
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_topology::gateway;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Result of registering with a gateway:
|
||||
/// - shared keys derived between ourselves and the node
|
||||
/// - an authenticated handle of an ephemeral handle created for the purposes of registration
|
||||
pub struct RegistrationResult {
|
||||
pub shared_keys: Arc<SharedKeys>,
|
||||
pub authenticated_ephemeral_client: InitGatewayClient,
|
||||
}
|
||||
|
||||
/// Result of fully initialising a client:
|
||||
/// - details of the associated gateway
|
||||
/// - all loaded (or derived) keys
|
||||
/// - an optional authenticated handle of an ephemeral gateway handle created for the purposes of registration,
|
||||
/// if this was the first time this client registered
|
||||
pub struct InitialisationResult<T = EmptyCustomDetails> {
|
||||
pub gateway_details: GatewayDetails<T>,
|
||||
pub managed_keys: ManagedKeys,
|
||||
pub authenticated_ephemeral_client: Option<InitGatewayClient>,
|
||||
}
|
||||
|
||||
impl<T> InitialisationResult<T> {
|
||||
pub fn new_loaded(gateway_details: GatewayDetails<T>, managed_keys: ManagedKeys) -> Self {
|
||||
InitialisationResult {
|
||||
gateway_details,
|
||||
managed_keys,
|
||||
authenticated_ephemeral_client: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_load<K, D>(key_store: &K, details_store: &D) -> Result<Self, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
D: GatewayDetailsStore<T>,
|
||||
K::StorageError: Send + Sync + 'static,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
T: DeserializeOwned + Send + Sync,
|
||||
{
|
||||
let loaded_details = _load_gateway_details(details_store).await?;
|
||||
let loaded_keys = _load_managed_keys(key_store).await?;
|
||||
|
||||
match &loaded_details {
|
||||
PersistedGatewayDetails::Default(loaded_default) => {
|
||||
if !loaded_default.verify(&loaded_keys.must_get_gateway_shared_key()) {
|
||||
return Err(ClientCoreError::MismatchedGatewayDetails {
|
||||
gateway_id: loaded_default.details.gateway_id.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
PersistedGatewayDetails::Custom(_) => {}
|
||||
}
|
||||
|
||||
Ok(InitialisationResult {
|
||||
gateway_details: loaded_details.into(),
|
||||
managed_keys: loaded_keys,
|
||||
authenticated_ephemeral_client: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn client_address(&self) -> Result<Recipient, ClientCoreError> {
|
||||
let client_recipient = Recipient::new(
|
||||
*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_details.gateway_id())?,
|
||||
);
|
||||
|
||||
Ok(client_recipient)
|
||||
}
|
||||
}
|
||||
|
||||
/// Details of particular gateway client got registered with
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GatewayDetails<T = EmptyCustomDetails> {
|
||||
/// Standard details of a remote gateway
|
||||
Configured(GatewayEndpointConfig),
|
||||
|
||||
/// Custom gateway setup, such as for a client embedded inside gateway itself
|
||||
Custom(CustomGatewayDetails<T>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct EmptyCustomDetails {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CustomGatewayDetails<T = EmptyCustomDetails> {
|
||||
// whatever custom method is used, gateway's identity must be known
|
||||
pub gateway_id: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub additional_data: T,
|
||||
}
|
||||
|
||||
impl<T> CustomGatewayDetails<T> {
|
||||
pub fn new(gateway_id: String, additional_data: T) -> Self {
|
||||
Self {
|
||||
gateway_id,
|
||||
additional_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> GatewayDetails<T> {
|
||||
pub fn try_get_configured_endpoint(&self) -> Option<&GatewayEndpointConfig> {
|
||||
if let GatewayDetails::Configured(endpoint) = &self {
|
||||
Some(endpoint)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_custom(&self) -> bool {
|
||||
matches!(self, GatewayDetails::Custom(_))
|
||||
}
|
||||
|
||||
pub fn gateway_id(&self) -> &str {
|
||||
match self {
|
||||
GatewayDetails::Configured(cfg) => &cfg.gateway_id,
|
||||
GatewayDetails::Custom(custom) => &custom.gateway_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GatewayEndpointConfig> for GatewayDetails {
|
||||
fn from(value: GatewayEndpointConfig) -> Self {
|
||||
GatewayDetails::Configured(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<PersistedCustomGatewayDetails<T>> for CustomGatewayDetails<T> {
|
||||
fn from(value: PersistedCustomGatewayDetails<T>) -> Self {
|
||||
CustomGatewayDetails {
|
||||
gateway_id: value.gateway_id,
|
||||
additional_data: value.additional_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<CustomGatewayDetails<T>> for PersistedCustomGatewayDetails<T> {
|
||||
fn from(value: CustomGatewayDetails<T>) -> Self {
|
||||
PersistedCustomGatewayDetails {
|
||||
gateway_id: value.gateway_id,
|
||||
additional_data: value.additional_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<PersistedGatewayDetails<T>> for GatewayDetails<T> {
|
||||
fn from(value: PersistedGatewayDetails<T>) -> Self {
|
||||
match value {
|
||||
PersistedGatewayDetails::Default(default) => {
|
||||
GatewayDetails::Configured(default.details)
|
||||
}
|
||||
PersistedGatewayDetails::Custom(custom) => GatewayDetails::Custom(custom.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub enum GatewaySelectionSpecification<T = EmptyCustomDetails> {
|
||||
/// Uniformly choose a random remote gateway.
|
||||
#[default]
|
||||
UniformRemote,
|
||||
|
||||
/// Should the new, remote, gateway be selected based on latency.
|
||||
RemoteByLatency,
|
||||
|
||||
/// Gateway with this specific identity should be chosen.
|
||||
// JS: I don't really like the name of this enum variant but couldn't think of anything better at the time
|
||||
Specified { identity: IdentityKey },
|
||||
|
||||
// TODO: this doesn't really fit in here..., but where else to put it?
|
||||
/// This client has handled the selection by itself
|
||||
Custom {
|
||||
gateway_identity: String,
|
||||
additional_data: T,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> GatewaySelectionSpecification<T> {
|
||||
pub fn new(gateway_identity: Option<String>, latency_based_selection: Option<bool>) -> Self {
|
||||
if let Some(identity) = gateway_identity {
|
||||
GatewaySelectionSpecification::Specified { identity }
|
||||
} else if let Some(true) = latency_based_selection {
|
||||
GatewaySelectionSpecification::RemoteByLatency
|
||||
} else {
|
||||
GatewaySelectionSpecification::UniformRemote
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GatewaySetup<T = EmptyCustomDetails> {
|
||||
/// The gateway specification (details + keys) MUST BE loaded from the underlying storage.
|
||||
MustLoad,
|
||||
|
||||
/// Specifies usage of a new gateway
|
||||
New {
|
||||
specification: GatewaySelectionSpecification<T>,
|
||||
|
||||
// TODO: seems to be a bit inefficient to pass them by value
|
||||
available_gateways: Vec<gateway::Node>,
|
||||
|
||||
/// Specifies whether old data should be overwritten whilst setting up new gateway client.
|
||||
overwrite_data: bool,
|
||||
},
|
||||
|
||||
ReuseConnection {
|
||||
/// The authenticated ephemeral client that was created during `init`
|
||||
authenticated_ephemeral_client: InitGatewayClient,
|
||||
|
||||
// Details of this pre-initialised client (i.e. gateway and keys)
|
||||
gateway_details: GatewayDetails<T>,
|
||||
|
||||
managed_keys: ManagedKeys,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> GatewaySetup<T> {
|
||||
// pub fn new_fresh(
|
||||
// gateway_identity: Option<String>,
|
||||
// latency_based_selection: Option<bool>,
|
||||
// gateways: Vec<gateway::Node>,
|
||||
// can_overwrite: bool,
|
||||
// ) -> Self {
|
||||
// if let Some(gateway_identity) = gateway_identity {
|
||||
// GatewaySetup::Specified { gateway_identity }
|
||||
// } else {
|
||||
// let specification = if let Some(true) = latency_based_selection {
|
||||
// GatewaySelectionSpecification::RemoteByLatency
|
||||
// } else {
|
||||
// GatewaySelectionSpecification::UniformRemote
|
||||
// };
|
||||
//
|
||||
// GatewaySetup::New { specification }
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn try_reuse_connection(
|
||||
init_res: InitialisationResult<T>,
|
||||
) -> Result<Self, ClientCoreError> {
|
||||
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
|
||||
Ok(GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
gateway_details: init_res.gateway_details,
|
||||
managed_keys: init_res.managed_keys,
|
||||
})
|
||||
} else {
|
||||
Err(ClientCoreError::NoInitClientPresent)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_setup<K, D>(
|
||||
self,
|
||||
key_store: &K,
|
||||
details_store: &D,
|
||||
) -> Result<InitialisationResult<T>, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
D: GatewayDetailsStore<T>,
|
||||
K::StorageError: Send + Sync + 'static,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
T: DeserializeOwned + Serialize + Send + Sync,
|
||||
{
|
||||
setup_gateway(self, key_store, details_store).await
|
||||
}
|
||||
|
||||
pub fn is_must_load(&self) -> bool {
|
||||
matches!(self, GatewaySetup::MustLoad)
|
||||
}
|
||||
|
||||
pub fn has_full_details(&self) -> bool {
|
||||
self.is_must_load()
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct describing the results of the client initialization procedure.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
version: String,
|
||||
id: String,
|
||||
identity_key: String,
|
||||
encryption_key: String,
|
||||
gateway_id: String,
|
||||
gateway_listener: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
pub fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
|
||||
Self {
|
||||
version: config.client.version.clone(),
|
||||
id: config.client.id.clone(),
|
||||
identity_key: address.identity().to_base58_string(),
|
||||
encryption_key: address.encryption_key().to_base58_string(),
|
||||
gateway_id: gateway.gateway_id.clone(),
|
||||
gateway_listener: gateway.gateway_listener.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Version: {}", self.version)?;
|
||||
writeln!(f, "ID: {}", self.id)?;
|
||||
writeln!(f, "Identity key: {}", self.identity_key)?;
|
||||
writeln!(f, "Encryption: {}", self.encryption_key)?;
|
||||
writeln!(f, "Gateway ID: {}", self.gateway_id)?;
|
||||
write!(f, "Gateway: {}", self.gateway_listener)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@ pub mod config;
|
||||
pub mod error;
|
||||
pub mod init;
|
||||
|
||||
pub use nym_topology::{
|
||||
HardcodedTopologyProvider, NymTopology, NymTopologyError, SerializableNymTopology,
|
||||
SerializableTopologyError, TopologyProvider,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(crate) fn spawn_future<F>(future: F)
|
||||
where
|
||||
|
||||
@@ -7,6 +7,7 @@ pub use crate::packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
||||
use crate::traits::GatewayPacketRouter;
|
||||
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
@@ -39,9 +40,23 @@ use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::sleep;
|
||||
|
||||
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
// bandwidth bridging protocol, we can come back to a smaller timeout value
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
||||
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
|
||||
pub struct GatewayConfig {
|
||||
pub gateway_identity: identity::PublicKey,
|
||||
|
||||
// currently a dead field
|
||||
pub gateway_owner: Option<String>,
|
||||
|
||||
pub gateway_listener: String,
|
||||
}
|
||||
|
||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||
pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
authenticated: bool,
|
||||
disabled_credentials_mode: bool,
|
||||
@@ -69,17 +84,12 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
}
|
||||
|
||||
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,
|
||||
config: GatewayConfig,
|
||||
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,
|
||||
response_timeout_duration: Duration,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
@@ -87,13 +97,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
authenticated: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
gateway_address: config.gateway_listener,
|
||||
gateway_identity: config.gateway_identity,
|
||||
local_identity,
|
||||
shared_key,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
|
||||
response_timeout_duration,
|
||||
packet_router,
|
||||
response_timeout_duration: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
bandwidth_controller,
|
||||
should_reconnect_on_failure: true,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
@@ -102,21 +112,34 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
|
||||
#[must_use]
|
||||
pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self {
|
||||
self.disabled_credentials_mode = disabled_credentials_mode;
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: later convert into proper builder methods
|
||||
pub fn with_reconnection_on_failure(&mut self, should_reconnect_on_failure: bool) {
|
||||
self.should_reconnect_on_failure = should_reconnect_on_failure
|
||||
#[must_use]
|
||||
pub fn with_reconnection_on_failure(mut self, should_reconnect_on_failure: bool) -> Self {
|
||||
self.should_reconnect_on_failure = should_reconnect_on_failure;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reconnection_attempts(&mut self, reconnection_attempts: usize) {
|
||||
self.reconnection_attempts = reconnection_attempts
|
||||
#[must_use]
|
||||
pub fn with_response_timeout(mut self, response_timeout_duration: Duration) -> Self {
|
||||
self.response_timeout_duration = response_timeout_duration;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reconnection_backoff(&mut self, backoff: Duration) {
|
||||
self.reconnection_backoff = backoff
|
||||
#[must_use]
|
||||
pub fn with_reconnection_attempts(mut self, reconnection_attempts: usize) -> Self {
|
||||
self.reconnection_attempts = reconnection_attempts;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reconnection_backoff(mut self, backoff: Duration) -> Self {
|
||||
self.reconnection_backoff = backoff;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gateway_identity(&self) -> identity::PublicKey {
|
||||
@@ -761,16 +784,14 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
// type alias for an ease of use
|
||||
pub type InitGatewayClient = GatewayClient<InitOnly>;
|
||||
|
||||
pub struct InitOnly;
|
||||
|
||||
impl GatewayClient<InitOnly, 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 {
|
||||
pub fn new_init(config: GatewayConfig, local_identity: Arc<identity::KeyPair>) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
|
||||
// note: this packet_router is completely invalid in normal circumstances, but "works"
|
||||
@@ -784,13 +805,13 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
authenticated: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
gateway_address: config.gateway_listener,
|
||||
gateway_identity: config.gateway_identity,
|
||||
local_identity,
|
||||
shared_key: None,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration,
|
||||
response_timeout_duration: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
bandwidth_controller: None,
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
@@ -801,9 +822,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
|
||||
pub fn upgrade<C, St>(
|
||||
self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
response_timeout_duration: Duration,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
shutdown: TaskClient,
|
||||
) -> GatewayClient<C, St> {
|
||||
@@ -822,8 +841,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
local_identity: self.local_identity,
|
||||
shared_key: self.shared_key,
|
||||
connection: self.connection,
|
||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
|
||||
response_timeout_duration,
|
||||
packet_router,
|
||||
response_timeout_duration: self.response_timeout_duration,
|
||||
bandwidth_controller,
|
||||
should_reconnect_on_failure: self.should_reconnect_on_failure,
|
||||
reconnection_attempts: self.reconnection_attempts,
|
||||
|
||||
@@ -76,6 +76,16 @@ pub enum GatewayClientError {
|
||||
|
||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
|
||||
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
|
||||
|
||||
#[error(
|
||||
"The packet router hasn't been set - are you sure you started up the client correctly?"
|
||||
)]
|
||||
PacketRouterUnavailable,
|
||||
|
||||
#[error(
|
||||
"this operation couldn't be completed as the program is in the process of shutting down"
|
||||
)]
|
||||
ShutdownInProgress,
|
||||
}
|
||||
|
||||
impl GatewayClientError {
|
||||
|
||||
@@ -2,20 +2,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
pub use client::GatewayClient;
|
||||
use log::warn;
|
||||
use nym_gateway_requests::BinaryResponse;
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub use client::{GatewayClient, GatewayConfig};
|
||||
pub use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
PacketRouter,
|
||||
};
|
||||
pub use traits::GatewayPacketRouter;
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod packet_router;
|
||||
pub mod socket_state;
|
||||
pub mod traits;
|
||||
|
||||
/// Helper method for reading from websocket stream. Helps to flatten the structure.
|
||||
pub(crate) fn cleanup_socket_message(
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
// I will gladly take any suggestions on how to rename this.
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::GatewayPacketRouter;
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
use nym_sphinx::params::packet_sizes::PacketSize;
|
||||
use nym_task::TaskClient;
|
||||
|
||||
pub type MixnetMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
||||
@@ -37,77 +35,52 @@ impl PacketRouter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn route_received(
|
||||
&mut self,
|
||||
unwrapped_packets: Vec<Vec<u8>>,
|
||||
pub fn route_mixnet_messages(
|
||||
&self,
|
||||
received_messages: Vec<Vec<u8>>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let mut received_messages = Vec::new();
|
||||
let mut received_acks = Vec::new();
|
||||
|
||||
// remember: gateway removes final layer of sphinx encryption and from the unwrapped
|
||||
// data he takes the SURB-ACK and first hop address.
|
||||
// currently SURB-ACKs are attached in EVERY packet, even cover, so this is always true
|
||||
let ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
let outfox_ack_overhead =
|
||||
PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
|
||||
for received_packet in unwrapped_packets {
|
||||
if received_packet.len() == PacketSize::AckPacket.plaintext_size()
|
||||
// we don't know the real size of the payload, it could be anything <= 48 bytes
|
||||
|| received_packet.len() <= PacketSize::OutfoxAckPacket.plaintext_size()
|
||||
{
|
||||
received_acks.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::RegularPacket.plaintext_size() - ack_overhead
|
||||
|| received_packet.len()
|
||||
== PacketSize::OutfoxRegularPacket.plaintext_size() - outfox_ack_overhead
|
||||
|| received_packet.len()
|
||||
== PacketSize::OutfoxRegularPacket.size() - outfox_ack_overhead
|
||||
{
|
||||
trace!("routing regular packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket8.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended8 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket16.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended16 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket32.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended32 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else {
|
||||
// this can happen if other clients are not padding their messages
|
||||
warn!("Received message of unexpected size. Probably from an outdated client... len: {}", received_packet.len());
|
||||
received_messages.push(received_packet);
|
||||
if let Err(err) = self.mixnet_message_sender.unbounded_send(received_messages) {
|
||||
// check if the failure is due to the shutdown being in progress and thus the receiver channel
|
||||
// having already been dropped
|
||||
if self.shutdown.is_shutdown_poll() || self.shutdown.is_dummy() {
|
||||
// This should ideally not happen, but it's ok
|
||||
log::warn!("Failed to send mixnet messages due to receiver task shutdown");
|
||||
return Err(GatewayClientError::ShutdownInProgress);
|
||||
}
|
||||
// This should never happen during ordinary operation the way it's currently used.
|
||||
// Abort to be on the safe side
|
||||
panic!("Failed to send mixnet message: {err}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if !received_messages.is_empty() {
|
||||
trace!("routing 'real'");
|
||||
if let Err(err) = self.mixnet_message_sender.unbounded_send(received_messages) {
|
||||
if self.shutdown.is_shutdown_poll() || self.shutdown.is_dummy() {
|
||||
// This should ideally not happen, but it's ok
|
||||
log::warn!("Failed to send mixnet message due to receiver task shutdown");
|
||||
return Err(GatewayClientError::MixnetMsgSenderFailedToSend);
|
||||
}
|
||||
// This should never happen during ordinary operation the way it's currently used.
|
||||
// Abort to be on the safe side
|
||||
panic!("Failed to send mixnet message: {err}");
|
||||
pub fn route_acks(&self, received_acks: Vec<Vec<u8>>) -> Result<(), GatewayClientError> {
|
||||
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
|
||||
// check if the failure is due to the shutdown being in progress and thus the receiver channel
|
||||
// having already been dropped
|
||||
if self.shutdown.is_shutdown_poll() || self.shutdown.is_dummy() {
|
||||
// This should ideally not happen, but it's ok
|
||||
log::warn!("Failed to send acks due to receiver task shutdown");
|
||||
return Err(GatewayClientError::ShutdownInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
if !received_acks.is_empty() {
|
||||
trace!("routing acks");
|
||||
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
|
||||
error!("failed to send ack: {err}");
|
||||
};
|
||||
// This should never happen during ordinary operation the way it's currently used.
|
||||
// Abort to be on the safe side
|
||||
panic!("Failed to send acks: {err}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayPacketRouter for PacketRouter {
|
||||
type Error = GatewayClientError;
|
||||
|
||||
// note: this trait tries to decide whether a given message is an ack or a data message
|
||||
|
||||
fn route_mixnet_messages(&self, received_messages: Vec<Vec<u8>>) -> Result<(), Self::Error> {
|
||||
self.route_mixnet_messages(received_messages)
|
||||
}
|
||||
|
||||
fn route_acks(&self, received_acks: Vec<Vec<u8>>) -> Result<(), Self::Error> {
|
||||
self.route_acks(received_acks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::packet_router::PacketRouter;
|
||||
use crate::traits::GatewayPacketRouter;
|
||||
use crate::{cleanup_socket_messages, try_decrypt_binary_message};
|
||||
use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use log::{error, trace, warn};
|
||||
use nym_sphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
|
||||
pub trait GatewayPacketRouter {
|
||||
type Error: std::error::Error;
|
||||
|
||||
fn route_received(&self, unwrapped_packets: Vec<Vec<u8>>) -> Result<(), Self::Error> {
|
||||
let mut received_messages = Vec::new();
|
||||
let mut received_acks = Vec::new();
|
||||
|
||||
// remember: gateway removes final layer of sphinx encryption and from the unwrapped
|
||||
// data he takes the SURB-ACK and first hop address.
|
||||
// currently SURB-ACKs are attached in EVERY packet, even cover, so this is always true
|
||||
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
let outfox_ack_overhead =
|
||||
PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
|
||||
for received_packet in unwrapped_packets {
|
||||
// note: if we ever fail to route regular outfox, it might be because I've removed a match on
|
||||
// `size == PacketSize::OutfoxRegularPacket.size() - outfox_ack_overhead` since it seemed
|
||||
// redundant given we have `size == PacketSize::OutfoxRegularPacket.plaintext_size() - outfox_ack_overhead`
|
||||
// and all the headers should have already be stripped at this point
|
||||
match received_packet.len() {
|
||||
n if n == PacketSize::AckPacket.plaintext_size() => {
|
||||
trace!("received sphinx ack");
|
||||
received_acks.push(received_packet);
|
||||
}
|
||||
|
||||
n if n <= PacketSize::OutfoxAckPacket.plaintext_size() => {
|
||||
// we don't know the real size of the payload, it could be anything <= 48 bytes
|
||||
trace!("received outfox ack");
|
||||
received_acks.push(received_packet);
|
||||
}
|
||||
|
||||
n if n == PacketSize::RegularPacket.plaintext_size() - sphinx_ack_overhead => {
|
||||
trace!("received regular sphinx packet");
|
||||
received_messages.push(received_packet);
|
||||
}
|
||||
|
||||
n if n
|
||||
== PacketSize::OutfoxRegularPacket.plaintext_size() - outfox_ack_overhead =>
|
||||
{
|
||||
trace!("received regular outfox packet");
|
||||
received_messages.push(received_packet);
|
||||
}
|
||||
|
||||
n if n == PacketSize::ExtendedPacket8.plaintext_size() - sphinx_ack_overhead => {
|
||||
trace!("received extended8 packet");
|
||||
received_messages.push(received_packet);
|
||||
}
|
||||
|
||||
n if n == PacketSize::ExtendedPacket16.plaintext_size() - sphinx_ack_overhead => {
|
||||
trace!("received extended16 packet");
|
||||
received_messages.push(received_packet);
|
||||
}
|
||||
|
||||
n if n == PacketSize::ExtendedPacket32.plaintext_size() - sphinx_ack_overhead => {
|
||||
trace!("received extended32 packet");
|
||||
received_messages.push(received_packet);
|
||||
}
|
||||
|
||||
n => {
|
||||
// this can happen if other clients are not padding their messages
|
||||
warn!("Received message of unexpected size. Probably from an outdated client... len: {n}");
|
||||
received_messages.push(received_packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !received_messages.is_empty() {
|
||||
trace!("routing {} received packets", received_messages.len());
|
||||
if let Err(err) = self.route_mixnet_messages(received_messages) {
|
||||
error!("failed to route received messages: {err}");
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
if !received_acks.is_empty() {
|
||||
trace!("routing {} received acks", received_acks.len());
|
||||
if let Err(err) = self.route_acks(received_acks) {
|
||||
error!("failed to route received acks: {err}");
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn route_mixnet_messages(&self, received_messages: Vec<Vec<u8>>) -> Result<(), Self::Error>;
|
||||
|
||||
fn route_acks(&self, received_acks: Vec<Vec<u8>>) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ impl PacketForwarder {
|
||||
log::trace!("PacketForwarder: Received shutdown");
|
||||
}
|
||||
Some(mix_packet) = self.packet_receiver.next() => {
|
||||
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
|
||||
trace!("Going to forward packet to {}", mix_packet.next_hop());
|
||||
|
||||
let next_hop = mix_packet.next_hop();
|
||||
let packet_type = mix_packet.packet_type();
|
||||
|
||||
@@ -33,18 +33,17 @@ pub fn may_get_home() -> Option<PathBuf> {
|
||||
}
|
||||
|
||||
pub trait NymConfigTemplate: Serialize {
|
||||
fn template() -> &'static str;
|
||||
fn template(&self) -> &'static str;
|
||||
|
||||
fn format_to_string(&self) -> String {
|
||||
// it is responsibility of whoever is implementing the trait to ensure the template is valid
|
||||
Handlebars::new()
|
||||
.render_template(Self::template(), &self)
|
||||
.render_template(self.template(), &self)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn format_to_writer<W: Write>(&self, writer: W) -> io::Result<()> {
|
||||
if let Err(err) =
|
||||
Handlebars::new().render_template_to_write(Self::template(), &self, writer)
|
||||
if let Err(err) = Handlebars::new().render_template_to_write(self.template(), &self, writer)
|
||||
{
|
||||
match err {
|
||||
TemplateRenderError::IOError(err, _) => return Err(err),
|
||||
@@ -64,7 +63,7 @@ where
|
||||
C: NymConfigTemplate,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
log::trace!("trying to save config file to {}", path.as_ref().display());
|
||||
log::debug!("trying to save config file to {}", path.as_ref().display());
|
||||
let file = File::create(path.as_ref())?;
|
||||
|
||||
// TODO: check for whether any of our configs stores anything sensitive
|
||||
@@ -108,7 +107,7 @@ where
|
||||
//
|
||||
//
|
||||
// pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
// fn template() -> &'static str;
|
||||
// fn template(&self) -> &'static str;
|
||||
//
|
||||
// fn config_file_name() -> String {
|
||||
// "config.toml".to_string()
|
||||
|
||||
@@ -71,7 +71,7 @@ pub async fn setup_recovery_storage(recovery_dir: PathBuf) -> RecoveryStorage {
|
||||
|
||||
pub async fn setup_persistent_storage(client_home_directory: PathBuf) -> PersistentStorage {
|
||||
let data_dir = client_home_directory.join(DEFAULT_DATA_DIR);
|
||||
let paths = CommonClientPaths::new_default(data_dir);
|
||||
let paths = CommonClientPaths::new_base(data_dir);
|
||||
let db_path = paths.credentials_database;
|
||||
|
||||
nym_credential_storage::initialise_persistent_storage(db_path).await
|
||||
|
||||
@@ -150,6 +150,13 @@ impl PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl From<PublicKey> for DestinationAddressBytes {
|
||||
fn from(value: PublicKey) -> Self {
|
||||
value.derive_destination_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PublicKey {
|
||||
type Err = Ed25519RecoveryError;
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
use crate::var_names;
|
||||
use crate::{DenomDetails, ValidatorDetails};
|
||||
|
||||
pub(crate) const NETWORK_NAME: &str = "mainnet";
|
||||
pub const NETWORK_NAME: &str = "mainnet";
|
||||
|
||||
pub(crate) const BECH32_PREFIX: &str = "n";
|
||||
pub const BECH32_PREFIX: &str = "n";
|
||||
|
||||
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
|
||||
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
|
||||
@@ -15,13 +15,12 @@ pub const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
|
||||
pub const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
|
||||
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
|
||||
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const EPHEMERA_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
|
||||
pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub const EPHEMERA_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
|
||||
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
dirs = "4.0"
|
||||
futures = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::path::PathBuf;
|
||||
//
|
||||
// //
|
||||
// // impl NymConfig for OldConfigV1_1_13 {
|
||||
// // fn template() -> &'static str {
|
||||
// // fn template(&self) -> &'static str {
|
||||
// // // not intended to be used
|
||||
// // unimplemented!()
|
||||
// // }
|
||||
|
||||
@@ -19,12 +19,13 @@ use nym_client_core::client::base_client::{
|
||||
use nym_client_core::client::key_manager::persistence::KeyStore;
|
||||
use nym_client_core::client::replies::reply_storage::ReplyStorageBackend;
|
||||
use nym_client_core::config::DebugConfig;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_client_core::init::types::GatewaySetup;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -44,7 +45,7 @@ pub enum Socks5ControlMessage {
|
||||
|
||||
pub struct StartedSocks5Client {
|
||||
/// Handle for managing graceful shutdown of this client. If dropped, the client will be stopped.
|
||||
pub shutdown_handle: TaskManager,
|
||||
pub shutdown_handle: TaskHandle,
|
||||
|
||||
/// Address of the started client
|
||||
pub address: Recipient,
|
||||
@@ -155,7 +156,7 @@ where
|
||||
pub async fn run_forever(self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let started = self.start().await?;
|
||||
|
||||
let res = started.shutdown_handle.catch_interrupt().await;
|
||||
let res = started.shutdown_handle.wait_for_shutdown().await;
|
||||
log::info!("Stopping nym-socks5-client");
|
||||
res
|
||||
}
|
||||
@@ -168,7 +169,12 @@ where
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
// Start the main task
|
||||
let started = self.start().await?;
|
||||
let mut shutdown = started.shutdown_handle;
|
||||
let mut shutdown = started
|
||||
.shutdown_handle
|
||||
.try_into_task_manager()
|
||||
.ok_or(anyhow!(
|
||||
"attempted to use `run_and_listen` without owning shutdown handle"
|
||||
))?;
|
||||
|
||||
// Listen to status messages from task, that we forward back to the caller
|
||||
shutdown.start_status_listener(sender).await;
|
||||
@@ -239,7 +245,7 @@ where
|
||||
client_output,
|
||||
client_state,
|
||||
self_address,
|
||||
started_client.task_manager.subscribe(),
|
||||
started_client.task_handle.get_handle(),
|
||||
packet_type,
|
||||
);
|
||||
|
||||
@@ -247,7 +253,7 @@ where
|
||||
info!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(StartedSocks5Client {
|
||||
shutdown_handle: started_client.task_manager,
|
||||
shutdown_handle: started_client.task_handle,
|
||||
address: self_address,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ impl Controller {
|
||||
} else {
|
||||
// check if there were any pending messages
|
||||
if let Some(pending) = self.pending_messages.remove(&conn_id) {
|
||||
debug!("There were some pending messages for {}", conn_id);
|
||||
debug!("There were some pending messages for {conn_id}");
|
||||
for data in pending {
|
||||
self.send_to_connection(data)
|
||||
}
|
||||
@@ -141,12 +141,9 @@ impl Controller {
|
||||
}
|
||||
|
||||
fn remove_connection(&mut self, conn_id: ConnectionId) {
|
||||
debug!("Removing {} from controller", conn_id);
|
||||
debug!("Removing {conn_id} from controller");
|
||||
if self.active_connections.remove(&conn_id).is_none() {
|
||||
error!(
|
||||
"tried to remove non-existing connection with id: {:?}",
|
||||
conn_id
|
||||
)
|
||||
error!("tried to remove non-existing connection with id: {conn_id}",)
|
||||
}
|
||||
self.recently_closed.insert(conn_id);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ pub mod manager;
|
||||
pub mod signal;
|
||||
pub mod spawn;
|
||||
|
||||
pub use manager::{StatusReceiver, StatusSender, TaskClient, TaskManager};
|
||||
pub use manager::{StatusReceiver, StatusSender, TaskClient, TaskHandle, TaskManager};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use signal::wait_for_signal_and_error;
|
||||
|
||||
|
||||
+135
-17
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::future::Future;
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
use futures::{future::pending, FutureExt, SinkExt, StreamExt};
|
||||
use log::{log, Level};
|
||||
use std::future::Future;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{error::Error, time::Duration};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
@@ -143,6 +143,17 @@ impl TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe_named<S: Into<String>>(&self, suffix: S) -> TaskClient {
|
||||
let task_client = self.subscribe();
|
||||
let suffix = suffix.into();
|
||||
let child_name = if let Some(base) = &self.name {
|
||||
format!("{base}-{suffix}")
|
||||
} else {
|
||||
format!("unknown-{suffix}")
|
||||
};
|
||||
task_client.named(child_name)
|
||||
}
|
||||
|
||||
pub fn signal_shutdown(&self) -> Result<(), SendError<()>> {
|
||||
self.notify_tx.send(())
|
||||
}
|
||||
@@ -159,9 +170,9 @@ impl TaskManager {
|
||||
crate::spawn::spawn(async move {
|
||||
loop {
|
||||
if let Some(msg) = task_status_rx.next().await {
|
||||
log::trace!("Got msg: {}", msg);
|
||||
log::trace!("Got msg: {msg}");
|
||||
if let Err(msg) = sender.send(msg).await {
|
||||
log::error!("Error sending status message: {}", msg);
|
||||
log::error!("Error sending status message: {msg}");
|
||||
}
|
||||
} else {
|
||||
log::trace!("Stopping since channel closed");
|
||||
@@ -238,7 +249,10 @@ pub struct TaskClient {
|
||||
name: Option<String>,
|
||||
|
||||
// If a shutdown notification has been registered
|
||||
shutdown: bool,
|
||||
// the reason for having an atomic here is to be able to cheat and modify that value whilst
|
||||
// holding an immutable reference to the `TaskClient`.
|
||||
// note: using `Relaxed` ordering everywhere is fine since it's not shared between threads
|
||||
shutdown: AtomicBool,
|
||||
|
||||
// Listen for shutdown notifications, as well as a mechanism to report back that we have
|
||||
// finished (the receiver is closed).
|
||||
@@ -272,7 +286,7 @@ impl Clone for TaskClient {
|
||||
|
||||
TaskClient {
|
||||
name,
|
||||
shutdown: self.shutdown,
|
||||
shutdown: AtomicBool::new(self.shutdown.load(Ordering::Relaxed)),
|
||||
notify: self.notify.clone(),
|
||||
return_error: self.return_error.clone(),
|
||||
drop_error: self.drop_error.clone(),
|
||||
@@ -297,7 +311,7 @@ impl TaskClient {
|
||||
) -> TaskClient {
|
||||
TaskClient {
|
||||
name: None,
|
||||
shutdown: false,
|
||||
shutdown: AtomicBool::new(false),
|
||||
notify,
|
||||
return_error,
|
||||
drop_error,
|
||||
@@ -341,6 +355,17 @@ impl TaskClient {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_suffix<S: Into<String>>(self, suffix: S) -> Self {
|
||||
let suffix = suffix.into();
|
||||
let name = if let Some(base) = &self.name {
|
||||
format!("{base}-{suffix}")
|
||||
} else {
|
||||
format!("unknown-{suffix}")
|
||||
};
|
||||
self.named(name)
|
||||
}
|
||||
|
||||
pub async fn run_future<Fut, T>(&mut self, fut: Fut) -> Option<T>
|
||||
where
|
||||
Fut: Future<Output = T>,
|
||||
@@ -360,7 +385,7 @@ impl TaskClient {
|
||||
let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128);
|
||||
TaskClient {
|
||||
name: None,
|
||||
shutdown: false,
|
||||
shutdown: AtomicBool::new(false),
|
||||
notify: notify_rx,
|
||||
return_error: task_halt_tx,
|
||||
drop_error: task_drop_tx,
|
||||
@@ -377,7 +402,7 @@ impl TaskClient {
|
||||
if self.mode.is_dummy() {
|
||||
false
|
||||
} else {
|
||||
self.shutdown
|
||||
self.shutdown.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,11 +410,11 @@ impl TaskClient {
|
||||
if self.mode.is_dummy() {
|
||||
return pending().await;
|
||||
}
|
||||
if self.shutdown {
|
||||
if self.shutdown.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
let _ = self.notify.changed().await;
|
||||
self.shutdown = true;
|
||||
self.shutdown.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub async fn recv_with_delay(&mut self) {
|
||||
@@ -406,26 +431,30 @@ impl TaskClient {
|
||||
#[cfg_attr(target_arch = "wasm32", allow(clippy::needless_return))]
|
||||
return pending().await;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::timeout(
|
||||
if let Err(timeout) = tokio::time::timeout(
|
||||
Self::SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT,
|
||||
self.recv(),
|
||||
)
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
{
|
||||
self.log(Level::Error, "Task stopped without shutdown called");
|
||||
panic!("{timeout}")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_shutdown_poll(&mut self) -> bool {
|
||||
pub fn is_shutdown_poll(&self) -> bool {
|
||||
if self.mode.is_dummy() {
|
||||
return false;
|
||||
}
|
||||
if self.shutdown {
|
||||
if self.shutdown.load(Ordering::Relaxed) {
|
||||
return true;
|
||||
}
|
||||
match self.notify.has_changed() {
|
||||
Ok(has_changed) => {
|
||||
if has_changed {
|
||||
self.shutdown = true;
|
||||
self.shutdown.store(true, Ordering::Relaxed);
|
||||
}
|
||||
has_changed
|
||||
}
|
||||
@@ -520,6 +549,95 @@ impl ClientOperatingMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TaskHandle {
|
||||
/// Full [`TaskManager`] that was created by the underlying task.
|
||||
Internal(TaskManager),
|
||||
|
||||
/// `[TaskClient]` that was passed from an external task, that controls the shutdown process.
|
||||
External(TaskClient),
|
||||
}
|
||||
|
||||
impl From<TaskManager> for TaskHandle {
|
||||
fn from(value: TaskManager) -> Self {
|
||||
TaskHandle::Internal(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TaskClient> for TaskHandle {
|
||||
fn from(value: TaskClient) -> Self {
|
||||
TaskHandle::External(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TaskHandle {
|
||||
fn default() -> Self {
|
||||
TaskHandle::Internal(TaskManager::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskHandle {
|
||||
#[must_use]
|
||||
pub fn name_if_unnamed<S: Into<String>>(self, name: S) -> Self {
|
||||
match self {
|
||||
TaskHandle::Internal(task_manager) => {
|
||||
if task_manager.name.is_none() {
|
||||
TaskHandle::Internal(task_manager.named(name))
|
||||
} else {
|
||||
TaskHandle::Internal(task_manager)
|
||||
}
|
||||
}
|
||||
TaskHandle::External(task_client) => {
|
||||
if task_client.name.is_none() {
|
||||
TaskHandle::External(task_client.named(name))
|
||||
} else {
|
||||
TaskHandle::External(task_client)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn named<S: Into<String>>(self, name: S) -> Self {
|
||||
match self {
|
||||
TaskHandle::Internal(task_manager) => TaskHandle::Internal(task_manager.named(name)),
|
||||
TaskHandle::External(task_client) => TaskHandle::External(task_client.named(name)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> TaskClient {
|
||||
match self {
|
||||
TaskHandle::External(shutdown) => shutdown.fork(child_suffix),
|
||||
TaskHandle::Internal(shutdown) => shutdown.subscribe_named(child_suffix),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_handle(&self) -> TaskClient {
|
||||
match self {
|
||||
TaskHandle::External(shutdown) => shutdown.clone(),
|
||||
TaskHandle::Internal(shutdown) => shutdown.subscribe(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_into_task_manager(self) -> Option<TaskManager> {
|
||||
match self {
|
||||
TaskHandle::External(_) => None,
|
||||
TaskHandle::Internal(shutdown) => Some(shutdown),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn wait_for_shutdown(self) -> Result<(), SentError> {
|
||||
match self {
|
||||
TaskHandle::Internal(task_manager) => task_manager.catch_interrupt().await,
|
||||
TaskHandle::External(mut task_client) => {
|
||||
task_client.recv().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -21,6 +21,9 @@ pub enum NymTopologyError {
|
||||
#[error("Gateway with identity key {identity_key} doesn't exist")]
|
||||
NonExistentGatewayError { identity_key: String },
|
||||
|
||||
#[error("timed out while waiting for gateway '{identity_key}' to come online")]
|
||||
TimedOutWaitingForGateway { identity_key: String },
|
||||
|
||||
#[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")]
|
||||
InvalidNumberOfHopsError { available: usize, requested: usize },
|
||||
|
||||
|
||||
@@ -402,8 +402,8 @@ pub fn nym_topology_from_detailed(
|
||||
let layer = bond.layer as MixLayer;
|
||||
if layer == 0 || layer > 3 {
|
||||
warn!(
|
||||
"{} says it's on invalid layer {}!",
|
||||
bond.mix_node.identity_key, layer
|
||||
"{} says it's on invalid layer {layer}!",
|
||||
bond.mix_node.identity_key
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -414,7 +414,7 @@ pub fn nym_topology_from_detailed(
|
||||
match bond.try_into() {
|
||||
Ok(mix) => layer_entry.push(mix),
|
||||
Err(err) => {
|
||||
warn!("Mix {} / {} is malformed - {err}", mix_id, mix_identity);
|
||||
warn!("Mix {mix_id} / {mix_identity} is malformed: {err}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -426,7 +426,7 @@ pub fn nym_topology_from_detailed(
|
||||
match bond.try_into() {
|
||||
Ok(gate) => gateways.push(gate),
|
||||
Err(err) => {
|
||||
warn!("Gateway {} is malformed - {err}", gate_id);
|
||||
warn!("Gateway {gate_id} is malformed: {err}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,23 +82,67 @@ pub struct GatewayNodeDetailsResponse {
|
||||
pub identity_key: String,
|
||||
pub sphinx_key: String,
|
||||
pub bind_address: String,
|
||||
pub version: String,
|
||||
pub mix_port: u16,
|
||||
pub clients_port: u16,
|
||||
pub config_path: String,
|
||||
pub data_store: String,
|
||||
|
||||
pub network_requester: Option<GatewayNetworkRequesterDetails>,
|
||||
}
|
||||
|
||||
impl fmt::Display for GatewayNodeDetailsResponse {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Identity Key: {}", self.identity_key)?;
|
||||
writeln!(f, "Sphinx Key: {}", self.sphinx_key)?;
|
||||
writeln!(f, "Version: {}", self.version)?;
|
||||
writeln!(f, "config path: {}", self.config_path)?;
|
||||
writeln!(f, "identity key: {}", self.identity_key)?;
|
||||
writeln!(f, "sphinx key: {}", self.sphinx_key)?;
|
||||
writeln!(f, "bind address: {}", self.bind_address)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Mix Port: {}, Clients port: {}",
|
||||
"mix Port: {}, clients port: {}",
|
||||
self.mix_port, self.clients_port
|
||||
)?;
|
||||
|
||||
writeln!(f, "Data store is at: {}", self.data_store)
|
||||
writeln!(f, "data store is at: {}", self.data_store)?;
|
||||
|
||||
if let Some(nr) = &self.network_requester {
|
||||
nr.fmt(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GatewayNetworkRequesterDetails {
|
||||
pub enabled: bool,
|
||||
|
||||
pub identity_key: String,
|
||||
pub encryption_key: String,
|
||||
|
||||
pub open_proxy: bool,
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
// just a convenience wrapper around all the keys
|
||||
pub address: String,
|
||||
|
||||
pub config_path: String,
|
||||
pub allow_list_path: String,
|
||||
pub unknown_list_path: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for GatewayNetworkRequesterDetails {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Network requester:")?;
|
||||
writeln!(f, "\tenabled: {}", self.enabled)?;
|
||||
writeln!(f, "\tconfig path: {}", self.config_path)?;
|
||||
|
||||
writeln!(f, "\tidentity key: {}", self.identity_key)?;
|
||||
writeln!(f, "\tencryption key: {}", self.encryption_key)?;
|
||||
writeln!(f, "\taddress: {}", self.address)?;
|
||||
|
||||
writeln!(f, "\tuses open proxy: {}", self.open_proxy)?;
|
||||
writeln!(f, "\tsends statistics: {}", self.enabled_statistics)?;
|
||||
|
||||
writeln!(f, "\tallow list path: {}", self.allow_list_path)?;
|
||||
writeln!(f, "\tunknown list path: {}", self.allow_list_path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,6 +356,10 @@ pub struct TopologyWasm {
|
||||
/// did not reach its destination.
|
||||
pub topology_resolution_timeout_ms: u32,
|
||||
|
||||
/// Defines how long the client is going to wait on startup for its gateway to come online,
|
||||
/// before abandoning the procedure.
|
||||
pub max_startup_gateway_waiting_period_ms: u32,
|
||||
|
||||
/// Specifies whether the client should not refresh the network topology after obtaining
|
||||
/// the first valid instance.
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
@@ -376,6 +380,9 @@ impl From<TopologyWasm> for ConfigTopology {
|
||||
topology.topology_resolution_timeout_ms as u64,
|
||||
),
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
max_startup_gateway_waiting_period: Duration::from_millis(
|
||||
topology.max_startup_gateway_waiting_period_ms as u64,
|
||||
),
|
||||
topology_structure: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -386,6 +393,9 @@ impl From<ConfigTopology> for TopologyWasm {
|
||||
TopologyWasm {
|
||||
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u32,
|
||||
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u32,
|
||||
max_startup_gateway_waiting_period_ms: topology
|
||||
.max_startup_gateway_waiting_period
|
||||
.as_millis() as u32,
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,11 @@ pub struct TopologyWasmOverride {
|
||||
#[tsify(optional)]
|
||||
pub topology_resolution_timeout_ms: Option<u32>,
|
||||
|
||||
/// Defines how long the client is going to wait on startup for its gateway to come online,
|
||||
/// before abandoning the procedure.
|
||||
#[tsify(optional)]
|
||||
pub max_startup_gateway_waiting_period_ms: Option<u32>,
|
||||
|
||||
/// Specifies whether the client should not refresh the network topology after obtaining
|
||||
/// the first valid instance.
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
@@ -246,6 +251,9 @@ impl From<TopologyWasmOverride> for TopologyWasm {
|
||||
topology_resolution_timeout_ms: value
|
||||
.topology_resolution_timeout_ms
|
||||
.unwrap_or(def.topology_resolution_timeout_ms),
|
||||
max_startup_gateway_waiting_period_ms: value
|
||||
.max_startup_gateway_waiting_period_ms
|
||||
.unwrap_or(def.max_startup_gateway_waiting_period_ms),
|
||||
disable_refreshing: value.disable_refreshing.unwrap_or(def.disable_refreshing),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,11 @@ use js_sys::Promise;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_client_core::config;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::{setup_gateway_from, GatewaySetup, InitialisationResult};
|
||||
use nym_client_core::init::types::GatewaySelectionSpecification;
|
||||
use nym_client_core::init::{
|
||||
self,
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_topology::{gateway, NymTopology, SerializableNymTopology};
|
||||
@@ -88,10 +92,16 @@ async fn setup_gateway(
|
||||
let setup = if client_store.has_full_gateway_info().await? {
|
||||
GatewaySetup::MustLoad
|
||||
} else {
|
||||
GatewaySetup::new_fresh(chosen_gateway.clone(), None)
|
||||
let selection_spec = GatewaySelectionSpecification::new(chosen_gateway.clone(), None);
|
||||
|
||||
GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways: gateways.to_vec(),
|
||||
overwrite_data: false,
|
||||
}
|
||||
};
|
||||
|
||||
setup_gateway_from(setup, client_store, client_store, false, Some(gateways))
|
||||
init::setup_gateway(setup, client_store, client_store)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -16,11 +16,9 @@ pub mod topology;
|
||||
pub use nym_bandwidth_controller::BandwidthController;
|
||||
pub use nym_client_core::*;
|
||||
pub use nym_client_core::{
|
||||
client::key_manager::ManagedKeys,
|
||||
error::ClientCoreError,
|
||||
init::{InitialisationDetails, InitialisationResult},
|
||||
client::key_manager::ManagedKeys, error::ClientCoreError, init::types::InitialisationResult,
|
||||
};
|
||||
pub use nym_gateway_client::{error::GatewayClientError, GatewayClient};
|
||||
pub use nym_gateway_client::{error::GatewayClientError, GatewayClient, GatewayConfig};
|
||||
pub use nym_sphinx::{
|
||||
addressing::{clients::Recipient, nodes::NodeIdentity},
|
||||
params::PacketType,
|
||||
|
||||
@@ -80,7 +80,7 @@ impl KeyStore for ClientStorage {
|
||||
Ok(KeyManager::from_keys(
|
||||
identity_keypair,
|
||||
encryption_keypair,
|
||||
gateway_shared_key,
|
||||
Some(gateway_shared_key),
|
||||
ack_keypair,
|
||||
))
|
||||
}
|
||||
@@ -93,8 +93,11 @@ impl KeyStore for ClientStorage {
|
||||
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
|
||||
|
||||
if let Some(shared_keys) = keys.gateway_shared_key() {
|
||||
self.store_gateway_shared_key(&shared_keys).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,6 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient {
|
||||
.await
|
||||
.unwrap()
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client.connect_to_mixnet().await.unwrap()
|
||||
|
||||
+3
-2
@@ -15,14 +15,14 @@ rust-version = "1.56"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.53"
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
atty = "0.2"
|
||||
bip39 = { workspace = true }
|
||||
bs58 = "0.4.0"
|
||||
clap = { version = "4.0", features = ["cargo", "derive"] }
|
||||
colored = "2.0"
|
||||
dashmap = "4.0"
|
||||
dashmap = { workspace = true }
|
||||
dirs = "4.0"
|
||||
dotenvy = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
@@ -55,6 +55,7 @@ nym-gateway-requests = { path = "gateway-requests" }
|
||||
nym-mixnet-client = { path = "../common/client-libs/mixnet-client" }
|
||||
nym-mixnode-common = { path = "../common/mixnode-common" }
|
||||
nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-network-requester = { path = "../service-providers/network-requester" }
|
||||
nym-pemstore = { path = "../common/pemstore" }
|
||||
nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-statistics-common = { path = "../common/statistics" }
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::upgrade_helpers;
|
||||
use crate::config::default_config_filepath;
|
||||
use crate::config::persistence::paths::default_network_requester_data_dir;
|
||||
use crate::config::Config;
|
||||
use crate::error::GatewayError;
|
||||
use log::{error, info};
|
||||
use nym_bin_common::version_checker;
|
||||
use nym_config::{save_formatted_config_to_file, OptionalSet};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_network_defaults::mainnet;
|
||||
use nym_network_defaults::var_names::NYXD;
|
||||
use nym_network_defaults::var_names::{BECH32_PREFIX, NYM_API, STATISTICS_SERVICE_DOMAIN_ADDRESS};
|
||||
use nym_network_requester::config::BaseClientConfig;
|
||||
use nym_network_requester::{
|
||||
setup_gateway, GatewaySelectionSpecification, GatewaySetup, OnDiskGatewayDetails, OnDiskKeys,
|
||||
};
|
||||
use nym_types::gateway::GatewayNetworkRequesterDetails;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Configuration that can be overridden.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OverrideConfig {
|
||||
pub(crate) host: Option<IpAddr>,
|
||||
pub(crate) mix_port: Option<u16>,
|
||||
pub(crate) clients_port: Option<u16>,
|
||||
pub(crate) datastore: Option<PathBuf>,
|
||||
pub(crate) enabled_statistics: Option<bool>,
|
||||
pub(crate) statistics_service_url: Option<url::Url>,
|
||||
pub(crate) nym_apis: Option<Vec<url::Url>>,
|
||||
pub(crate) mnemonic: Option<bip39::Mnemonic>,
|
||||
pub(crate) nyxd_urls: Option<Vec<url::Url>>,
|
||||
pub(crate) only_coconut_credentials: Option<bool>,
|
||||
pub(crate) with_network_requester: Option<bool>,
|
||||
}
|
||||
|
||||
impl OverrideConfig {
|
||||
pub(crate) fn do_override(self, mut config: Config) -> Result<Config, GatewayError> {
|
||||
config = config
|
||||
.with_optional(Config::with_listening_address, self.host)
|
||||
.with_optional(Config::with_mix_port, self.mix_port)
|
||||
.with_optional(Config::with_clients_port, self.clients_port)
|
||||
.with_optional_custom_env(
|
||||
Config::with_custom_nym_apis,
|
||||
self.nym_apis,
|
||||
NYM_API,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional(Config::with_enabled_statistics, self.enabled_statistics)
|
||||
.with_optional_env(
|
||||
Config::with_custom_statistics_service_url,
|
||||
self.statistics_service_url,
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS,
|
||||
)
|
||||
.with_optional(Config::with_custom_persistent_store, self.datastore)
|
||||
.with_optional(Config::with_cosmos_mnemonic, self.mnemonic)
|
||||
.with_optional_custom_env(
|
||||
Config::with_custom_validator_nyxd,
|
||||
self.nyxd_urls,
|
||||
NYXD,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional(
|
||||
Config::with_only_coconut_credentials,
|
||||
self.only_coconut_credentials,
|
||||
)
|
||||
.with_optional(
|
||||
Config::with_enabled_network_requester,
|
||||
self.with_network_requester,
|
||||
);
|
||||
|
||||
if config.network_requester.enabled
|
||||
&& config.storage_paths.network_requester_config.is_none()
|
||||
{
|
||||
Ok(config.with_default_network_requester_config_path())
|
||||
} else {
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OverrideNetworkRequesterConfig {
|
||||
pub(crate) fastmode: bool,
|
||||
pub(crate) no_cover: bool,
|
||||
pub(crate) medium_toggle: bool,
|
||||
|
||||
pub(crate) open_proxy: Option<bool>,
|
||||
pub(crate) enable_statistics: Option<bool>,
|
||||
pub(crate) statistics_recipient: Option<String>,
|
||||
}
|
||||
|
||||
/// Ensures that a given bech32 address is valid
|
||||
pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), GatewayError> {
|
||||
let expected_prefix =
|
||||
std::env::var(BECH32_PREFIX).unwrap_or(mainnet::BECH32_PREFIX.to_string());
|
||||
let actual_prefix = address.prefix();
|
||||
|
||||
if expected_prefix != actual_prefix {
|
||||
return Err(GatewayError::InvalidBech32AccountPrefix {
|
||||
account: address.to_owned(),
|
||||
expected_prefix,
|
||||
actual_prefix: actual_prefix.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
pub(crate) fn ensure_config_version_compatibility(cfg: &Config) -> Result<(), GatewayError> {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = &cfg.gateway.version;
|
||||
|
||||
if binary_version == config_version {
|
||||
Ok(())
|
||||
} else if version_checker::is_minor_version_compatible(binary_version, config_version) {
|
||||
log::warn!(
|
||||
"The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \
|
||||
But, they are still semver compatible. However, consider running the `upgrade` command.");
|
||||
Ok(())
|
||||
} else {
|
||||
log::error!(
|
||||
"The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \
|
||||
And they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
Err(GatewayError::LocalVersionCheckFailure {
|
||||
binary_version: binary_version.to_owned(),
|
||||
config_version: config_version.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_load_current_config(id: &str) -> Result<Config, GatewayError> {
|
||||
upgrade_helpers::try_upgrade_config(id)?;
|
||||
|
||||
Config::read_from_default_path(id).map_err(|err| {
|
||||
error!(
|
||||
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
|
||||
);
|
||||
GatewayError::ConfigLoadFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn make_nr_id(gateway_id: &str) -> String {
|
||||
format!("{gateway_id}-network-requester")
|
||||
}
|
||||
|
||||
// NOTE: make sure this is in sync with service-providers/network-requester/src/cli/mod.rs::override_config
|
||||
pub(crate) fn override_network_requester_config(
|
||||
mut cfg: nym_network_requester::Config,
|
||||
opts: Option<OverrideNetworkRequesterConfig>,
|
||||
) -> nym_network_requester::Config {
|
||||
let Some(opts) = opts else { return cfg };
|
||||
|
||||
// as of 12.09.23 the below is true (not sure how this comment will rot in the future)
|
||||
// medium_toggle:
|
||||
// - sets secondary packet size to 16kb
|
||||
// - disables poisson distribution of the main traffic stream
|
||||
// - sets the cover traffic stream to 1 packet / 5s (on average)
|
||||
// - disables per hop delay
|
||||
//
|
||||
// fastmode (to be renamed to `fast-poisson`):
|
||||
// - sets average per hop delay to 10ms
|
||||
// - sets the cover traffic stream to 1 packet / 2000s (on average); for all intents and purposes it disables the stream
|
||||
// - sets the poisson distribution of the main traffic stream to 4ms, i.e. 250 packets / s on average
|
||||
//
|
||||
// no_cover:
|
||||
// - disables poisson distribution of the main traffic stream
|
||||
// - disables the secondary cover traffic stream
|
||||
|
||||
// disable poisson rate in the BASE client if the NR option is enabled
|
||||
if cfg.network_requester.disable_poisson_rate {
|
||||
cfg.set_no_poisson_process();
|
||||
}
|
||||
|
||||
// those should be enforced by `clap` when parsing the arguments
|
||||
if opts.medium_toggle {
|
||||
assert!(!opts.fastmode);
|
||||
assert!(!opts.no_cover);
|
||||
|
||||
cfg.set_medium_toggle();
|
||||
}
|
||||
|
||||
cfg.with_base(
|
||||
BaseClientConfig::with_high_default_traffic_volume,
|
||||
opts.fastmode,
|
||||
)
|
||||
.with_base(BaseClientConfig::with_disabled_cover_traffic, opts.no_cover)
|
||||
.with_optional(
|
||||
nym_network_requester::Config::with_open_proxy,
|
||||
opts.open_proxy,
|
||||
)
|
||||
.with_optional(
|
||||
nym_network_requester::Config::with_enabled_statistics,
|
||||
opts.enable_statistics,
|
||||
)
|
||||
.with_optional(
|
||||
nym_network_requester::Config::with_statistics_recipient,
|
||||
opts.statistics_recipient,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn initialise_local_network_requester(
|
||||
gateway_config: &Config,
|
||||
opts: OverrideNetworkRequesterConfig,
|
||||
identity: identity::PublicKey,
|
||||
) -> Result<GatewayNetworkRequesterDetails, GatewayError> {
|
||||
info!("initialising network requester...");
|
||||
let Some(nr_cfg_path) = gateway_config.storage_paths.network_requester_config() else {
|
||||
return Err(GatewayError::UnspecifiedNetworkRequesterConfig);
|
||||
};
|
||||
|
||||
let id = &gateway_config.gateway.id;
|
||||
let nr_id = make_nr_id(id);
|
||||
let nr_data_dir = default_network_requester_data_dir(id);
|
||||
let mut nr_cfg = nym_network_requester::Config::new(&nr_id).with_data_directory(nr_data_dir);
|
||||
nr_cfg = override_network_requester_config(nr_cfg, Some(opts));
|
||||
|
||||
let key_store = OnDiskKeys::new(nr_cfg.storage_paths.common_paths.keys.clone());
|
||||
let details_store =
|
||||
OnDiskGatewayDetails::new(&nr_cfg.storage_paths.common_paths.gateway_details);
|
||||
|
||||
// gateway setup here is way simpler as we're 'connecting' to ourselves
|
||||
let init_res = setup_gateway(
|
||||
GatewaySetup::New {
|
||||
specification: GatewaySelectionSpecification::Custom {
|
||||
gateway_identity: identity.to_base58_string(),
|
||||
additional_data: Default::default(),
|
||||
},
|
||||
available_gateways: vec![],
|
||||
overwrite_data: false,
|
||||
},
|
||||
&key_store,
|
||||
&details_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let address = init_res.client_address()?;
|
||||
|
||||
if let Err(err) = save_formatted_config_to_file(&nr_cfg, nr_cfg_path) {
|
||||
log::error!("Failed to save the network requester config file: {err}");
|
||||
return Err(GatewayError::ConfigSaveFailure {
|
||||
id: nr_id,
|
||||
path: nr_cfg_path.to_path_buf(),
|
||||
source: err,
|
||||
});
|
||||
};
|
||||
|
||||
Ok(GatewayNetworkRequesterDetails {
|
||||
enabled: gateway_config.network_requester.enabled,
|
||||
identity_key: address.identity().to_string(),
|
||||
encryption_key: address.encryption_key().to_string(),
|
||||
open_proxy: nr_cfg.network_requester.open_proxy,
|
||||
enabled_statistics: nr_cfg.network_requester.enabled_statistics,
|
||||
address: address.to_string(),
|
||||
config_path: nr_cfg_path.display().to_string(),
|
||||
allow_list_path: nr_cfg
|
||||
.storage_paths
|
||||
.allowed_list_location
|
||||
.display()
|
||||
.to_string(),
|
||||
unknown_list_path: nr_cfg
|
||||
.storage_paths
|
||||
.unknown_list_location
|
||||
.display()
|
||||
.to_string(),
|
||||
})
|
||||
}
|
||||
+125
-40
@@ -1,15 +1,15 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::{default_config_directory, default_config_filepath, default_data_directory};
|
||||
use crate::{
|
||||
commands::{override_config, OverrideConfig},
|
||||
config::Config,
|
||||
OutputFormat,
|
||||
use crate::commands::helpers::{
|
||||
initialise_local_network_requester, OverrideNetworkRequesterConfig,
|
||||
};
|
||||
use crate::config::{default_config_directory, default_config_filepath, default_data_directory};
|
||||
use crate::node::helpers::node_details;
|
||||
use crate::{commands::helpers::OverrideConfig, config::Config, OutputFormat};
|
||||
use anyhow::bail;
|
||||
use clap::Args;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, io};
|
||||
@@ -17,32 +17,32 @@ use std::{fs, io};
|
||||
#[derive(Args, Clone)]
|
||||
pub struct Init {
|
||||
/// Id of the gateway we want to create config for
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
id: String,
|
||||
|
||||
/// The custom host on which the gateway will be running for receiving sphinx packets
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
host: IpAddr,
|
||||
|
||||
/// The port on which the gateway will be listening for sphinx packets
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
mix_port: Option<u16>,
|
||||
|
||||
/// The port on which the gateway will be listening for clients gateway-requests
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
clients_port: Option<u16>,
|
||||
|
||||
/// Path to sqlite database containing all gateway persistent data
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
datastore: Option<PathBuf>,
|
||||
|
||||
/// Comma separated list of endpoints of nym APIs
|
||||
#[clap(long, alias = "validator_apis", value_delimiter = ',')]
|
||||
#[arg(long, alias = "validator_apis", value_delimiter = ',')]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of endpoints of the validator
|
||||
#[clap(
|
||||
#[arg(
|
||||
long,
|
||||
alias = "validators",
|
||||
alias = "nyxd_validators",
|
||||
@@ -53,23 +53,71 @@ pub struct Init {
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Cosmos wallet mnemonic needed for double spending protection
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
/// Set this gateway to work only with coconut credentials; that would disallow clients to
|
||||
/// bypass bandwidth credential requirement
|
||||
#[clap(long, hide = true)]
|
||||
#[arg(long, hide = true)]
|
||||
only_coconut_credentials: Option<bool>,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
enabled_statistics: Option<bool>,
|
||||
|
||||
/// URL where a statistics aggregator is running. The default value is a Nym aggregator server
|
||||
#[clap(long)]
|
||||
#[arg(long)]
|
||||
statistics_service_url: Option<url::Url>,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
/// Allows this gateway to run an embedded network requester for minimal network overhead
|
||||
#[arg(long)]
|
||||
with_network_requester: bool,
|
||||
|
||||
// ##### NETWORK REQUESTER FLAGS #####
|
||||
/// Specifies whether this network requester should run in 'open-proxy' mode
|
||||
#[arg(long, requires = "with_network_requester")]
|
||||
open_proxy: Option<bool>,
|
||||
|
||||
/// Enable service anonymized statistics that get sent to a statistics aggregator server
|
||||
#[arg(long, requires = "with_network_requester")]
|
||||
enable_statistics: Option<bool>,
|
||||
|
||||
/// Mixnet client address where a statistics aggregator is running. The default value is a Nym
|
||||
/// aggregator client
|
||||
#[arg(long, requires = "with_network_requester")]
|
||||
statistics_recipient: Option<String>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[arg(
|
||||
long,
|
||||
hide = true,
|
||||
conflicts_with = "medium_toggle",
|
||||
requires = "with_network_requester"
|
||||
)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[arg(
|
||||
long,
|
||||
hide = true,
|
||||
conflicts_with = "medium_toggle",
|
||||
requires = "with_network_requester"
|
||||
)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Enable medium mixnet traffic, for experiments only.
|
||||
/// This includes things like disabling cover traffic, no per hop delays, etc.
|
||||
#[arg(
|
||||
long,
|
||||
hide = true,
|
||||
conflicts_with = "no_cover",
|
||||
conflicts_with = "fastmode",
|
||||
requires = "with_network_requester"
|
||||
)]
|
||||
medium_toggle: bool,
|
||||
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
@@ -88,6 +136,20 @@ impl From<Init> for OverrideConfig {
|
||||
|
||||
nyxd_urls: init_config.nyxd_urls,
|
||||
only_coconut_credentials: init_config.only_coconut_credentials,
|
||||
with_network_requester: Some(init_config.with_network_requester),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Init> for OverrideNetworkRequesterConfig {
|
||||
fn from(value: &'a Init) -> Self {
|
||||
OverrideNetworkRequesterConfig {
|
||||
fastmode: value.fastmode,
|
||||
no_cover: value.no_cover,
|
||||
medium_toggle: value.medium_toggle,
|
||||
open_proxy: value.open_proxy,
|
||||
enable_statistics: value.enable_statistics,
|
||||
statistics_recipient: value.statistics_recipient.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,8 +159,9 @@ fn init_paths(id: &str) -> io::Result<()> {
|
||||
fs::create_dir_all(default_config_directory(id))
|
||||
}
|
||||
|
||||
pub async fn execute(args: Init) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
pub async fn execute(args: Init) -> anyhow::Result<()> {
|
||||
eprintln!("Initialising gateway {}...", args.id);
|
||||
let output = args.output;
|
||||
|
||||
let already_init = if default_config_filepath(&args.id).exists() {
|
||||
eprintln!(
|
||||
@@ -112,52 +175,60 @@ pub async fn execute(args: Init) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
false
|
||||
};
|
||||
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
|
||||
// Initialising the config structure is just overriding a default constructed one
|
||||
let config = override_config(Config::new(&args.id), override_config_fields)?;
|
||||
let fresh_config = Config::new(&args.id);
|
||||
let nr_opts = (&args).into();
|
||||
let mut config = OverrideConfig::from(args).do_override(fresh_config)?;
|
||||
|
||||
// if gateway was already initialised, don't generate new keys
|
||||
// if gateway was already initialised, don't generate new keys, et al.
|
||||
if !already_init {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
let identity_keys = identity::KeyPair::new(&mut rng);
|
||||
let sphinx_keys = encryption::KeyPair::new(&mut rng);
|
||||
|
||||
nym_pemstore::store_keypair(
|
||||
if let Err(err) = nym_pemstore::store_keypair(
|
||||
&identity_keys,
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
config.storage_paths.private_identity_key(),
|
||||
config.storage_paths.public_identity_key(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save identity keys");
|
||||
) {
|
||||
bail!("failed to save the identity keys: {err}")
|
||||
}
|
||||
|
||||
nym_pemstore::store_keypair(
|
||||
if let Err(err) = nym_pemstore::store_keypair(
|
||||
&sphinx_keys,
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
config.storage_paths.private_encryption_key(),
|
||||
config.storage_paths.public_encryption_key(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save sphinx keys");
|
||||
) {
|
||||
bail!("failed to save the sphinx keys: {err}")
|
||||
}
|
||||
|
||||
if config.network_requester.enabled {
|
||||
initialise_local_network_requester(&config, nr_opts, *identity_keys.public_key())
|
||||
.await?;
|
||||
}
|
||||
|
||||
eprintln!("Saved identity and mixnet sphinx keypairs");
|
||||
}
|
||||
|
||||
let config_save_location = config.default_location();
|
||||
config
|
||||
.save_to_default_location()
|
||||
.expect("Failed to save the config file");
|
||||
if let Err(err) = config.save_to_default_location() {
|
||||
bail!("failed to save the config file: {err}")
|
||||
}
|
||||
config.save_path = Some(config_save_location.clone());
|
||||
|
||||
eprintln!(
|
||||
"Saved configuration file to {}",
|
||||
config_save_location.display()
|
||||
);
|
||||
eprintln!("Gateway configuration completed.\n\n\n");
|
||||
|
||||
crate::node::create_gateway(config)
|
||||
.await
|
||||
.print_node_details(args.output);
|
||||
output.to_stdout(&node_details(&config)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -184,11 +255,20 @@ mod tests {
|
||||
nyxd_urls: None,
|
||||
only_coconut_credentials: None,
|
||||
output: Default::default(),
|
||||
with_network_requester: false,
|
||||
open_proxy: None,
|
||||
enable_statistics: None,
|
||||
statistics_recipient: None,
|
||||
fastmode: false,
|
||||
no_cover: false,
|
||||
medium_toggle: false,
|
||||
};
|
||||
std::env::set_var(BECH32_PREFIX, "n");
|
||||
|
||||
let config = Config::new(&args.id);
|
||||
let config = override_config(config, OverrideConfig::from(args.clone())).unwrap();
|
||||
let fresh_config = Config::new(&args.id);
|
||||
let config = OverrideConfig::from(args)
|
||||
.do_override(fresh_config)
|
||||
.unwrap();
|
||||
|
||||
let (identity_keys, sphinx_keys) = {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
@@ -199,8 +279,13 @@ mod tests {
|
||||
};
|
||||
|
||||
// The test is really if this instantiates with InMemStorage without panics
|
||||
let _gateway =
|
||||
Gateway::new_from_keys_and_storage(config, identity_keys, sphinx_keys, InMemStorage)
|
||||
.await;
|
||||
let _gateway = Gateway::new_from_keys_and_storage(
|
||||
config,
|
||||
None,
|
||||
identity_keys,
|
||||
sphinx_keys,
|
||||
InMemStorage,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-139
@@ -1,28 +1,20 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::default_config_filepath;
|
||||
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
|
||||
use crate::error::GatewayError;
|
||||
use crate::{config::Config, Cli};
|
||||
use crate::Cli;
|
||||
use clap::CommandFactory;
|
||||
use clap::Subcommand;
|
||||
use log::{error, info};
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_bin_common::version_checker;
|
||||
use nym_config::OptionalSet;
|
||||
use nym_network_defaults::var_names::NYXD;
|
||||
use nym_network_defaults::var_names::{BECH32_PREFIX, NYM_API, STATISTICS_SERVICE_DOMAIN_ADDRESS};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub(crate) mod build_info;
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod node_details;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod setup_network_requester;
|
||||
pub(crate) mod sign;
|
||||
mod upgrade_helpers;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
@@ -35,6 +27,10 @@ pub(crate) enum Commands {
|
||||
/// Starts the gateway
|
||||
Run(run::Run),
|
||||
|
||||
/// Add network requester support to this gateway
|
||||
// essentially an option to include NR without having to setup fresh gateway
|
||||
SetupNetworkRequester(setup_network_requester::CmdArgs),
|
||||
|
||||
/// Sign text to prove ownership of this mixnode
|
||||
Sign(sign::Sign),
|
||||
|
||||
@@ -48,21 +44,6 @@ pub(crate) enum Commands {
|
||||
GenerateFigSpec,
|
||||
}
|
||||
|
||||
// Configuration that can be overridden.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OverrideConfig {
|
||||
host: Option<IpAddr>,
|
||||
mix_port: Option<u16>,
|
||||
clients_port: Option<u16>,
|
||||
datastore: Option<PathBuf>,
|
||||
enabled_statistics: Option<bool>,
|
||||
statistics_service_url: Option<url::Url>,
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
mnemonic: Option<bip39::Mnemonic>,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
only_coconut_credentials: Option<bool>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let bin_name = "nym-gateway";
|
||||
|
||||
@@ -70,6 +51,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::NodeDetails(m) => node_details::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::SetupNetworkRequester(m) => setup_network_requester::execute(m).await?,
|
||||
Commands::Sign(m) => sign::execute(m)?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name),
|
||||
@@ -78,118 +60,6 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(
|
||||
mut config: Config,
|
||||
args: OverrideConfig,
|
||||
) -> Result<Config, GatewayError> {
|
||||
config = config
|
||||
.with_optional(Config::with_listening_address, args.host)
|
||||
.with_optional(Config::with_mix_port, args.mix_port)
|
||||
.with_optional(Config::with_clients_port, args.clients_port)
|
||||
.with_optional_custom_env(
|
||||
Config::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
NYM_API,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional(Config::with_enabled_statistics, args.enabled_statistics)
|
||||
.with_optional_env(
|
||||
Config::with_custom_statistics_service_url,
|
||||
args.statistics_service_url,
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS,
|
||||
)
|
||||
.with_optional(Config::with_custom_persistent_store, args.datastore)
|
||||
.with_optional(Config::with_cosmos_mnemonic, args.mnemonic)
|
||||
.with_optional_custom_env(
|
||||
Config::with_custom_validator_nyxd,
|
||||
args.nyxd_urls,
|
||||
NYXD,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional(
|
||||
Config::with_only_coconut_credentials,
|
||||
args.only_coconut_credentials,
|
||||
);
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Ensures that a given bech32 address is valid
|
||||
pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), GatewayError> {
|
||||
let expected_prefix = std::env::var(BECH32_PREFIX).expect("bech32 prefix not set");
|
||||
let actual_prefix = address.prefix();
|
||||
if expected_prefix != actual_prefix {
|
||||
return Err(GatewayError::InvalidBech32AccountPrefix {
|
||||
account: address.to_owned(),
|
||||
expected_prefix,
|
||||
actual_prefix: actual_prefix.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
pub(crate) fn ensure_config_version_compatibility(cfg: &Config) -> Result<(), GatewayError> {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = &cfg.gateway.version;
|
||||
|
||||
if binary_version == config_version {
|
||||
Ok(())
|
||||
} else if version_checker::is_minor_version_compatible(binary_version, config_version) {
|
||||
log::warn!(
|
||||
"The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \
|
||||
But, they are still semver compatible. However, consider running the `upgrade` command.");
|
||||
Ok(())
|
||||
} else {
|
||||
log::error!(
|
||||
"The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \
|
||||
And they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
Err(GatewayError::LocalVersionCheckFailure {
|
||||
binary_version: binary_version.to_owned(),
|
||||
config_version: config_version.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_20_config(id: &str) -> Result<(), GatewayError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.20 (which is incompatible with the current, i.e. 1.1.21+)
|
||||
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(());
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.20 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn try_load_current_config(id: &str) -> Result<Config, GatewayError> {
|
||||
try_upgrade_v1_1_20_config(id)?;
|
||||
|
||||
Config::read_from_default_path(id).map_err(|err| {
|
||||
error!(
|
||||
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
|
||||
);
|
||||
GatewayError::ConfigLoadFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::OverrideConfig;
|
||||
use crate::support::config::build_config;
|
||||
use crate::commands::helpers::try_load_current_config;
|
||||
use crate::node::helpers::node_details;
|
||||
use clap::Args;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub struct NodeDetails {
|
||||
@@ -17,11 +16,9 @@ pub struct NodeDetails {
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
pub async fn execute(args: NodeDetails) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let config = build_config(args.id.clone(), OverrideConfig::default())?;
|
||||
pub async fn execute(args: NodeDetails) -> anyhow::Result<()> {
|
||||
let config = try_load_current_config(&args.id)?;
|
||||
args.output.to_stdout(&node_details(&config)?);
|
||||
|
||||
crate::node::create_gateway(config)
|
||||
.await
|
||||
.print_node_details(args.output);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::{ensure_config_version_compatibility, OverrideConfig};
|
||||
use crate::commands::helpers::{
|
||||
ensure_config_version_compatibility, OverrideConfig, OverrideNetworkRequesterConfig,
|
||||
};
|
||||
use crate::node::helpers::node_details;
|
||||
use crate::support::config::build_config;
|
||||
use clap::Args;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_config::helpers::SPECIAL_ADDRESSES;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -33,7 +35,12 @@ pub struct Run {
|
||||
datastore: Option<PathBuf>,
|
||||
|
||||
/// Comma separated list of endpoints of nym APIs
|
||||
#[clap(long, alias = "validator_apis", value_delimiter = ',')]
|
||||
#[clap(
|
||||
long,
|
||||
alias = "validator_apis",
|
||||
value_delimiter = ',',
|
||||
group = "network"
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
@@ -65,6 +72,48 @@ pub struct Run {
|
||||
#[clap(long)]
|
||||
statistics_service_url: Option<url::Url>,
|
||||
|
||||
/// Allows this gateway to run an embedded network requester for minimal network overhead
|
||||
#[clap(long)]
|
||||
with_network_requester: Option<bool>,
|
||||
|
||||
// ##### NETWORK REQUESTER FLAGS #####
|
||||
/// Specifies whether this network requester should run in 'open-proxy' mode
|
||||
#[arg(long)]
|
||||
open_proxy: Option<bool>,
|
||||
|
||||
/// Enable service anonymized statistics that get sent to a statistics aggregator server
|
||||
#[arg(long)]
|
||||
enable_statistics: Option<bool>,
|
||||
|
||||
/// Mixnet client address where a statistics aggregator is running. The default value is a Nym
|
||||
/// aggregator client
|
||||
#[arg(long)]
|
||||
statistics_recipient: Option<String>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[arg(long, hide = true, conflicts_with = "medium_toggle")]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[arg(long, hide = true, conflicts_with = "medium_toggle")]
|
||||
no_cover: bool,
|
||||
|
||||
/// Enable medium mixnet traffic, for experiments only.
|
||||
/// This includes things like disabling cover traffic, no per hop delays, etc.
|
||||
#[arg(
|
||||
long,
|
||||
hide = true,
|
||||
conflicts_with = "no_cover",
|
||||
conflicts_with = "fastmode"
|
||||
)]
|
||||
medium_toggle: bool,
|
||||
|
||||
/// Path to .json file containing custom network specification.
|
||||
/// Only usable when local network requester is enabled.
|
||||
#[clap(long, group = "network", hide = true)]
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
@@ -83,6 +132,20 @@ impl From<Run> for OverrideConfig {
|
||||
statistics_service_url: run_config.statistics_service_url,
|
||||
nyxd_urls: run_config.nyxd_urls,
|
||||
only_coconut_credentials: run_config.only_coconut_credentials,
|
||||
with_network_requester: run_config.with_network_requester,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Run> for OverrideNetworkRequesterConfig {
|
||||
fn from(value: &'a Run) -> Self {
|
||||
OverrideNetworkRequesterConfig {
|
||||
fastmode: value.fastmode,
|
||||
no_cover: value.no_cover,
|
||||
medium_toggle: value.medium_toggle,
|
||||
open_proxy: value.open_proxy,
|
||||
enable_statistics: value.enable_statistics,
|
||||
statistics_recipient: value.statistics_recipient.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,11 +160,14 @@ fn show_binding_warning(address: &str) {
|
||||
eprintln!("\n\n");
|
||||
}
|
||||
|
||||
pub async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
pub async fn execute(args: Run) -> anyhow::Result<()> {
|
||||
let id = args.id.clone();
|
||||
eprintln!("Starting gateway {id}...");
|
||||
|
||||
let output = args.output;
|
||||
let custom_mixnet = args.custom_mixnet.clone();
|
||||
let nr_opts = (&args).into();
|
||||
|
||||
let config = build_config(id, args)?;
|
||||
ensure_config_version_compatibility(&config)?;
|
||||
|
||||
@@ -109,11 +175,12 @@ pub async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
show_binding_warning(&config.gateway.listening_address.to_string());
|
||||
}
|
||||
|
||||
let mut gateway = crate::node::create_gateway(config).await;
|
||||
let node_details = node_details(&config)?;
|
||||
let gateway = crate::node::create_gateway(config, Some(nr_opts), custom_mixnet).await?;
|
||||
eprintln!(
|
||||
"\nTo bond your gateway you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\
|
||||
Select the correct version and install it to your machine. You will need to provide the following: \n ");
|
||||
gateway.print_node_details(output);
|
||||
output.to_stdout(&node_details);
|
||||
|
||||
gateway.run().await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::helpers::{
|
||||
initialise_local_network_requester, try_load_current_config, OverrideNetworkRequesterConfig,
|
||||
};
|
||||
use crate::node::helpers::load_public_key;
|
||||
use clap::Args;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub struct CmdArgs {
|
||||
/// The id of the gateway you want to initialise local network requester for.
|
||||
#[arg(long)]
|
||||
id: String,
|
||||
|
||||
/// Path to custom location for network requester's config.
|
||||
#[arg(long)]
|
||||
custom_config_path: Option<PathBuf>,
|
||||
|
||||
/// Specify whether the network requester should be enabled.
|
||||
// (you might want to create all the configs, generate keys, etc. but not actually run the NR just yet)
|
||||
#[arg(long)]
|
||||
enabled: Option<bool>,
|
||||
|
||||
// note: those flags are set as bools as we want to explicitly override any settings values
|
||||
// so say `open_proxy` was set to true in the config.toml. youd have to explicitly state `open-proxy=false`
|
||||
// as an argument here to override it as opposed to not providing the value at all.
|
||||
/// Specifies whether this network requester should run in 'open-proxy' mode
|
||||
#[arg(long)]
|
||||
open_proxy: Option<bool>,
|
||||
|
||||
/// Enable service anonymized statistics that get sent to a statistics aggregator server
|
||||
#[arg(long)]
|
||||
enable_statistics: Option<bool>,
|
||||
|
||||
/// Mixnet client address where a statistics aggregator is running. The default value is a Nym
|
||||
/// aggregator client
|
||||
#[arg(long)]
|
||||
statistics_recipient: Option<String>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[arg(long, hide = true, conflicts_with = "medium_toggle")]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[arg(long, hide = true, conflicts_with = "medium_toggle")]
|
||||
no_cover: bool,
|
||||
|
||||
/// Enable medium mixnet traffic, for experiments only.
|
||||
/// This includes things like disabling cover traffic, no per hop delays, etc.
|
||||
#[arg(
|
||||
long,
|
||||
hide = true,
|
||||
conflicts_with = "no_cover",
|
||||
conflicts_with = "fastmode"
|
||||
)]
|
||||
medium_toggle: bool,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a CmdArgs> for OverrideNetworkRequesterConfig {
|
||||
fn from(value: &'a CmdArgs) -> Self {
|
||||
OverrideNetworkRequesterConfig {
|
||||
fastmode: value.fastmode,
|
||||
no_cover: value.no_cover,
|
||||
medium_toggle: value.medium_toggle,
|
||||
open_proxy: value.open_proxy,
|
||||
enable_statistics: value.enable_statistics,
|
||||
statistics_recipient: value.statistics_recipient.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(args: CmdArgs) -> anyhow::Result<()> {
|
||||
let mut config = try_load_current_config(&args.id)?;
|
||||
let opts = (&args).into();
|
||||
|
||||
// if somebody provided config file of a custom NR, that's fine
|
||||
// but in 90% cases, I'd assume, it won't work due to invalid gateway configuration
|
||||
// but it might be nice to be able to move files around.
|
||||
if let Some(custom_config_path) = args.custom_config_path {
|
||||
// if you specified anything as the argument, overwrite whatever was already in the config file
|
||||
config.storage_paths.network_requester_config = Some(custom_config_path);
|
||||
}
|
||||
|
||||
if let Some(override_enabled) = args.enabled {
|
||||
config.network_requester.enabled = override_enabled;
|
||||
}
|
||||
|
||||
if config.storage_paths.network_requester_config.is_none() {
|
||||
config = config.with_default_network_requester_config_path()
|
||||
}
|
||||
|
||||
let identity_public_key = load_public_key(
|
||||
&config.storage_paths.keys.public_identity_key_file,
|
||||
"gateway identity",
|
||||
)?;
|
||||
let details = initialise_local_network_requester(&config, opts, identity_public_key).await?;
|
||||
config.try_save()?;
|
||||
|
||||
args.output.to_stdout(&details);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::{ensure_correct_bech32_prefix, OverrideConfig};
|
||||
use crate::error::GatewayError;
|
||||
use crate::support::config::build_config;
|
||||
use crate::{
|
||||
commands::ensure_config_version_compatibility, config::persistence::paths::GatewayPaths,
|
||||
use crate::commands::helpers::{
|
||||
ensure_config_version_compatibility, ensure_correct_bech32_prefix, OverrideConfig,
|
||||
};
|
||||
use crate::error::GatewayError;
|
||||
use crate::node::helpers::load_identity_keys;
|
||||
use crate::support::config::build_config;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{ArgGroup, Args};
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_types::helpers::ConsoleSigningOutput;
|
||||
use nym_validator_client::nyxd;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
#[clap(group(ArgGroup::new("sign").required(true).args(&["wallet_address", "text", "contract_msg"])))]
|
||||
@@ -64,16 +63,6 @@ impl TryFrom<Sign> for SignedTarget {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_identity_keys(paths: &GatewayPaths) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
paths.private_identity_key().to_owned(),
|
||||
paths.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
fn print_signed_address(
|
||||
private_key: &identity::PrivateKey,
|
||||
wallet_address: nyxd::AccountId,
|
||||
@@ -90,14 +79,11 @@ fn print_signed_text(
|
||||
text: &str,
|
||||
output: OutputFormat,
|
||||
) -> Result<(), GatewayError> {
|
||||
eprintln!(
|
||||
"Signing the text {:?} using your mixnode's Ed25519 identity key...",
|
||||
text
|
||||
);
|
||||
eprintln!("Signing the text {text:?} using your mixnode's Ed25519 identity key...",);
|
||||
|
||||
let signature = private_key.sign_text(text);
|
||||
let sign_output = ConsoleSigningOutput::new(text, signature);
|
||||
println!("{}", output.format(&sign_output));
|
||||
output.to_stdout(&sign_output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -125,6 +111,7 @@ fn print_signed_contract_msg(
|
||||
};
|
||||
|
||||
// if this is a valid json, it MUST be a valid string
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let decoded_string = String::from_utf8(decoded.clone()).unwrap();
|
||||
let signature = private_key.sign(&decoded).to_base58_string();
|
||||
|
||||
@@ -132,14 +119,14 @@ fn print_signed_contract_msg(
|
||||
println!("{}", output.format(&sign_output));
|
||||
}
|
||||
|
||||
pub fn execute(args: Sign) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
pub fn execute(args: Sign) -> anyhow::Result<()> {
|
||||
let config = build_config(args.id.clone(), OverrideConfig::default())?;
|
||||
ensure_config_version_compatibility(&config)?;
|
||||
|
||||
let output = args.output;
|
||||
let signed_target = SignedTarget::try_from(args)?;
|
||||
|
||||
let identity_keypair = load_identity_keys(&config.storage_paths);
|
||||
let identity_keypair = load_identity_keys(&config)?;
|
||||
|
||||
match signed_target {
|
||||
SignedTarget::Text(text) => {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
|
||||
use crate::config::old_config_v1_1_28::ConfigV1_1_28;
|
||||
use crate::config::{default_config_filepath, Config};
|
||||
use crate::error::GatewayError;
|
||||
use log::info;
|
||||
|
||||
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, GatewayError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.20 (which is incompatible with the current, i.e. 1.1.21+)
|
||||
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.20 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_28 = old_config.into();
|
||||
let updated: Config = updated_step1.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_28_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.28 (which is incompatible with the current, i.e. 1.1.29+)
|
||||
let Ok(old_config) = ConfigV1_1_28::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.28 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn try_upgrade_config(id: &str) -> Result<(), GatewayError> {
|
||||
if try_upgrade_v1_1_20_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_28_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::config::persistence::paths::GatewayPaths;
|
||||
use crate::config::template::CONFIG_TEMPLATE;
|
||||
use log::{debug, warn};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
use nym_config::helpers::inaddr_any;
|
||||
@@ -20,6 +21,7 @@ use url::Url;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub(crate) mod old_config_v1_1_20;
|
||||
pub(crate) mod old_config_v1_1_28;
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
|
||||
@@ -65,10 +67,16 @@ pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
pub gateway: Gateway,
|
||||
|
||||
pub storage_paths: GatewayPaths,
|
||||
|
||||
pub network_requester: NetworkRequester,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettings,
|
||||
|
||||
@@ -77,7 +85,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
@@ -85,19 +93,33 @@ impl NymConfigTemplate for Config {
|
||||
impl Config {
|
||||
pub fn new<S: AsRef<str>>(id: S) -> Self {
|
||||
Config {
|
||||
save_path: None,
|
||||
gateway: Gateway::new_default(id.as_ref()),
|
||||
storage_paths: GatewayPaths::new_default(id.as_ref()),
|
||||
network_requester: Default::default(),
|
||||
logging: Default::default(),
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// simple wrapper that reads config file and assigns path location
|
||||
fn read_from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
let path = path.as_ref();
|
||||
let mut loaded: Config = read_config_from_toml_file(path)?;
|
||||
loaded.save_path = Some(path.to_path_buf());
|
||||
debug!("loaded config file from {}", path.display());
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
// currently this is dead code, but once we allow loading configs from custom paths
|
||||
// well, we will have to be using it
|
||||
#[allow(dead_code)]
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
Self::read_from_path(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
Self::read_from_path(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn default_location(&self) -> PathBuf {
|
||||
@@ -109,6 +131,27 @@ impl Config {
|
||||
save_formatted_config_to_file(self, config_save_location)
|
||||
}
|
||||
|
||||
pub fn try_save(&self) -> io::Result<()> {
|
||||
if let Some(save_location) = &self.save_path {
|
||||
save_formatted_config_to_file(self, save_location)
|
||||
} else {
|
||||
warn!("config file save location is unknown. falling back to the default");
|
||||
self.save_to_default_location()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_enabled_network_requester(mut self, enabled_network_requester: bool) -> Self {
|
||||
self.network_requester.enabled = enabled_network_requester;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_default_network_requester_config_path(mut self) -> Self {
|
||||
self.storage_paths = self
|
||||
.storage_paths
|
||||
.with_default_network_requester_config(&self.gateway.id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_only_coconut_credentials(mut self, only_coconut_credentials: bool) -> Self {
|
||||
self.gateway.only_coconut_credentials = only_coconut_credentials;
|
||||
self
|
||||
@@ -227,6 +270,8 @@ pub struct Gateway {
|
||||
|
||||
impl Gateway {
|
||||
pub fn new_default<S: Into<String>>(id: S) -> Self {
|
||||
// allow usage of `expect` here as our default mainnet values should have been well-formed.
|
||||
#[allow(clippy::expect_used)]
|
||||
Gateway {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: id.into(),
|
||||
@@ -240,11 +285,26 @@ impl Gateway {
|
||||
.expect("Invalid default statistics service URL"),
|
||||
nym_api_urls: vec![mainnet::NYM_API.parse().expect("Invalid default API URL")],
|
||||
nyxd_urls: vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")],
|
||||
cosmos_mnemonic: bip39::Mnemonic::generate(24).unwrap(),
|
||||
cosmos_mnemonic: bip39::Mnemonic::generate(24)
|
||||
.expect("failed to generate fresh mnemonic"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkRequester {
|
||||
/// Specifies whether network requester service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequester {
|
||||
fn default() -> Self {
|
||||
NetworkRequester { enabled: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct Debug {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::paths::{GatewayPaths, KeysPaths};
|
||||
use crate::config::{Config, Debug, Gateway};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use crate::config::old_config_v1_1_28::{
|
||||
ConfigV1_1_28, DebugV1_1_28, GatewayPathsV1_1_28, GatewayV1_1_28, KeysPathsV1_1_28,
|
||||
LoggingSettingsV1_1_28,
|
||||
};
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_validator_client::nyxd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::IpAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
@@ -28,8 +29,9 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// returns a `0.0.0.0` / INADDR_ANY
|
||||
fn bind_all_address() -> IpAddr {
|
||||
"0.0.0.0".parse().unwrap()
|
||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
@@ -42,10 +44,10 @@ pub struct ConfigV1_1_20 {
|
||||
debug: DebugV1_1_20,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_20> for Config {
|
||||
impl From<ConfigV1_1_20> for ConfigV1_1_28 {
|
||||
fn from(value: ConfigV1_1_20) -> Self {
|
||||
Config {
|
||||
gateway: Gateway {
|
||||
ConfigV1_1_28 {
|
||||
gateway: GatewayV1_1_28 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
@@ -58,8 +60,8 @@ impl From<ConfigV1_1_20> for Config {
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
storage_paths: GatewayPaths {
|
||||
keys: KeysPaths {
|
||||
storage_paths: GatewayPathsV1_1_28 {
|
||||
keys: KeysPathsV1_1_28 {
|
||||
private_identity_key_file: value.gateway.private_identity_key_file,
|
||||
public_identity_key_file: value.gateway.public_identity_key_file,
|
||||
private_sphinx_key_file: value.gateway.private_sphinx_key_file,
|
||||
@@ -75,6 +77,8 @@ impl From<ConfigV1_1_20> for Config {
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_20 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
// unless this is run on some esoteric system, it should not fail thus the expect is fine
|
||||
#[allow(clippy::expect_used)]
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
@@ -112,6 +116,8 @@ pub struct GatewayV1_1_20 {
|
||||
|
||||
impl Default for GatewayV1_1_20 {
|
||||
fn default() -> Self {
|
||||
// allow usage of `expect` here as our default mainnet values should have been well-formed.
|
||||
#[allow(clippy::expect_used)]
|
||||
GatewayV1_1_20 {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
@@ -129,7 +135,8 @@ impl Default for GatewayV1_1_20 {
|
||||
.expect("Invalid default statistics service URL"),
|
||||
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
|
||||
nyxd_urls: vec![Url::from_str(NYXD_URL).expect("Invalid default nyxd URL")],
|
||||
cosmos_mnemonic: bip39::Mnemonic::generate(24).unwrap(),
|
||||
cosmos_mnemonic: bip39::Mnemonic::generate(24)
|
||||
.expect("failed to generate fresh mnemonic"),
|
||||
nym_root_directory: ConfigV1_1_20::default_root_directory(),
|
||||
persistent_storage: Default::default(),
|
||||
wallet_address: None,
|
||||
@@ -141,9 +148,9 @@ impl Default for GatewayV1_1_20 {
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct LoggingV1_1_20 {}
|
||||
|
||||
impl From<LoggingV1_1_20> for LoggingSettings {
|
||||
impl From<LoggingV1_1_20> for LoggingSettingsV1_1_28 {
|
||||
fn from(_value: LoggingV1_1_20) -> Self {
|
||||
LoggingSettings {}
|
||||
LoggingSettingsV1_1_28 {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +171,9 @@ struct DebugV1_1_20 {
|
||||
use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl From<DebugV1_1_20> for Debug {
|
||||
impl From<DebugV1_1_20> for DebugV1_1_28 {
|
||||
fn from(value: DebugV1_1_20) -> Self {
|
||||
Debug {
|
||||
DebugV1_1_28 {
|
||||
packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.initial_connection_timeout,
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::paths::{GatewayPaths, KeysPaths};
|
||||
use crate::config::{Config, Debug, Gateway};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeysPathsV1_1_28 {
|
||||
pub private_identity_key_file: PathBuf,
|
||||
pub public_identity_key_file: PathBuf,
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_28 {
|
||||
pub keys: KeysPathsV1_1_28,
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_28 {}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_28 {
|
||||
pub gateway: GatewayV1_1_28,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_28,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_28,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_28,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_28 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_28> for Config {
|
||||
fn from(value: ConfigV1_1_28) -> Self {
|
||||
Config {
|
||||
save_path: None,
|
||||
gateway: Gateway {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
storage_paths: GatewayPaths {
|
||||
keys: KeysPaths {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
network_requester_config: None,
|
||||
},
|
||||
network_requester: Default::default(),
|
||||
logging: LoggingSettings {},
|
||||
debug: Debug {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_28 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_28 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_28 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_28 {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::default_data_directory;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::config::{default_config_directory, default_data_directory};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
|
||||
@@ -12,8 +12,28 @@ pub const DEFAULT_PUBLIC_SPHINX_KEY_FILENAME: &str = "public_sphinx.pem";
|
||||
|
||||
pub const DEFAULT_CLIENTS_STORAGE_FILENAME: &str = "db.sqlite";
|
||||
|
||||
pub const DEFAULT_NETWORK_REQUESTER_CONFIG_FILENAME: &str = "network_requester_config.toml";
|
||||
pub const DEFAULT_NETWORK_REQUESTER_DATA_DIR: &str = "network-requester-data";
|
||||
|
||||
// pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml";
|
||||
|
||||
pub fn default_network_requester_data_dir<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_data_directory(id).join(DEFAULT_NETWORK_REQUESTER_DATA_DIR)
|
||||
}
|
||||
|
||||
/// makes sure that an empty path is converted into a `None` as opposed to `Some("")`
|
||||
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = PathBuf::deserialize(deserializer)?;
|
||||
if path.as_os_str().is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPaths {
|
||||
@@ -23,6 +43,10 @@ pub struct GatewayPaths {
|
||||
/// derived shared keys and available client bandwidths.
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
/// Path to the configuration of the embedded network requester.
|
||||
#[serde(deserialize_with = "de_maybe_path")]
|
||||
pub network_requester_config: Option<PathBuf>,
|
||||
// pub node_description: PathBuf,
|
||||
|
||||
// pub cosmos_bip39_mnemonic: PathBuf,
|
||||
@@ -34,9 +58,27 @@ impl GatewayPaths {
|
||||
keys: KeysPaths::new_default(id.as_ref()),
|
||||
clients_storage: default_data_directory(id).join(DEFAULT_CLIENTS_STORAGE_FILENAME),
|
||||
// node_description: default_config_filepath(id).join(DEFAULT_DESCRIPTION_FILENAME),
|
||||
network_requester_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_network_requester_config<P: AsRef<Path>>(mut self, path: P) -> Self {
|
||||
self.network_requester_config = Some(path.as_ref().into());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_default_network_requester_config<P: AsRef<Path>>(self, id: P) -> Self {
|
||||
self.with_network_requester_config(
|
||||
default_config_directory(id).join(DEFAULT_NETWORK_REQUESTER_CONFIG_FILENAME),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn network_requester_config(&self) -> &Option<PathBuf> {
|
||||
&self.network_requester_config
|
||||
}
|
||||
|
||||
pub fn private_identity_key(&self) -> &Path {
|
||||
self.keys.private_identity_key()
|
||||
}
|
||||
|
||||
@@ -53,7 +53,11 @@ nyxd_urls = [
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
cosmos_mnemonic = "{{ gateway.cosmos_mnemonic }}"
|
||||
cosmos_mnemonic = '{{ gateway.cosmos_mnemonic }}'
|
||||
|
||||
[network_requester]
|
||||
# Specifies whether network requester service is enabled in this process.
|
||||
enabled = {{ network_requester.enabled }}
|
||||
|
||||
[storage_paths]
|
||||
|
||||
@@ -73,6 +77,9 @@ keys.public_sphinx_key_file = '{{ storage_paths.keys.public_sphinx_key_file }}'
|
||||
# derived shared keys and available client bandwidths.
|
||||
clients_storage = '{{ storage_paths.clients_storage }}'
|
||||
|
||||
# Path to the configuration of the embedded network requester.
|
||||
network_requester_config = '{{ storage_paths.network_requester_config }}'
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
[logging]
|
||||
|
||||
+70
-2
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::storage::error::StorageError;
|
||||
use nym_network_requester::error::{ClientCoreError, NetworkRequesterError};
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use std::io;
|
||||
@@ -9,8 +12,24 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum GatewayError {
|
||||
#[error("failed to load {keys} keys from '{}' (private key) and '{}' (public key): {err}", .paths.private_key_path.display(), .paths.public_key_path.display())]
|
||||
KeyPairLoadFailure {
|
||||
keys: String,
|
||||
paths: nym_pemstore::KeyPairPath,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to load {key} public key from '{}': {err}", .path.display())]
|
||||
PublicKeyLoadFailure {
|
||||
key: String,
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to load config file for id {id} using path {path}. detailed message: {source}"
|
||||
"failed to load config file for id {id} using path '{}'. detailed message: {source}", path.display()
|
||||
)]
|
||||
ConfigLoadFailure {
|
||||
id: String,
|
||||
@@ -20,7 +39,17 @@ pub(crate) enum GatewayError {
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to save config file for id {id} using path {path}. detailed message: {source}"
|
||||
"failed to load config file for network requester (gateway-id: '{id}') using path '{}'. detailed message: {source}", path.display()
|
||||
)]
|
||||
NetworkRequesterConfigLoadFailure {
|
||||
id: String,
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display()
|
||||
)]
|
||||
ConfigSaveFailure {
|
||||
id: String,
|
||||
@@ -47,4 +76,43 @@ pub(crate) enum GatewayError {
|
||||
expected_prefix: String,
|
||||
actual_prefix: String,
|
||||
},
|
||||
|
||||
#[error("storage failure: {source}")]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error("Path to network requester configuration file hasn't been specified. Perhaps try to run `setup-network-requester`?")]
|
||||
UnspecifiedNetworkRequesterConfig,
|
||||
|
||||
#[error("there was an issue with the local network requester: {source}")]
|
||||
NetworkRequesterFailure {
|
||||
#[from]
|
||||
source: NetworkRequesterError,
|
||||
},
|
||||
|
||||
#[error("failed to startup local network requester")]
|
||||
NetworkRequesterStartupFailure,
|
||||
|
||||
#[error("there are no nym API endpoints available")]
|
||||
NoNymApisAvailable,
|
||||
|
||||
#[error("there are no nyxd endpoints available")]
|
||||
NoNyxdAvailable,
|
||||
|
||||
#[error("there was an issue attempting to use the validator [nyxd]: {source}")]
|
||||
ValidatorFailure {
|
||||
#[from]
|
||||
source: NyxdError,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ClientCoreError> for GatewayError {
|
||||
fn from(value: ClientCoreError) -> Self {
|
||||
// if we ever get a client core error, it must have come from the network requester
|
||||
GatewayError::NetworkRequesterFailure {
|
||||
source: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use clap::{crate_name, crate_version, Parser};
|
||||
use colored::Colorize;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
@@ -1,14 +1,41 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender};
|
||||
use crate::node::client_handling::embedded_network_requester::LocalNetworkRequesterHandle;
|
||||
use dashmap::DashMap;
|
||||
use log::warn;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender};
|
||||
enum ActiveClient {
|
||||
/// Handle to a remote client connected via a network socket.
|
||||
Remote(ClientIncomingChannels),
|
||||
|
||||
/// Handle to a locally (inside the same process) running network requester client.
|
||||
Embedded(LocalNetworkRequesterHandle),
|
||||
}
|
||||
|
||||
impl ActiveClient {
|
||||
fn get_sender_ref(&self) -> &MixMessageSender {
|
||||
match self {
|
||||
ActiveClient::Remote(remote) => &remote.mix_message_sender,
|
||||
ActiveClient::Embedded(embedded) => &embedded.mix_message_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sender(&self) -> MixMessageSender {
|
||||
match self {
|
||||
ActiveClient::Remote(remote) => remote.mix_message_sender.clone(),
|
||||
ActiveClient::Embedded(embedded) => embedded.mix_message_sender.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ActiveClientsStore(Arc<DashMap<DestinationAddressBytes, ClientIncomingChannels>>);
|
||||
pub(crate) struct ActiveClientsStore {
|
||||
inner: Arc<DashMap<DestinationAddressBytes, ActiveClient>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClientIncomingChannels {
|
||||
@@ -22,7 +49,9 @@ pub(crate) struct ClientIncomingChannels {
|
||||
impl ActiveClientsStore {
|
||||
/// Creates new instance of `ActiveClientsStore` to store in-memory handles to all currently connected clients.
|
||||
pub(crate) fn new() -> Self {
|
||||
ActiveClientsStore(Arc::new(DashMap::new()))
|
||||
ActiveClientsStore {
|
||||
inner: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to obtain sending channel to specified client. Note that if stale entry existed, it is
|
||||
@@ -31,29 +60,72 @@ impl ActiveClientsStore {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client`: address of the client for which to obtain the handle.
|
||||
pub(crate) fn get(&self, client: DestinationAddressBytes) -> Option<ClientIncomingChannels> {
|
||||
let entry = self.0.get(&client)?;
|
||||
let handle = entry.value();
|
||||
pub(crate) fn get_sender(&self, client: DestinationAddressBytes) -> Option<MixMessageSender> {
|
||||
let entry = self.inner.get(&client)?;
|
||||
let handle = entry.value().get_sender();
|
||||
|
||||
// if the entry is stale, remove it from the map
|
||||
// if handle.is_valid() {
|
||||
if !handle.mix_message_sender.is_closed() {
|
||||
Some(handle.clone())
|
||||
if !handle.is_closed() {
|
||||
Some(handle)
|
||||
} else {
|
||||
// drop the reference to the map to prevent deadlocks
|
||||
drop(entry);
|
||||
self.0.remove(&client);
|
||||
self.inner.remove(&client);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to get full handle to a remotely connected client
|
||||
pub(crate) fn get_remote_client(
|
||||
&self,
|
||||
address: DestinationAddressBytes,
|
||||
) -> Option<ClientIncomingChannels> {
|
||||
let entry = self.inner.get(&address)?;
|
||||
let handle = entry.value();
|
||||
|
||||
let ActiveClient::Remote(channels) = handle else {
|
||||
warn!("attempted to get a remote handle to a embedded network requester");
|
||||
return None;
|
||||
};
|
||||
|
||||
// if the entry is stale, remove it from the map
|
||||
if !channels.mix_message_sender.is_closed() {
|
||||
Some(channels.clone())
|
||||
} else {
|
||||
// drop the reference to the map to prevent deadlocks
|
||||
drop(entry);
|
||||
self.inner.remove(&address);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether there's already an active connection to this client.
|
||||
/// It will also remove the entry from the map if its stale.
|
||||
pub(crate) fn is_active(&self, client: DestinationAddressBytes) -> bool {
|
||||
let Some(entry) = self.inner.get(&client) else {
|
||||
return false;
|
||||
};
|
||||
let handle = entry.value().get_sender_ref();
|
||||
|
||||
// if the entry is stale, remove it from the map
|
||||
if !handle.is_closed() {
|
||||
true
|
||||
} else {
|
||||
// drop the reference to the map to prevent deadlocks
|
||||
drop(entry);
|
||||
self.inner.remove(&client);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates particular client has disconnected from the gateway and its handle should get removed.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client`: address of the client for which to remove the handle.
|
||||
pub(crate) fn disconnect(&self, client: DestinationAddressBytes) {
|
||||
self.0.remove(&client);
|
||||
self.inner.remove(&client);
|
||||
}
|
||||
|
||||
/// Insert new client handle into the store.
|
||||
@@ -62,23 +134,33 @@ impl ActiveClientsStore {
|
||||
///
|
||||
/// * `client`: address of the client for which to insert the handle.
|
||||
/// * `handle`: the sender channel for all mix packets to be pushed back onto the websocket
|
||||
pub(crate) fn insert(
|
||||
pub(crate) fn insert_remote(
|
||||
&self,
|
||||
client: DestinationAddressBytes,
|
||||
handle: MixMessageSender,
|
||||
is_active_request_sender: IsActiveRequestSender,
|
||||
) {
|
||||
self.0.insert(
|
||||
client,
|
||||
ClientIncomingChannels {
|
||||
mix_message_sender: handle,
|
||||
is_active_request_sender,
|
||||
},
|
||||
);
|
||||
let entry = ActiveClient::Remote(ClientIncomingChannels {
|
||||
mix_message_sender: handle,
|
||||
is_active_request_sender,
|
||||
});
|
||||
if self.inner.insert(client, entry).is_some() {
|
||||
panic!("inserted a duplicate remote client")
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a handle to the embedded network requester
|
||||
pub(crate) fn insert_embedded(&self, local_nr_handle: LocalNetworkRequesterHandle) {
|
||||
let key = local_nr_handle.client_destination();
|
||||
let entry = ActiveClient::Embedded(local_nr_handle);
|
||||
if self.inner.insert(key, entry).is_some() {
|
||||
// this is literally impossible since we're starting local NR before even spawning the websocket listener task
|
||||
panic!("somehow we already had a client with the same address as our local NR!")
|
||||
}
|
||||
}
|
||||
|
||||
/// Get number of active clients in store
|
||||
pub(crate) fn size(&self) -> usize {
|
||||
self.0.len()
|
||||
self.inner.len()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::client_handling::websocket::message_receiver::{
|
||||
MixMessageReceiver, MixMessageSender,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use log::{debug, error};
|
||||
use nym_network_requester::core::OnStartData;
|
||||
use nym_network_requester::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_task::TaskClient;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LocalNetworkRequesterHandle {
|
||||
/// Nym address of the embedded network requester.
|
||||
pub(crate) address: Recipient,
|
||||
|
||||
/// Message channel used internally to forward any received mix packets to the network requester.
|
||||
pub(crate) mix_message_sender: MixMessageSender,
|
||||
}
|
||||
|
||||
impl LocalNetworkRequesterHandle {
|
||||
pub(crate) fn new(start_data: OnStartData, mix_message_sender: MixMessageSender) -> Self {
|
||||
Self {
|
||||
address: start_data.address,
|
||||
mix_message_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn client_destination(&self) -> DestinationAddressBytes {
|
||||
self.address.identity().derive_destination_address()
|
||||
}
|
||||
}
|
||||
|
||||
// we could have just passed a `PacketRouter` around instead of creating a dedicated task for
|
||||
// calling the method. however, this would have caused slightly more complexity and more overhead
|
||||
// (due to more data being copied to every [mix] connection)
|
||||
//
|
||||
/// task responsible for receiving messages for locally NR requester from multiple mix connections
|
||||
/// and forwarding them via the router. kinda equivalent of a client socket handler
|
||||
pub(crate) struct MessageRouter {
|
||||
mix_receiver: MixMessageReceiver,
|
||||
packet_router: PacketRouter,
|
||||
}
|
||||
|
||||
impl MessageRouter {
|
||||
pub(crate) fn new(mix_receiver: MixMessageReceiver, packet_router: PacketRouter) -> Self {
|
||||
Self {
|
||||
mix_receiver,
|
||||
packet_router,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_with_shutdown(self, shutdown: TaskClient) {
|
||||
tokio::spawn(self.run_with_shutdown(shutdown));
|
||||
}
|
||||
|
||||
fn handle_received_messages(&self, messages: Vec<Vec<u8>>) {
|
||||
if let Err(err) = self.packet_router.route_received(messages) {
|
||||
// TODO: what should we do here? I don't think this could/should ever fail.
|
||||
// is panicking the appropriate thing to do then?
|
||||
error!("failed to route packets to local NR: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run_with_shutdown(mut self, mut shutdown: TaskClient) {
|
||||
debug!("Started embedded network requester message router with graceful shutdown support");
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
messages = self.mix_receiver.next() => match messages {
|
||||
Some(messages) => self.handle_received_messages(messages),
|
||||
None => {
|
||||
log::trace!("embedded_network_requester::MessageRouter: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("embedded_network_requester::MessageRouter: Received shutdown");
|
||||
debug_assert!(shutdown.is_shutdown());
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("embedded_network_requester::MessageRouter: Exiting")
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
pub(crate) mod active_clients;
|
||||
mod bandwidth;
|
||||
pub(crate) mod embedded_network_requester;
|
||||
pub(crate) mod websocket;
|
||||
|
||||
pub(crate) const FREE_TESTNET_BANDWIDTH_VALUE: i64 = 64 * 1024 * 1024 * 1024; // 64GB
|
||||
|
||||
@@ -375,21 +375,24 @@ where
|
||||
async fn handle_pong(&mut self, msg: Vec<u8>) {
|
||||
if let Ok(msg) = msg.try_into() {
|
||||
let msg = u64::from_be_bytes(msg);
|
||||
trace!("Received pong from client: {}", msg);
|
||||
trace!("Received pong from client: {msg}");
|
||||
if let Some((tag, _)) = &self.is_active_ping_pending_reply {
|
||||
if tag == &msg {
|
||||
debug!("Reporting back to the handler that the client is still active");
|
||||
// safety:
|
||||
// the unwrap here is fine as we can only enter this if branch if `self.is_active_ping_pending_reply`
|
||||
// was a `Some`
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let tx = self.is_active_ping_pending_reply.take().unwrap().1;
|
||||
if let Err(err) = tx.send(IsActive::Active) {
|
||||
warn!("Failed to send pong reply back to the requesting handler: {err:?}");
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Received pong reply from the client with unexpected tag: {}",
|
||||
msg
|
||||
);
|
||||
warn!("Received pong reply from the client with unexpected tag: {msg}",);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("the received pong message was not a valid u64")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 futures::{
|
||||
@@ -10,6 +10,7 @@ use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::authentication::encrypted_address::{
|
||||
EncryptedAddressBytes, EncryptedAddressConversionError,
|
||||
};
|
||||
use nym_gateway_requests::registration::handshake::shared_key::SharedKeyConversionError;
|
||||
use nym_gateway_requests::{
|
||||
iv::{IVConversionError, IV},
|
||||
registration::handshake::{error::HandshakeError, gateway_handshake, SharedKeys},
|
||||
@@ -43,26 +44,35 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error("Internal gateway storage error")]
|
||||
StorageError(#[from] StorageError),
|
||||
|
||||
#[error("Failed to perform registration handshake - {0}")]
|
||||
#[error(
|
||||
"our datastore is corrupted. the stored key for client {client_id} is malformed: {source}"
|
||||
)]
|
||||
MalformedStoredSharedKey {
|
||||
client_id: String,
|
||||
#[source]
|
||||
source: SharedKeyConversionError,
|
||||
},
|
||||
|
||||
#[error("Failed to perform registration handshake: {0}")]
|
||||
HandshakeError(#[from] HandshakeError),
|
||||
|
||||
#[error("Provided client address is malformed - {0}")]
|
||||
// sphinx error is not used here directly as it's messaging might be confusing to people
|
||||
#[error("Provided client address is malformed: {0}")]
|
||||
// sphinx error is not used here directly as its messaging might be confusing to people
|
||||
MalformedClientAddress(String),
|
||||
|
||||
#[error("Provided encrypted client address is malformed - {0}")]
|
||||
#[error("Provided encrypted client address is malformed: {0}")]
|
||||
MalformedEncryptedAddress(#[from] EncryptedAddressConversionError),
|
||||
|
||||
#[error("There is already an open connection to this client")]
|
||||
DuplicateConnection,
|
||||
|
||||
#[error("Provided authentication IV is malformed - {0}")]
|
||||
#[error("Provided authentication IV is malformed: {0}")]
|
||||
MalformedIV(#[from] IVConversionError),
|
||||
|
||||
#[error("Only 'Register' or 'Authenticate' requests are allowed")]
|
||||
InvalidRequest,
|
||||
|
||||
#[error("Experienced connection error - {0}")]
|
||||
#[error("Experienced connection error: {0}")]
|
||||
ConnectionError(#[from] WsError),
|
||||
|
||||
#[error("Attempted to negotiate connection with client using incompatible protocol version. Ours is {current} and the client reports {client:?}")]
|
||||
@@ -316,13 +326,19 @@ where
|
||||
let shared_keys = self.storage.get_shared_keys(client_address).await?;
|
||||
|
||||
if let Some(shared_keys) = shared_keys {
|
||||
// the unwrap here is fine as we only ever construct persisted shared keys ourselves when inserting
|
||||
// this should never fail as we only ever construct persisted shared keys ourselves when inserting
|
||||
// data to the storage. The only way it could fail is if we somehow changed implementation without
|
||||
// performing proper migration
|
||||
let keys = SharedKeys::try_from_base58_string(
|
||||
shared_keys.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
)
|
||||
.unwrap();
|
||||
.map_err(|source| {
|
||||
InitialAuthenticationError::MalformedStoredSharedKey {
|
||||
client_id: client_address.as_base58_string(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
|
||||
// TODO: SECURITY:
|
||||
// this is actually what we have been doing in the past, however,
|
||||
// after looking deeper into implementation it seems that only checks the encryption
|
||||
@@ -482,8 +498,8 @@ where
|
||||
let iv = IV::try_from_base58_string(iv)?;
|
||||
|
||||
// Check for duplicate clients
|
||||
if let Some(client_tx) = self.active_clients_store.get(address) {
|
||||
log::warn!("Detected duplicate connection for client: {}", address);
|
||||
if let Some(client_tx) = self.active_clients_store.get_remote_client(address) {
|
||||
log::warn!("Detected duplicate connection for client: {address}");
|
||||
self.handle_duplicate_client(address, client_tx.is_active_request_sender)
|
||||
.await?;
|
||||
}
|
||||
@@ -569,7 +585,7 @@ where
|
||||
let remote_identity = Self::extract_remote_identity_from_register_init(&init_data)?;
|
||||
let remote_address = remote_identity.derive_destination_address();
|
||||
|
||||
if self.active_clients_store.get(remote_address).is_some() {
|
||||
if self.active_clients_store.is_active(remote_address) {
|
||||
return Err(InitialAuthenticationError::DuplicateConnection);
|
||||
}
|
||||
|
||||
@@ -675,7 +691,7 @@ where
|
||||
// Channel for handlers to ask other handlers if they are still active.
|
||||
let (is_active_request_sender, is_active_request_receiver) =
|
||||
mpsc::unbounded();
|
||||
self.active_clients_store.insert(
|
||||
self.active_clients_store.insert_remote(
|
||||
client_details.address,
|
||||
mix_sender,
|
||||
is_active_request_sender,
|
||||
|
||||
@@ -78,7 +78,7 @@ impl Listener {
|
||||
active_clients_store.clone(),
|
||||
Arc::clone(&self.coconut_verifier),
|
||||
);
|
||||
let shutdown = shutdown.clone();
|
||||
let shutdown = shutdown.clone().named(format!("ClientConnectionHandler_{remote_addr}"));
|
||||
tokio::spawn(async move { handle.start_handling(shutdown).await });
|
||||
}
|
||||
Err(err) => warn!("failed to get client: {err}"),
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::GatewayError;
|
||||
use crate::node::storage::PersistentStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use nym_pemstore::KeyPairPath;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_types::gateway::{GatewayNetworkRequesterDetails, GatewayNodeDetailsResponse};
|
||||
use std::path::Path;
|
||||
|
||||
fn display_maybe_path<P: AsRef<Path>>(path: Option<P>) -> String {
|
||||
path.as_ref()
|
||||
.map(|p| p.as_ref().display().to_string())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn display_path<P: AsRef<Path>>(path: P) -> String {
|
||||
path.as_ref().display().to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn node_details(config: &Config) -> Result<GatewayNodeDetailsResponse, GatewayError> {
|
||||
let gateway_identity_public_key: identity::PublicKey = load_public_key(
|
||||
&config.storage_paths.keys.public_identity_key_file,
|
||||
"gateway identity",
|
||||
)?;
|
||||
|
||||
let gateway_sphinx_public_key: encryption::PublicKey = load_public_key(
|
||||
&config.storage_paths.keys.public_sphinx_key_file,
|
||||
"gateway sphinx",
|
||||
)?;
|
||||
|
||||
let network_requester =
|
||||
if let Some(nr_cfg_path) = &config.storage_paths.network_requester_config {
|
||||
let cfg = load_network_requester_config(&config.gateway.id, nr_cfg_path)?;
|
||||
|
||||
let nr_identity_public_key: identity::PublicKey = load_public_key(
|
||||
&cfg.storage_paths.common_paths.keys.public_identity_key_file,
|
||||
"network requester identity",
|
||||
)?;
|
||||
|
||||
let nr_encryption_key: encryption::PublicKey = load_public_key(
|
||||
&cfg.storage_paths
|
||||
.common_paths
|
||||
.keys
|
||||
.public_encryption_key_file,
|
||||
"network requester encryption",
|
||||
)?;
|
||||
|
||||
let address = Recipient::new(
|
||||
nr_identity_public_key,
|
||||
nr_encryption_key,
|
||||
gateway_identity_public_key,
|
||||
);
|
||||
|
||||
Some(GatewayNetworkRequesterDetails {
|
||||
enabled: config.network_requester.enabled,
|
||||
identity_key: nr_identity_public_key.to_base58_string(),
|
||||
encryption_key: nr_encryption_key.to_base58_string(),
|
||||
open_proxy: cfg.network_requester.open_proxy,
|
||||
enabled_statistics: cfg.network_requester.enabled_statistics,
|
||||
address: address.to_string(),
|
||||
config_path: display_path(nr_cfg_path),
|
||||
allow_list_path: display_path(&cfg.storage_paths.allowed_list_location),
|
||||
unknown_list_path: display_path(&cfg.storage_paths.unknown_list_location),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(GatewayNodeDetailsResponse {
|
||||
identity_key: gateway_identity_public_key.to_base58_string(),
|
||||
sphinx_key: gateway_sphinx_public_key.to_base58_string(),
|
||||
bind_address: config.gateway.listening_address.to_string(),
|
||||
mix_port: config.gateway.mix_port,
|
||||
clients_port: config.gateway.clients_port,
|
||||
config_path: display_maybe_path(config.save_path.as_ref()),
|
||||
data_store: display_path(&config.storage_paths.clients_storage),
|
||||
network_requester,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn load_network_requester_config<P: AsRef<Path>>(
|
||||
id: &str,
|
||||
path: P,
|
||||
) -> Result<nym_network_requester::Config, GatewayError> {
|
||||
let path = path.as_ref();
|
||||
nym_network_requester::Config::read_from_toml_file(path).map_err(|err| {
|
||||
GatewayError::NetworkRequesterConfigLoadFailure {
|
||||
id: id.to_string(),
|
||||
path: path.to_path_buf(),
|
||||
source: err,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn initialise_main_storage(
|
||||
config: &Config,
|
||||
) -> Result<PersistentStorage, GatewayError> {
|
||||
let path = &config.storage_paths.clients_storage;
|
||||
let retrieval_limit = config.debug.message_retrieval_limit;
|
||||
|
||||
Ok(PersistentStorage::init(path, retrieval_limit).await?)
|
||||
}
|
||||
|
||||
pub(crate) fn load_keypair<T: PemStorableKeyPair>(
|
||||
paths: KeyPairPath,
|
||||
name: impl Into<String>,
|
||||
) -> Result<T, GatewayError> {
|
||||
nym_pemstore::load_keypair(&paths).map_err(|err| GatewayError::KeyPairLoadFailure {
|
||||
keys: name.into(),
|
||||
paths,
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn load_public_key<T, P, S>(path: P, name: S) -> Result<T, GatewayError>
|
||||
where
|
||||
T: PemStorableKey,
|
||||
P: AsRef<Path>,
|
||||
S: Into<String>,
|
||||
{
|
||||
nym_pemstore::load_key(path.as_ref()).map_err(|err| GatewayError::PublicKeyLoadFailure {
|
||||
key: name.into(),
|
||||
path: path.as_ref().to_path_buf(),
|
||||
err,
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads identity keys stored on disk
|
||||
pub(crate) fn load_identity_keys(config: &Config) -> Result<identity::KeyPair, GatewayError> {
|
||||
let identity_paths = KeyPairPath::new(
|
||||
config.storage_paths.keys.private_identity_key(),
|
||||
config.storage_paths.keys.public_identity_key(),
|
||||
);
|
||||
load_keypair(identity_paths, "gateway identity")
|
||||
}
|
||||
|
||||
/// Loads Sphinx keys stored on disk
|
||||
pub(crate) fn load_sphinx_keys(config: &Config) -> Result<encryption::KeyPair, GatewayError> {
|
||||
let sphinx_paths = KeyPairPath::new(
|
||||
config.storage_paths.keys.private_encryption_key(),
|
||||
config.storage_paths.keys.public_encryption_key(),
|
||||
);
|
||||
load_keypair(sphinx_paths, "gateway sphinx")
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use crate::node::client_handling::websocket::message_receiver::MixMessageSender;
|
||||
use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor;
|
||||
use crate::node::storage::error::StorageError;
|
||||
use crate::node::storage::Storage;
|
||||
use futures::channel::mpsc::SendError;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
@@ -17,9 +18,17 @@ use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use thiserror::Error;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::Framed;
|
||||
|
||||
// defines errors that warrant a panic if not thrown in the context of a shutdown
|
||||
#[derive(Debug, Error)]
|
||||
enum CriticalPacketProcessingError {
|
||||
#[error("failed to forward an ack")]
|
||||
AckForwardingFailure { source: SendError },
|
||||
}
|
||||
|
||||
pub(crate) struct ConnectionHandler<St: Storage> {
|
||||
packet_processor: PacketProcessor,
|
||||
|
||||
@@ -70,9 +79,9 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
}
|
||||
|
||||
fn update_clients_store_cache_entry(&mut self, client_address: DestinationAddressBytes) {
|
||||
if let Some(client_senders) = self.active_clients_store.get(client_address) {
|
||||
if let Some(client_senders) = self.active_clients_store.get_sender(client_address) {
|
||||
self.clients_store_cache
|
||||
.insert(client_address, client_senders.mix_message_sender);
|
||||
.insert(client_address, client_senders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +106,14 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
match self.clients_store_cache.get(&client_address) {
|
||||
None => Err(message),
|
||||
Some(sender_channel) => {
|
||||
sender_channel
|
||||
.unbounded_send(vec![message])
|
||||
// right now it's a "simpler" case here as we're only ever sending 1 message
|
||||
// at the time, but the channel itself could accept arbitrary many messages at once
|
||||
.map_err(|try_send_err| try_send_err.into_inner().pop().unwrap())
|
||||
if let Err(unsent) = sender_channel.unbounded_send(vec![message]) {
|
||||
// the unwrap here is fine as the original message got returned;
|
||||
// plus we're only ever sending 1 message at the time (for now)
|
||||
#[allow(clippy::unwrap_used)]
|
||||
return Err(unsent.into_inner().pop().unwrap());
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,27 +123,35 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
client_address: DestinationAddressBytes,
|
||||
message: Vec<u8>,
|
||||
) -> Result<(), StorageError> {
|
||||
debug!(
|
||||
"Storing received message for {} on the disk...",
|
||||
client_address
|
||||
);
|
||||
debug!("Storing received message for {client_address} on the disk...",);
|
||||
|
||||
self.storage.store_message(client_address, message).await
|
||||
}
|
||||
|
||||
fn forward_ack(&self, forward_ack: Option<MixPacket>, client_address: DestinationAddressBytes) {
|
||||
fn forward_ack(
|
||||
&self,
|
||||
forward_ack: Option<MixPacket>,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), CriticalPacketProcessingError> {
|
||||
if let Some(forward_ack) = forward_ack {
|
||||
trace!(
|
||||
"Sending ack from packet for {} to {}",
|
||||
client_address,
|
||||
forward_ack.next_hop()
|
||||
);
|
||||
let next_hop = forward_ack.next_hop();
|
||||
trace!("Sending ack from packet for {client_address} to {next_hop}",);
|
||||
|
||||
self.ack_sender.unbounded_send(forward_ack).unwrap();
|
||||
self.ack_sender
|
||||
.unbounded_send(forward_ack)
|
||||
.map_err(
|
||||
|source| CriticalPacketProcessingError::AckForwardingFailure {
|
||||
source: source.into_send_error(),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_processed_packet(&mut self, processed_final_hop: ProcessedFinalHop) {
|
||||
async fn handle_processed_packet(
|
||||
&mut self,
|
||||
processed_final_hop: ProcessedFinalHop,
|
||||
) -> Result<(), CriticalPacketProcessingError> {
|
||||
let client_address = processed_final_hop.destination;
|
||||
let message = processed_final_hop.message;
|
||||
let forward_ack = processed_final_hop.forward_ack;
|
||||
@@ -144,18 +164,21 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
.await
|
||||
{
|
||||
Err(err) => error!("Failed to store client data - {err}"),
|
||||
Ok(_) => trace!("Stored packet for {}", client_address),
|
||||
Ok(_) => trace!("Stored packet for {client_address}"),
|
||||
},
|
||||
Ok(_) => trace!("Pushed received packet to {}", client_address),
|
||||
Ok(_) => trace!("Pushed received packet to {client_address}"),
|
||||
}
|
||||
|
||||
// if we managed to either push message directly to the [online] client or store it at
|
||||
// its inbox, it means that it must exist at this gateway, hence we can send the
|
||||
// received ack back into the network
|
||||
self.forward_ack(forward_ack, client_address);
|
||||
self.forward_ack(forward_ack, client_address)
|
||||
}
|
||||
|
||||
async fn handle_received_packet(&mut self, framed_sphinx_packet: FramedNymPacket) {
|
||||
async fn handle_received_packet(
|
||||
&mut self,
|
||||
framed_sphinx_packet: FramedNymPacket,
|
||||
) -> Result<(), CriticalPacketProcessingError> {
|
||||
//
|
||||
// TODO: here be replay attack detection - it will require similar key cache to the one in
|
||||
// packet processor for vpn packets,
|
||||
@@ -166,7 +189,7 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
{
|
||||
Err(err) => {
|
||||
debug!("We failed to process received sphinx packet - {err}");
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
Ok(processed_final_hop) => processed_final_hop,
|
||||
};
|
||||
@@ -198,7 +221,11 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
// in theory we could process multiple sphinx packet from the same connection in parallel,
|
||||
// but we already handle multiple concurrent connections so if anything, making
|
||||
// that change would only slow things down
|
||||
self.handle_received_packet(framed_sphinx_packet).await;
|
||||
if let Err(critical_err) = self.handle_received_packet(framed_sphinx_packet).await {
|
||||
if !shutdown.is_shutdown() {
|
||||
panic!("experienced critical failure when processing received packet: {critical_err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
error!(
|
||||
@@ -212,9 +239,13 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"Closing connection from {:?}",
|
||||
framed_conn.into_inner().peer_addr()
|
||||
);
|
||||
match framed_conn.into_inner().peer_addr() {
|
||||
Ok(peer_addr) => {
|
||||
debug!("closing connection from {peer_addr}")
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("closing connection from an unknown peer: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ impl Listener {
|
||||
match connection {
|
||||
Ok((socket, remote_addr)) => {
|
||||
let handler = connection_handler.clone();
|
||||
tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone()));
|
||||
tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone().named(format!("MixnetConnectionHandler_{remote_addr}"))));
|
||||
}
|
||||
Err(err) => warn!("failed to get client: {err}"),
|
||||
}
|
||||
|
||||
+162
-81
@@ -2,19 +2,26 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use self::storage::PersistentStorage;
|
||||
use crate::commands::helpers::{override_network_requester_config, OverrideNetworkRequesterConfig};
|
||||
use crate::config::Config;
|
||||
use crate::error::GatewayError;
|
||||
use crate::node::client_handling::active_clients::ActiveClientsStore;
|
||||
use crate::node::client_handling::embedded_network_requester::{
|
||||
LocalNetworkRequesterHandle, MessageRouter,
|
||||
};
|
||||
use crate::node::client_handling::websocket;
|
||||
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
|
||||
use crate::node::helpers::{initialise_main_storage, load_network_requester_config};
|
||||
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
|
||||
use crate::node::statistics::collector::GatewayStatisticsCollector;
|
||||
use crate::node::storage::Storage;
|
||||
use anyhow::bail;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use log::*;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_mixnet_client::forwarder::{MixForwardingSender, PacketForwarder};
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use nym_network_requester::{LocalGateway, NRServiceProviderBuilder};
|
||||
use nym_statistics_common::collector::StatisticsSender;
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
@@ -22,32 +29,59 @@ use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) mod client_handling;
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod mixnet_handling;
|
||||
pub(crate) mod statistics;
|
||||
pub(crate) mod storage;
|
||||
|
||||
/// Wire up and create Gateway instance
|
||||
pub(crate) async fn create_gateway(config: Config) -> Gateway<PersistentStorage> {
|
||||
let storage = initialise_storage(&config).await;
|
||||
Gateway::new(config, storage).await
|
||||
}
|
||||
|
||||
async fn initialise_storage(config: &Config) -> PersistentStorage {
|
||||
let path = &config.storage_paths.clients_storage;
|
||||
let retrieval_limit = config.debug.message_retrieval_limit;
|
||||
match PersistentStorage::init(path, retrieval_limit).await {
|
||||
Err(err) => panic!("failed to initialise gateway storage: {err}"),
|
||||
Ok(storage) => storage,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Gateway<St> {
|
||||
pub(crate) async fn create_gateway(
|
||||
config: Config,
|
||||
nr_config_override: Option<OverrideNetworkRequesterConfig>,
|
||||
custom_nr_mixnet: Option<PathBuf>,
|
||||
) -> Result<Gateway, GatewayError> {
|
||||
// don't attempt to read config if NR is disabled
|
||||
let network_requester_config = if config.network_requester.enabled {
|
||||
if let Some(path) = &config.storage_paths.network_requester_config {
|
||||
let cfg = load_network_requester_config(&config.gateway.id, path)?;
|
||||
Some(override_network_requester_config(cfg, nr_config_override))
|
||||
} else {
|
||||
// if NR is enabled, the config path must be specified
|
||||
return Err(GatewayError::UnspecifiedNetworkRequesterConfig);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let storage = initialise_main_storage(&config).await?;
|
||||
|
||||
let nr_opts = network_requester_config.map(|config| LocalNetworkRequesterOpts {
|
||||
config,
|
||||
custom_mixnet_path: custom_nr_mixnet,
|
||||
});
|
||||
|
||||
Gateway::new(config, nr_opts, storage)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalNetworkRequesterOpts {
|
||||
config: nym_network_requester::Config,
|
||||
|
||||
custom_mixnet_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub(crate) struct Gateway<St = PersistentStorage> {
|
||||
config: Config,
|
||||
|
||||
network_requester_opts: Option<LocalNetworkRequesterOpts>,
|
||||
|
||||
/// ed25519 keypair used to assert one's identity.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
/// x25519 keypair used for Diffie-Hellman. Currently only used for sphinx key derivation.
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
storage: St,
|
||||
@@ -55,71 +89,37 @@ pub(crate) struct Gateway<St> {
|
||||
|
||||
impl<St> Gateway<St> {
|
||||
/// Construct from the given `Config` instance.
|
||||
pub async fn new(config: Config, storage: St) -> Self {
|
||||
Gateway {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
network_requester_opts: Option<LocalNetworkRequesterOpts>,
|
||||
storage: St,
|
||||
) -> Result<Self, GatewayError> {
|
||||
Ok(Gateway {
|
||||
storage,
|
||||
identity_keypair: Arc::new(Self::load_identity_keys(&config)),
|
||||
sphinx_keypair: Arc::new(Self::load_sphinx_keys(&config)),
|
||||
identity_keypair: Arc::new(helpers::load_identity_keys(&config)?),
|
||||
sphinx_keypair: Arc::new(helpers::load_sphinx_keys(&config)?),
|
||||
config,
|
||||
}
|
||||
network_requester_opts,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub async fn new_from_keys_and_storage(
|
||||
config: Config,
|
||||
network_requester_opts: Option<LocalNetworkRequesterOpts>,
|
||||
identity_keypair: identity::KeyPair,
|
||||
sphinx_keypair: encryption::KeyPair,
|
||||
storage: St,
|
||||
) -> Self {
|
||||
Gateway {
|
||||
config,
|
||||
network_requester_opts,
|
||||
identity_keypair: Arc::new(identity_keypair),
|
||||
sphinx_keypair: Arc::new(sphinx_keypair),
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads identity keys stored on disk
|
||||
pub(crate) fn load_identity_keys(config: &Config) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
config.storage_paths.keys.private_identity_key(),
|
||||
config.storage_paths.keys.public_identity_key(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
/// Loads Sphinx keys stored on disk
|
||||
fn load_sphinx_keys(config: &Config) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
config.storage_paths.keys.private_encryption_key(),
|
||||
config.storage_paths.keys.public_encryption_key(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
sphinx_keypair
|
||||
}
|
||||
|
||||
pub(crate) fn print_node_details(&self, output: OutputFormat) {
|
||||
let node_details = nym_types::gateway::GatewayNodeDetailsResponse {
|
||||
identity_key: self.identity_keypair.public_key().to_base58_string(),
|
||||
sphinx_key: self.sphinx_keypair.public_key().to_base58_string(),
|
||||
bind_address: self.config.gateway.listening_address.to_string(),
|
||||
version: self.config.gateway.version.clone(),
|
||||
mix_port: self.config.gateway.mix_port,
|
||||
clients_port: self.config.gateway.clients_port,
|
||||
data_store: self
|
||||
.config
|
||||
.storage_paths
|
||||
.clients_storage
|
||||
.display()
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
println!("{}", output.format(&node_details));
|
||||
}
|
||||
|
||||
fn start_mix_socket_listener(
|
||||
&self,
|
||||
ack_sender: MixForwardingSender,
|
||||
@@ -194,46 +194,106 @@ impl<St> Gateway<St> {
|
||||
packet_sender
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(
|
||||
// TODO: rethink the logic in this function...
|
||||
async fn start_network_requester(
|
||||
&self,
|
||||
shutdown: TaskManager,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
forwarding_channel: MixForwardingSender,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<LocalNetworkRequesterHandle, GatewayError> {
|
||||
info!("Starting network requester...");
|
||||
|
||||
// if network requester is enabled, configuration file must be provided!
|
||||
let Some(nr_opts) = &self.network_requester_opts else {
|
||||
return Err(GatewayError::UnspecifiedNetworkRequesterConfig);
|
||||
};
|
||||
|
||||
// this gateway, whenever it has anything to send to its local NR will use fake_client_tx
|
||||
let (nr_mix_sender, nr_mix_receiver) = mpsc::unbounded();
|
||||
let router_shutdown = shutdown.fork("message_router");
|
||||
|
||||
let (router_tx, mut router_rx) = oneshot::channel();
|
||||
|
||||
let transceiver = LocalGateway::new(
|
||||
*self.identity_keypair.public_key(),
|
||||
forwarding_channel,
|
||||
router_tx,
|
||||
);
|
||||
|
||||
// TODO: well, wire it up internally to gateway traffic, shutdowns, etc.
|
||||
let (on_start_tx, on_start_rx) = oneshot::channel();
|
||||
let mut nr_builder = NRServiceProviderBuilder::new(nr_opts.config.clone())
|
||||
.with_shutdown(shutdown)
|
||||
.with_custom_gateway_transceiver(Box::new(transceiver))
|
||||
.with_wait_for_gateway(true)
|
||||
.with_on_start(on_start_tx);
|
||||
|
||||
if let Some(custom_mixnet) = &nr_opts.custom_mixnet_path {
|
||||
nr_builder = nr_builder.with_stored_topology(custom_mixnet)?
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = nr_builder.run_service_provider().await {
|
||||
// no need to panic as we have passed a task client to the NR so we're most likely
|
||||
// already in the process of shutting down
|
||||
error!("network requester has failed: {err}")
|
||||
}
|
||||
});
|
||||
|
||||
let start_data = on_start_rx
|
||||
.await
|
||||
.map_err(|_| GatewayError::NetworkRequesterStartupFailure)?;
|
||||
|
||||
// this should be instantaneous since the data is sent on this channel before the on start is called;
|
||||
// the failure should be impossible
|
||||
let Ok(Some(packet_router)) = router_rx.try_recv() else {
|
||||
return Err(GatewayError::NetworkRequesterStartupFailure);
|
||||
};
|
||||
|
||||
MessageRouter::new(nr_mix_receiver, packet_router).start_with_shutdown(router_shutdown);
|
||||
info!(
|
||||
"the local network requester is running on {}",
|
||||
start_data.address
|
||||
);
|
||||
|
||||
Ok(LocalNetworkRequesterHandle::new(start_data, nr_mix_sender))
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(shutdown: TaskManager) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let res = shutdown.catch_interrupt().await;
|
||||
log::info!("Stopping nym gateway");
|
||||
res
|
||||
}
|
||||
|
||||
fn random_api_client(&self) -> nym_validator_client::NymApiClient {
|
||||
fn random_api_client(&self) -> Result<nym_validator_client::NymApiClient, GatewayError> {
|
||||
let endpoints = self.config.get_nym_api_endpoints();
|
||||
let nym_api = endpoints
|
||||
.choose(&mut thread_rng())
|
||||
.expect("The list of validator apis is empty");
|
||||
.ok_or(GatewayError::NoNymApisAvailable)?;
|
||||
|
||||
nym_validator_client::NymApiClient::new(nym_api.clone())
|
||||
Ok(nym_validator_client::NymApiClient::new(nym_api.clone()))
|
||||
}
|
||||
|
||||
fn random_nyxd_client(&self) -> DirectSigningHttpRpcNyxdClient {
|
||||
fn random_nyxd_client(&self) -> Result<DirectSigningHttpRpcNyxdClient, GatewayError> {
|
||||
let endpoints = self.config.get_nyxd_urls();
|
||||
let validator_nyxd = endpoints
|
||||
.choose(&mut thread_rng())
|
||||
.expect("The list of validators is empty");
|
||||
.ok_or(GatewayError::NoNyxdAvailable)?;
|
||||
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let client_config = nyxd::Config::try_from_nym_network_details(&network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?;
|
||||
|
||||
DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
|
||||
client_config,
|
||||
validator_nyxd.as_ref(),
|
||||
self.config.get_cosmos_mnemonic(),
|
||||
)
|
||||
.expect("Could not connect with mnemonic")
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn check_if_bonded(&self) -> Result<bool, GatewayError> {
|
||||
// TODO: if anything, this should be getting data directly from the contract
|
||||
// as opposed to the validator API
|
||||
let validator_client = self.random_api_client();
|
||||
let validator_client = self.random_api_client()?;
|
||||
let existing_nodes = match validator_client.get_cached_gateways().await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
@@ -247,7 +307,7 @@ impl<St> Gateway<St> {
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), Box<dyn Error + Send + Sync>>
|
||||
pub async fn run(self) -> anyhow::Result<()>
|
||||
where
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
@@ -260,17 +320,18 @@ impl<St> Gateway<St> {
|
||||
let shutdown = TaskManager::new(10);
|
||||
|
||||
let coconut_verifier = {
|
||||
let nyxd_client = self.random_nyxd_client();
|
||||
let nyxd_client = self.random_nyxd_client()?;
|
||||
CoconutVerifier::new(nyxd_client)
|
||||
};
|
||||
|
||||
let mix_forwarding_channel = self.start_packet_forwarder(shutdown.subscribe());
|
||||
let mix_forwarding_channel =
|
||||
self.start_packet_forwarder(shutdown.subscribe().named("PacketForwarder"));
|
||||
|
||||
let active_clients_store = ActiveClientsStore::new();
|
||||
self.start_mix_socket_listener(
|
||||
mix_forwarding_channel.clone(),
|
||||
active_clients_store.clone(),
|
||||
shutdown.subscribe(),
|
||||
shutdown.subscribe().named("mixnet_handling::Listener"),
|
||||
);
|
||||
|
||||
if self.config.gateway.enabled_statistics {
|
||||
@@ -286,20 +347,40 @@ impl<St> Gateway<St> {
|
||||
});
|
||||
}
|
||||
|
||||
if self.config.network_requester.enabled {
|
||||
let embedded_nr = self
|
||||
.start_network_requester(
|
||||
mix_forwarding_channel.clone(),
|
||||
shutdown.subscribe().named("NetworkRequester"),
|
||||
)
|
||||
.await?;
|
||||
// insert information about embedded NR to the active clients store
|
||||
active_clients_store.insert_embedded(embedded_nr)
|
||||
} else {
|
||||
info!("embedded network requester is disabled");
|
||||
}
|
||||
|
||||
self.start_client_websocket_listener(
|
||||
mix_forwarding_channel,
|
||||
active_clients_store,
|
||||
shutdown.subscribe(),
|
||||
shutdown.subscribe().named("websocket::Listener"),
|
||||
Arc::new(coconut_verifier),
|
||||
);
|
||||
|
||||
// Once this is a bit more mature, make this a commandline flag instead of a compile time
|
||||
// flag
|
||||
#[cfg(feature = "wireguard")]
|
||||
nym_wireguard::start_wg_listener(shutdown.subscribe()).await?;
|
||||
if let Err(err) = nym_wireguard::start_wg_listener(shutdown.subscribe()).await {
|
||||
// that's a nasty workaround, but anyhow errors are generally nicer, especially on exit
|
||||
bail!("{err}")
|
||||
}
|
||||
|
||||
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
|
||||
|
||||
self.wait_for_interrupt(shutdown).await
|
||||
if let Err(err) = Self::wait_for_interrupt(shutdown).await {
|
||||
// that's a nasty workaround, but anyhow errors are generally nicer, especially on exit
|
||||
bail!("{err}")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum StorageError {
|
||||
#[error("Database experienced an internal error - {0}")]
|
||||
#[error("Database experienced an internal error: {0}")]
|
||||
InternalDatabaseError(#[from] sqlx::Error),
|
||||
|
||||
#[error("Failed to perform database migration - {0}")]
|
||||
#[error("Failed to perform database migration: {0}")]
|
||||
MigrationError(#[from] sqlx::migrate::MigrateError),
|
||||
}
|
||||
|
||||
@@ -18,7 +18,12 @@ impl InboxManager {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `connection_pool`: database connection pool to use.
|
||||
pub(crate) fn new(connection_pool: sqlx::SqlitePool, retrieval_limit: i64) -> Self {
|
||||
pub(crate) fn new(connection_pool: sqlx::SqlitePool, mut retrieval_limit: i64) -> Self {
|
||||
// TODO: make this into a hard error instead
|
||||
if retrieval_limit == 0 {
|
||||
retrieval_limit = 100;
|
||||
}
|
||||
|
||||
InboxManager {
|
||||
connection_pool,
|
||||
retrieval_limit,
|
||||
@@ -99,7 +104,8 @@ impl InboxManager {
|
||||
|
||||
if res.len() > self.retrieval_limit as usize {
|
||||
res.truncate(self.retrieval_limit as usize);
|
||||
// assuming retrieval_limit > 0, unwrap will not fail
|
||||
// given retrieval_limit > 0, unwrap will not fail
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let start_after = res.last().unwrap().id;
|
||||
Ok((res, Some(start_after)))
|
||||
//
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::{override_config, try_load_current_config, OverrideConfig};
|
||||
use crate::commands::helpers::{try_load_current_config, OverrideConfig};
|
||||
use crate::config::Config;
|
||||
use crate::error::GatewayError;
|
||||
|
||||
@@ -10,6 +10,5 @@ pub(crate) fn build_config<O: Into<OverrideConfig>>(
|
||||
override_args: O,
|
||||
) -> Result<Config, GatewayError> {
|
||||
let config = try_load_current_config(&id)?;
|
||||
|
||||
override_config(config, override_args.into())
|
||||
override_args.into().do_override(config)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,11 @@ use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_config::defaults::REMAINING_BANDWIDTH_THRESHOLD;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH};
|
||||
use nym_gateway_client::client::GatewayConfig;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_gateway_client::{AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver};
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver, PacketRouter,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_task::TaskClient;
|
||||
use pin_project::pin_project;
|
||||
@@ -55,6 +58,14 @@ impl GatewayPackets {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gateway_config(&self) -> GatewayConfig {
|
||||
GatewayConfig {
|
||||
gateway_identity: self.pub_key,
|
||||
gateway_owner: None,
|
||||
gateway_listener: self.clients_address.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty(clients_address: String, pub_key: identity::PublicKey) -> Self {
|
||||
GatewayPackets {
|
||||
clients_address,
|
||||
@@ -169,13 +180,16 @@ impl PacketSender {
|
||||
}
|
||||
|
||||
fn new_gateway_client_handle(
|
||||
address: String,
|
||||
identity: identity::PublicKey,
|
||||
config: GatewayConfig,
|
||||
fresh_gateway_client_data: &FreshGatewayClientData,
|
||||
) -> (
|
||||
GatewayClientHandle,
|
||||
(MixnetMessageReceiver, AcknowledgementReceiver),
|
||||
) {
|
||||
// I think the proper one should be passed around instead...
|
||||
let task_client =
|
||||
nym_task::TaskClient::dummy().named(format!("gateway-{}", config.gateway_identity));
|
||||
|
||||
// TODO: future optimization: if we're remaking client for a gateway to which we used to be connected in the past,
|
||||
// use old shared keys
|
||||
let (message_sender, message_receiver) = mpsc::unbounded();
|
||||
@@ -184,20 +198,22 @@ impl PacketSender {
|
||||
// so that the gateway client would not crash
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
address,
|
||||
Arc::clone(&fresh_gateway_client_data.local_identity),
|
||||
identity,
|
||||
None,
|
||||
message_sender,
|
||||
let gateway_packet_router = PacketRouter::new(
|
||||
ack_sender,
|
||||
fresh_gateway_client_data.gateway_response_timeout,
|
||||
Some(fresh_gateway_client_data.bandwidth_controller.clone()),
|
||||
nym_task::TaskClient::dummy(),
|
||||
message_sender,
|
||||
task_client.fork("packet-router"),
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.set_disabled_credentials_mode(fresh_gateway_client_data.disabled_credentials_mode);
|
||||
let gateway_client = GatewayClient::new(
|
||||
config,
|
||||
Arc::clone(&fresh_gateway_client_data.local_identity),
|
||||
None,
|
||||
gateway_packet_router,
|
||||
Some(fresh_gateway_client_data.bandwidth_controller.clone()),
|
||||
task_client,
|
||||
)
|
||||
.with_disabled_credentials_mode(fresh_gateway_client_data.disabled_credentials_mode)
|
||||
.with_response_timeout(fresh_gateway_client_data.gateway_response_timeout);
|
||||
|
||||
(
|
||||
GatewayClientHandle::new(gateway_client),
|
||||
@@ -265,16 +281,16 @@ impl PacketSender {
|
||||
}
|
||||
|
||||
async fn create_new_gateway_client_handle_and_authenticate(
|
||||
address: String,
|
||||
identity: identity::PublicKey,
|
||||
config: GatewayConfig,
|
||||
fresh_gateway_client_data: &FreshGatewayClientData,
|
||||
gateway_connection_timeout: Duration,
|
||||
) -> Option<(
|
||||
GatewayClientHandle,
|
||||
(MixnetMessageReceiver, AcknowledgementReceiver),
|
||||
)> {
|
||||
let gateway_identity = config.gateway_identity;
|
||||
let (new_client, (message_receiver, ack_receiver)) =
|
||||
Self::new_gateway_client_handle(address, identity, fresh_gateway_client_data);
|
||||
Self::new_gateway_client_handle(config, fresh_gateway_client_data);
|
||||
|
||||
// Put this in timeout in case the gateway has incorrectly set their ulimit and our connection
|
||||
// gets stuck in their TCP queue and just hangs on our end but does not terminate
|
||||
@@ -295,19 +311,12 @@ impl PacketSender {
|
||||
Some((new_client, (message_receiver, ack_receiver)))
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
warn!(
|
||||
"failed to authenticate with new gateway ({}) - {}",
|
||||
identity.to_base58_string(),
|
||||
err
|
||||
);
|
||||
warn!("failed to authenticate with new gateway ({gateway_identity}): {err}",);
|
||||
// we failed to create a client, can't do much here
|
||||
None
|
||||
}
|
||||
Err(_) => {
|
||||
warn!(
|
||||
"timed out while trying to authenticate with new gateway ({})",
|
||||
identity.to_base58_string()
|
||||
);
|
||||
warn!("timed out while trying to authenticate with new gateway {gateway_identity}",);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -349,8 +358,7 @@ impl PacketSender {
|
||||
} else {
|
||||
let (client, gateway_channels) =
|
||||
Self::create_new_gateway_client_handle_and_authenticate(
|
||||
packets.clients_address,
|
||||
packets.pub_key,
|
||||
packets.gateway_config(),
|
||||
&fresh_gateway_client_data,
|
||||
gateway_connection_timeout,
|
||||
)
|
||||
|
||||
@@ -110,7 +110,7 @@ impl<'a> From<&'a Config> for NymNetworkDetails {
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// I'm not convinced by the lint (on Arc<WasmStorage>).
|
||||
// Sure. wasm is currently single threaded and does not require `Send` or `Sync`
|
||||
// but this data is moved across futures, so imo we should leave the Arc as it is,
|
||||
// because it might cause us headache in the future
|
||||
#![allow(clippy::arc_with_non_send_sync)]
|
||||
|
||||
use crate::ExtensionStorageError;
|
||||
use js_sys::Promise;
|
||||
use std::sync::Arc;
|
||||
|
||||
Generated
+3
-2
@@ -1513,9 +1513,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.0"
|
||||
version = "5.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.14.0",
|
||||
@@ -4384,6 +4384,7 @@ dependencies = [
|
||||
name = "nym-socks5-client-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs 4.0.0",
|
||||
"futures",
|
||||
"log",
|
||||
|
||||
@@ -9,7 +9,8 @@ use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewa
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::types::{GatewaySelectionSpecification, GatewaySetup};
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
|
||||
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
|
||||
@@ -70,7 +71,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
@@ -203,7 +204,15 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
}
|
||||
|
||||
let gateway_setup = if register_gateway {
|
||||
GatewaySetup::new_fresh(Some(chosen_gateway_id), None)
|
||||
let selection_spec = GatewaySelectionSpecification::new(Some(chosen_gateway_id), None);
|
||||
let mut rng = rand_07::thread_rng();
|
||||
let available_gateways =
|
||||
current_gateways(&mut rng, &config.core.base.client.nym_api_urls).await?;
|
||||
GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
overwrite_data: register_gateway,
|
||||
}
|
||||
} else {
|
||||
GatewaySetup::MustLoad
|
||||
};
|
||||
@@ -212,21 +221,18 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
|
||||
let details_store =
|
||||
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
|
||||
let init_details = nym_client_core::init::setup_gateway(
|
||||
gateway_setup,
|
||||
&key_store,
|
||||
&details_store,
|
||||
register_gateway,
|
||||
Some(&config.core.base.client.nym_api_urls),
|
||||
)
|
||||
.await?
|
||||
.details;
|
||||
let init_details =
|
||||
nym_client_core::init::setup_gateway(gateway_setup, &key_store, &details_store).await?;
|
||||
let gateway_endpoint = init_details
|
||||
.gateway_details
|
||||
.try_get_configured_endpoint()
|
||||
.unwrap();
|
||||
|
||||
config.save_to_default_location().tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
print_saved_config(&config, &init_details.gateway_details);
|
||||
print_saved_config(&config, gateway_endpoint);
|
||||
|
||||
let address = init_details.client_address()?;
|
||||
log::info!("The address of this client is: {address}");
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct NymConnectPaths {
|
||||
impl NymConnectPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
NymConnectPaths {
|
||||
common_paths: CommonClientPaths::new_default(base_data_directory),
|
||||
common_paths: CommonClientPaths::new_base(base_data_directory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ fn persist_gateway_details(config: &Config, details: GatewayEndpointConfig) -> R
|
||||
},
|
||||
}
|
||||
})?;
|
||||
let persisted_details = PersistedGatewayDetails::new(details, &shared_keys);
|
||||
let persisted_details = PersistedGatewayDetails::new(details.into(), Some(&shared_keys))?;
|
||||
details_store
|
||||
.store_to_disk(&persisted_details)
|
||||
.map_err(|source| BackendError::ClientCoreError {
|
||||
|
||||
@@ -257,7 +257,13 @@ impl State {
|
||||
let (control_tx, msg_rx, exit_status_rx, used_gateway) =
|
||||
tasks::start_nym_socks5_client(&id, &privacy_level).await?;
|
||||
self.socks5_client_sender = Some(control_tx);
|
||||
self.gateway = Some(used_gateway.gateway_id);
|
||||
self.gateway = Some(
|
||||
used_gateway
|
||||
.try_get_configured_endpoint()
|
||||
.unwrap()
|
||||
.gateway_id
|
||||
.clone(),
|
||||
);
|
||||
Ok((msg_rx, exit_status_rx))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use nym_client_core::init::types::GatewayDetails;
|
||||
use nym_client_core::{
|
||||
client::base_client::storage::{
|
||||
gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent,
|
||||
},
|
||||
config::{GatewayEndpointConfig, GroupBy, TopologyStructure},
|
||||
config::{GroupBy, TopologyStructure},
|
||||
error::ClientCoreStatusMessage,
|
||||
};
|
||||
use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender};
|
||||
@@ -73,7 +74,7 @@ pub async fn start_nym_socks5_client(
|
||||
Socks5ControlMessageSender,
|
||||
nym_task::StatusReceiver,
|
||||
ExitStatusReceiver,
|
||||
GatewayEndpointConfig,
|
||||
GatewayDetails,
|
||||
)> {
|
||||
log::info!("Loading config from file: {id}");
|
||||
let mut config = Config::read_from_default_path(id)
|
||||
|
||||
@@ -55,7 +55,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template() -> &'static str {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user