Compare commits

..

2 Commits

Author SHA1 Message Date
Dave Hrycyszyn c3d908642a Merge branch 'release/v1.12.1' of github.com:nymtech/nym into release/v0.12.1 2021-12-23 13:15:02 +00:00
Dave Hrycyszyn f84bac18a1 Changelog for 0.12.1 2021-12-23 13:14:51 +00:00
41 changed files with 3359 additions and 4233 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
unreleased=true
future-release=v0.12.0
future-release=v0.12.1
since-tag=v0.11.0
+23
View File
@@ -1,5 +1,27 @@
# Changelog
## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.0...v0.12.1)
**Implemented enhancements:**
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
**Fixed bugs:**
- \[Issue\] NYM wallet doesn't work after login [\#995](https://github.com/nymtech/nym/issues/995)
- \[Issue\] [\#993](https://github.com/nymtech/nym/issues/993)
- NYM wallet setup trouble\[Issue\] [\#958](https://github.com/nymtech/nym/issues/958)
**Merged pull requests:**
- Update wallet version [\#998](https://github.com/nymtech/nym/pull/998) ([tommyv1987](https://github.com/tommyv1987))
- Fix wallet build instructions [\#997](https://github.com/nymtech/nym/pull/997) ([tommyv1987](https://github.com/tommyv1987))
- Make the separation between testnet-mode and erc20 bandwidth mode clearer [\#994](https://github.com/nymtech/nym/pull/994) ([neacsu](https://github.com/neacsu))
- Bump @openzeppelin/contracts from 3.4.0 to 4.4.1 in /contracts/basic-bandwidth-generation [\#983](https://github.com/nymtech/nym/pull/983) ([dependabot[bot]](https://github.com/apps/dependabot))
- Differentiate staking and ownership [\#961](https://github.com/nymtech/nym/pull/961) ([durch](https://github.com/durch))
## [v0.12.0](https://github.com/nymtech/nym/tree/v0.12.0) (2021-12-21)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.11.0...v0.12.0)
@@ -58,6 +80,7 @@
**Merged pull requests:**
- Update wallet to align with versioning on nodes and gateways [\#991](https://github.com/nymtech/nym/pull/991) ([tommyv1987](https://github.com/tommyv1987))
- Fix success view messages. [\#990](https://github.com/nymtech/nym/pull/990) ([tommyv1987](https://github.com/tommyv1987))
- Feature/enable signature check [\#989](https://github.com/nymtech/nym/pull/989) ([neacsu](https://github.com/neacsu))
- Update mixnet contract address [\#988](https://github.com/nymtech/nym/pull/988) ([neacsu](https://github.com/neacsu))
@@ -13,6 +13,7 @@ use nymsphinx::utils::sample_poisson_duration;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::pin::Pin;
use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
use tokio::time;
@@ -164,8 +165,8 @@ impl LoopCoverTrafficStream<OsRng> {
}
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
self.run().await;
})
}
@@ -6,6 +6,7 @@ use futures::StreamExt;
use gateway_client::GatewayClient;
use log::*;
use nymsphinx::forwarding::packet::MixPacket;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
@@ -71,8 +72,8 @@ impl MixTrafficController {
}
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
self.run().await;
})
}
@@ -22,6 +22,7 @@ use nymsphinx::addressing::clients::Recipient;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
mod acknowledgement_control;
@@ -169,8 +170,10 @@ impl RealMessagesController<OsRng> {
self.ack_control = Some(ack_control_fut.await.unwrap());
}
pub fn start(mut self) -> JoinHandle<Self> {
tokio::spawn(async move {
// &Handle is only passed for consistency sake with other client modules, but I think
// when we get to refactoring, we should apply gateway approach and make it implicit
pub fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
handle.spawn(async move {
self.run().await;
self
})
@@ -15,6 +15,7 @@ use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorith
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use std::collections::HashSet;
use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
@@ -290,8 +291,8 @@ impl RequestReceiver {
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
while let Some(request) = self.query_receiver.next().await {
match request {
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
@@ -321,8 +322,8 @@ impl FragmentedMessageReceiver {
mixnet_packet_receiver,
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
self.received_buffer.handle_new_received(new_messages).await;
}
@@ -354,9 +355,9 @@ impl ReceivedMessagesBufferController {
}
}
pub fn start(self) {
pub fn start(self, handle: &Handle) {
// TODO: should we do anything with JoinHandle(s) returned by start methods?
self.fragmented_message_receiver.start();
self.request_receiver.start();
self.fragmented_message_receiver.start(handle);
self.request_receiver.start(handle);
}
}
@@ -10,6 +10,7 @@ use std::ops::Deref;
use std::sync::Arc;
use std::time;
use std::time::Duration;
use tokio::runtime::Handle;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology};
@@ -303,8 +304,8 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
loop {
tokio::time::sleep(self.refresh_rate).await;
self.refresh().await;
+67 -49
View File
@@ -32,6 +32,7 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
use crate::client::config::{Config, SocketType};
use crate::websocket;
@@ -43,6 +44,11 @@ pub struct NymClient {
/// key filepaths, etc.
config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
@@ -62,6 +68,7 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
runtime: Runtime::new().unwrap(),
config,
key_manager,
input_tx: None,
@@ -87,6 +94,9 @@ impl NymClient {
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
@@ -99,7 +109,7 @@ impl NymClient {
self.as_mix_recipient(),
topology_accessor,
)
.start();
.start(self.runtime.handle());
}
fn start_real_traffic_controller(
@@ -121,6 +131,10 @@ impl NymClient {
);
info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new(
controller_config,
@@ -130,7 +144,7 @@ impl NymClient {
topology_accessor,
reply_key_storage,
)
.start();
.start(self.runtime.handle());
}
// buffer controlling all messages fetched from provider
@@ -148,10 +162,10 @@ impl NymClient {
mixnet_receiver,
reply_key_storage,
)
.start()
.start(self.runtime.handle())
}
async fn start_gateway_client(
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
@@ -168,44 +182,47 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
})
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -216,10 +233,13 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
self.runtime.block_on(topology_refresher.refresh());
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
@@ -227,7 +247,7 @@ impl NymClient {
}
info!("Starting topology refresher...");
topology_refresher.start();
topology_refresher.start(self.runtime.handle());
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -240,7 +260,7 @@ impl NymClient {
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start();
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
}
fn start_websocket_listener(
@@ -253,7 +273,8 @@ impl NymClient {
let websocket_handler =
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
websocket::Listener::new(self.config.get_listening_port())
.start(self.runtime.handle(), websocket_handler);
}
/// EXPERIMENTAL DIRECT RUST API
@@ -300,9 +321,9 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
pub fn run_forever(&mut self) {
self.start();
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
@@ -314,7 +335,7 @@ impl NymClient {
);
}
pub async fn start(&mut self) {
pub fn start(&mut self) {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -346,17 +367,14 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_topology_refresher(shared_topology_accessor.clone());
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
+19 -15
View File
@@ -32,7 +32,6 @@ use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
@@ -73,7 +72,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
@@ -219,7 +217,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
@@ -237,7 +235,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, &matches);
config = override_config(config, matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
@@ -250,20 +248,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
let chosen_gateway_id = matches.value_of("gateway");
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gateway_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
let registration_fut = async {
let gate_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_details.clients_address());
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+3 -5
View File
@@ -5,7 +5,6 @@ use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
@@ -43,7 +42,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
@@ -82,7 +80,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) {
@@ -93,12 +91,12 @@ pub async fn execute(matches: ArgMatches<'static>) {
}
};
config = override_config(config, &matches);
config = override_config(config, matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever();
}
+5 -6
View File
@@ -7,8 +7,7 @@ pub mod client;
pub mod commands;
pub mod websocket;
#[tokio::main]
async fn main() {
fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
@@ -23,13 +22,13 @@ async fn main() {
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches).await;
execute(arg_matches);
}
async fn execute(matches: ArgMatches<'static>) {
fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
+3 -2
View File
@@ -5,6 +5,7 @@ use super::handler::Handler;
use log::*;
use std::{net::SocketAddr, process, sync::Arc};
use tokio::io::AsyncWriteExt;
use tokio::runtime;
use tokio::{sync::Notify, task::JoinHandle};
enum State {
@@ -86,9 +87,9 @@ impl Listener {
}
}
pub(crate) fn start(mut self, handler: Handler) -> JoinHandle<()> {
pub(crate) fn start(mut self, rt_handle: &runtime::Handle, handler: Handler) -> JoinHandle<()> {
info!("Running websocket on {:?}", self.address.to_string());
tokio::spawn(async move { self.run(handler).await })
rt_handle.spawn(async move { self.run(handler).await })
}
}
+67 -49
View File
@@ -28,6 +28,7 @@ use gateway_client::{
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
use crate::client::config::Config;
use crate::socks::{
@@ -42,6 +43,11 @@ pub struct NymClient {
/// key filepaths, etc.
config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
}
@@ -52,6 +58,7 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
runtime: Runtime::new().unwrap(),
config,
key_manager,
}
@@ -75,6 +82,9 @@ impl NymClient {
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
@@ -87,7 +97,7 @@ impl NymClient {
self.as_mix_recipient(),
topology_accessor,
)
.start();
.start(self.runtime.handle());
}
fn start_real_traffic_controller(
@@ -109,6 +119,10 @@ impl NymClient {
);
info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new(
controller_config,
@@ -118,7 +132,7 @@ impl NymClient {
topology_accessor,
reply_key_storage,
)
.start();
.start(self.runtime.handle());
}
// buffer controlling all messages fetched from provider
@@ -136,10 +150,10 @@ impl NymClient {
mixnet_receiver,
reply_key_storage,
)
.start()
.start(self.runtime.handle())
}
async fn start_gateway_client(
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
@@ -156,44 +170,47 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
})
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -204,10 +221,13 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
self.runtime.block_on(topology_refresher.refresh());
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
@@ -215,7 +235,7 @@ impl NymClient {
}
info!("Starting topology refresher...");
topology_refresher.start();
topology_refresher.start(self.runtime.handle());
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -228,7 +248,7 @@ impl NymClient {
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start();
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
}
fn start_socks5_listener(
@@ -247,13 +267,14 @@ impl NymClient {
self.config.get_provider_mix_address(),
self.as_mix_recipient(),
);
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
self.runtime
.spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
pub fn run_forever(&mut self) {
self.start();
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
@@ -265,7 +286,7 @@ impl NymClient {
);
}
pub async fn start(&mut self) {
pub fn start(&mut self) {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -297,17 +318,14 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_topology_refresher(shared_topology_accessor.clone());
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
+19 -15
View File
@@ -30,7 +30,6 @@ use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
@@ -73,7 +72,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
@@ -219,7 +217,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
@@ -238,7 +236,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, &matches);
config = override_config(config, matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
@@ -251,20 +249,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
let chosen_gateway_id = matches.value_of("gateway");
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gateway_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
let registration_fut = async {
let gate_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_details.clients_address());
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+3 -5
View File
@@ -5,7 +5,6 @@ use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
@@ -49,7 +48,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
@@ -88,7 +86,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) {
@@ -99,12 +97,12 @@ pub async fn execute(matches: ArgMatches<'static>) {
}
};
config = override_config(config, &matches);
config = override_config(config, matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever();
}
+5 -6
View File
@@ -7,8 +7,7 @@ pub mod client;
mod commands;
pub mod socks;
#[tokio::main]
async fn main() {
fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
@@ -23,13 +22,13 @@ async fn main() {
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches).await;
execute(arg_matches);
}
async fn execute(matches: ArgMatches<'static>) {
fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
-6
View File
@@ -1,6 +0,0 @@
NYMD_URL=https://sandbox-validator.nymtech.net
VALIDATOR_API=https://sandbox-validator.nymtech.net/api
MIXNET_CONTRACT=nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j
VESTING_CONTRACT=nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s
CURRENCY_PREFIX=nymt
CHAIN_ID=nym-sandbox
+12 -6
View File
@@ -3,20 +3,26 @@ Nym Validator Client
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
Running examples
-----------------
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
Running tests
-------------
The tests will be separated into three categories: unit, integration and mock.
Currently the command to run all tests:
```
npm test
```
The tests require `.env.example` being renamed to `.env`. The variables and their values for these tests are currently pointing to the `nym-sandbox` environment.
You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
`Tests are still in development` - the test libary is `jest` and the test script will execute currently with: `--coverage --verbosity false`
```
cargo watch -s "cd clients/validator && npm test"
```
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
Generating Documentation
------------------------
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFiles: ["dotenv/config"]
};
+14 -7
View File
@@ -6,7 +6,9 @@
"main": "./dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "jest --coverage --verbose false",
"build": "tsc",
"test": "ts-mocha tests/**/*.test.ts",
"coverage": "nyc npm test",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"docs": "typedoc --out docs src/index.ts"
@@ -19,21 +21,27 @@
],
"license": "Apache-2.0",
"devDependencies": {
"@types/jest": "27.4.0",
"@types/chai": "^4.2.15",
"@types/expect": "^24.3.0",
"@types/mocha": "^8.2.1",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"chai": "^4.2.0",
"eslint": "^7.18.0",
"eslint-config-airbnb": "^19.0.2",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.4.5",
"mocha": "^8.2.1",
"moq.ts": "^7.2.0",
"nyc": "^15.1.0",
"prettier": "^2.5.1",
"ts-jest": "^27.1.2",
"ts-mocha": "^8.0.0",
"typedoc": "^0.20.27",
"typescript": "^4.5.4"
"typescript": "^4.1.3"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.27.0-rc2",
@@ -43,7 +51,6 @@
"@cosmjs/stargate": "^0.27.0-rc2",
"@cosmjs/tendermint-rpc": "^0.27.0-rc2",
"axios": "^0.21.1",
"cosmjs-types": "^0.4.0",
"dotenv": "^10.0.0"
"cosmjs-types": "^0.4.0"
}
}
+1 -6
View File
@@ -2,12 +2,7 @@ import axios from 'axios';
import { GasPrice } from '@cosmjs/stargate';
export function nymGasPrice(prefix: string): GasPrice {
if (typeof prefix === 'string') {
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
}
else {
throw new Error(`${prefix} is not of type string`);
}
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
}
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
@@ -1,92 +0,0 @@
import validatorClient from "../../src/index";
import { config } from '../test-utils/config';
let client: validatorClient;
let mnemonic: string;
beforeEach(async () => {
mnemonic = validatorClient.randomMnemonic();
client = await validatorClient.connect(
mnemonic,
config.NYMD_URL as string,
config.VALIDATOR_API as string,
config.CURRENCY_PREFIX as string,
config.MIXNET_CONTRACT as string,
config.VESTING_CONTRACT as string
);
});
//todos
//we want to mock the majority of these tests
//and keep a few integration tests in place
describe("connect to the nym validator client and perform integration tests against the current testnet", () => {
test("get cached mixnodes", async () => {
try {
const response = await client.getCachedMixnodes();
//expect all mixnodes to have their owner address
response.forEach(mixnodeDetails => {
expect(mixnodeDetails.owner).toHaveLength(43)
});
}
catch (e) {
console.log(e);
}
});
test("get balance of address and denom of the network", async () => {
try {
//provide a users address and get their balance
//we expect their balance to be zero, as it's a new account
const address = await validatorClient.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
const response = await client.getBalance(address);
expect(response.amount).toStrictEqual("0");
expect(response.denom).toBe("unymt");
}
catch (e) {
console.log(e);
}
});
test("get minimium pledge amount for a mixnode", async () => {
try {
const response = await client.minimumMixnodePledge();
expect(response.amount).toBe("100000000");
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
}
catch (e) {
console.log(e);
}
});
test("get minimium gateway pledge amount", async () => {
try {
const response = await client.minimumGatewayPledge();
expect(response.amount).toBe("100000000");
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
}
catch (e) {
console.log(e);
}
});
test("get current mixnet contract address", () => {
try {
const response = client.mixnetContract;
expect(response).toStrictEqual(config.MIXNET_CONTRACT as string)
}
catch (e) {
console.log(e);
}
});
test("get current vesting contract address", () => {
try {
const response = client.vestingContract;
expect(response).toStrictEqual(config.VESTING_CONTRACT as string)
}
catch (e) {
console.log(e);
}
});
});
@@ -1,66 +0,0 @@
import SigningClient from '../../src/signing-client';
import validator from "../../src/index";
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { config } from '../test-utils/config';
let cosmWasmClient: CosmWasmClient;
let signingClient: SigningClient;
let mnemonic: string;
beforeEach(async () => {
cosmWasmClient = await SigningClient.connect(config.NYMD_URL as string);
mnemonic = validator.randomMnemonic();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic)
signingClient = await SigningClient.connectWithNymSigner(
wallet,
config.NYMD_URL as string,
config.VALIDATOR_API as string,
config.CURRENCY_PREFIX as string);
});
describe("alternate between the signing clients of nym and perform basic requests", () => {
test("retrieve balance using the cosmwasm client", async () => {
try {
const randomAddress = await validator.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
const balance = await cosmWasmClient.getBalance(randomAddress, config.CURRENCY_PREFIX as string);
expect(balance.denom).toStrictEqual(config.CURRENCY_PREFIX as string);
expect(balance.amount).toStrictEqual("0");
}
catch (e) {
console.log(e);
}
});
test("get the chain id of the network", async () => {
try {
//pass these values in environment variables in the future
const chainId = await cosmWasmClient.getChainId();
expect(chainId).toStrictEqual(config.CHAIN_ID as string);
}
catch (e) {
console.log(e);
}
});
test("get height then search its block", async () => {
try {
const height = await cosmWasmClient.getHeight()
const block = await cosmWasmClient.getBlock(height);
expect(block.header.chainId).toStrictEqual(config.CHAIN_ID as string);
expect(block.header.height).toStrictEqual(height);
}
catch (e) {
console.log(e);
}
});
test("get current reward pool", async () => {
try {
//this is due to change due to when rewards get distributed
const rewards = await signingClient.getRewardPool(config.MIXNET_CONTRACT as string);
expect(rewards).toStrictEqual("250000000000000");
}
catch (e) {
console.log(e);
}
});
});
@@ -1,70 +0,0 @@
import ValidatorApiQuerier from '../../src/validator-api-querier';
import { config } from '../test-utils/config';
import { now, elapsed} from '../test-utils/utils';
let client: ValidatorApiQuerier;
beforeEach(() => {
client = new ValidatorApiQuerier(config.VALIDATOR_API as string);
});
//todos
//we want to mock the majority of these tests
//and keep a few integration tests in place
describe("call out to the validator api and run queries", () => {
test.skip("build client and get all mixnodes", async () => {
try {
//this test was currently ran against a set of prefix data
//this will change
const ownerAddress = "nymt1ydqkmz0ddpvkd3l0vyf8k5xjrqtcnxxvhlpdsr";
let response = await client.getActiveMixnodes();
expect(response[0].owner).toStrictEqual(ownerAddress);
}
catch (e) {
console.log(e);
}
});
test("get rewarded mixnodes", async () => {
try {
// we assume that all mixnodes will have their owners address
// also active sets will determine rewarded mixnodes
let response = await client.getRewardedMixnodes();
response.forEach(rNode => {
expect(rNode.owner.length).toStrictEqual(43);
})
}
catch (e) {
console.log(e);
}
});
test("get cached gateways and it should be six", async () => {
try {
//current gateways running in sandbox-testnet 6
let response = await client.getCachedGateways();
expect(response.length).toStrictEqual(6);
}
catch (e) {
console.log(e);
}
});
test("get cached mixnodes", async () => {
try {
const start = now();
let response = await client.getCachedMixnodes();
response.forEach(mixnode => {
expect(mixnode.owner.length).toStrictEqual(43);
})
console.log(elapsed(start, true));
}
catch (e) {
console.log(e);
}
});
});
@@ -1,9 +0,0 @@
export const config = {
NYMD_URL: process.env.NYMD_URL,
VALIDATOR_API: process.env.VALIDATOR_API,
MIXNET_CONTRACT: process.env.MIXNET_CONTRACT,
VESTING_CONTRACT: process.env.MIXNET_CONTRACT,
USER_MNEMONIC: process.env.MNEMONIC,
CURRENCY_PREFIX: process.env.CURRENCY_PREFIX,
CHAIN_ID: process.env.CHAIN_ID
}
@@ -1,17 +0,0 @@
// timer actions
// Store current time as `start`
export const now = (eventName = null) => {
if (eventName) {
console.log(`Started ${eventName}..`);
}
return new Date().getTime();
}
//takes arg of start time
export const elapsed = (beginning: number, log = false) => {
const duration = new Date().getTime() - beginning;
if (log) {
console.log(`${duration / 1000}s`);
}
return duration;
}
@@ -1,10 +0,0 @@
const currency = require('../../src/currency');
describe("provide unit tests around the the currency module", () => {
test.skip("convert to native balance", () => {
const decimal = "12.0346";
const value = currency.printableBalanceToNative(decimal);
expect(value).toStrictEqual("12034600");
});
});
@@ -1,27 +0,0 @@
const stargate = require("../../src/stargate-helper");
import { config } from '../test-utils/config';
describe("test the stargate functions within the client", () => {
test("test that the gas price is returned correctly", () => {
const nymCurrency = config.CURRENCY_PREFIX as string;
const getGasPrice = stargate.nymGasPrice(nymCurrency);
expect(getGasPrice.denom).toBe(`u${nymCurrency}`);
});
test("provide invalid type returns an error message", () => {
//pass invalid type
expect(() => {
const nymCurrency = 13;
stargate.nymGasPrice(nymCurrency);
}).toThrow("13 is not of type string");
});
//provide test for downloading wasm
//mock this test
// test.skip("providing nothing returns", async () => {
// //pass invalid type
// const downloadWasm = stargate.downloadWasm("http://localhost");
// console.log(downloadWasm);
// })
});
@@ -1,19 +0,0 @@
import validator from "../../src/index";
import { config } from '../test-utils/config';
const NETWORK_DENOM = config.CURRENCY_PREFIX as string;
describe("perform basic interactions with the validator", () => {
test("build client and get all mixnodes", async () => {
const mnemonic = validator.randomMnemonic();
const mnemonicCount = mnemonic.split(" ").length;
expect(mnemonicCount).toStrictEqual(24);
});
test("build client and get all mixnodes", async () => {
const buildMnemonic = validator.randomMnemonic();
const mnemonic = await validator.mnemonicToAddress(buildMnemonic, NETWORK_DENOM);
expect(mnemonic).toHaveLength(43);
expect(mnemonic).toContain(NETWORK_DENOM);
});
});
+2 -10
View File
@@ -5,11 +5,7 @@
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist",
"types": [
"jest",
"node",
]
"outDir": "./dist"
},
"typedocOptions": {
"entryPoints": [
@@ -20,11 +16,7 @@
"exclude": [
"dist",
"examples",
"node_modules"
],
"include": [
"tests",
"./tests/*/*.tsx",
"./tests/*/*.ts"
"node_modules"
]
}
+2903 -3586
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -65,7 +65,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
-19
View File
@@ -148,22 +148,3 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) {
process::exit(1);
}
}
// 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 version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_version();
if binary_version != config_version {
log::warn!("The gateway binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
if version_checker::is_minor_version_compatible(binary_version, config_version) {
log::info!("but they are still semver compatible. However, consider running the `upgrade` command");
true
} else {
log::error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
false
}
} else {
true
}
}
+20
View File
@@ -7,6 +7,7 @@ use crate::node::Gateway;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run")
@@ -94,6 +95,25 @@ fn special_addresses() -> Vec<&'static str> {
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
}
// this only checks compatibility between config the binary. It does not take into consideration
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_version();
if binary_version != config_version {
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
if is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command");
true
} else {
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
false
}
} else {
true
}
}
pub async fn execute(matches: ArgMatches<'static>) {
let id = matches.value_of(ID_ARG_NAME).unwrap();
+72 -13
View File
@@ -8,6 +8,8 @@ use colored::Colorize;
use config::NymConfig;
use crypto::asymmetric::identity;
use log::error;
#[cfg(not(feature = "coconut"))]
use validator_client::nymd::AccountId;
const SIGN_TEXT_ARG_NAME: &str = "text";
const SIGN_ADDRESS_ARG_NAME: &str = "address";
@@ -33,7 +35,6 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> {
let mut address_sign_cmd = Arg::with_name(SIGN_ADDRESS_ARG_NAME)
.long(SIGN_ADDRESS_ARG_NAME)
.help("Signs your blockchain address with your identity key")
.takes_value(true)
.conflicts_with(SIGN_TEXT_ARG_NAME);
if cfg!(feature = "coconut") {
@@ -54,7 +55,58 @@ pub fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair {
identity_keypair
}
fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) -> String {
#[cfg(not(feature = "coconut"))]
fn derive_address(raw_mnemonic: &str) -> AccountId {
let mnemonic = match raw_mnemonic.parse() {
Ok(mnemonic) => mnemonic,
Err(err) => {
let error_message = format!("failed to parse the provided mnemonic - {}", err).red();
println!("{}", error_message);
process::exit(1);
}
};
let wallet =
match validator_client::nymd::wallet::DirectSecp256k1HdWallet::from_mnemonic(mnemonic) {
Ok(wallet) => wallet,
Err(err) => {
let error_message = format!(
"failed to derive your account with the provided mnemonic - {}",
err
)
.red();
println!("{}", error_message);
process::exit(1);
}
};
let account_data = match wallet.try_derive_accounts() {
Ok(data) => data,
Err(err) => {
let error_message = format!(
"failed to derive your account with the provided mnemonic - {}",
err
)
.red();
println!("{}", error_message);
process::exit(1);
}
};
account_data[0].address().clone()
}
#[cfg(not(feature = "coconut"))]
fn sign_derived_address(private_key: &identity::PrivateKey, address: &AccountId) {
let signature_bytes = private_key.sign(&address.to_bytes()).to_bytes();
let signature = bs58::encode(signature_bytes).into_string();
println!(
"The base58-encoded signature on '{}' is: {}",
address, signature
)
}
// we do tiny bit of sanity check validation
#[cfg(feature = "coconut")]
fn sign_provided_address(private_key: &identity::PrivateKey, raw_address: &str) {
let trimmed = raw_address.trim();
validate_bech32_address_or_exit(trimmed);
let signature = private_key.sign_text(trimmed);
@@ -62,8 +114,7 @@ fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) -
println!(
"The base58-encoded signature on '{}' is: {}",
trimmed, signature
);
signature
)
}
fn print_signed_text(private_key: &identity::PrivateKey, text: &str) {
@@ -90,20 +141,28 @@ pub fn execute(matches: &ArgMatches) {
return;
}
};
if !version_check(&config) {
error!("failed the local version check");
return;
}
let pathfinder = GatewayPathfinder::new_from_config(&config);
let identity_keypair = load_identity_keys(&pathfinder);
if let Some(text) = matches.value_of(SIGN_TEXT_ARG_NAME) {
print_signed_text(identity_keypair.private_key(), text)
} else if let Some(address) = matches.value_of(SIGN_ADDRESS_ARG_NAME) {
print_signed_address(identity_keypair.private_key(), address);
} else {
}
#[cfg(not(feature = "coconut"))]
{
if matches.is_present(SIGN_ADDRESS_ARG_NAME) {
let address = derive_address(&config.get_cosmos_mnemonic());
sign_derived_address(identity_keypair.private_key(), &address);
}
}
#[cfg(feature = "coconut")]
{
if let Some(address) = matches.value_of(SIGN_ADDRESS_ARG_NAME) {
sign_provided_address(identity_keypair.private_key(), address)
}
}
if !matches.is_present(SIGN_TEXT_ARG_NAME) && !matches.is_present(SIGN_ADDRESS_ARG_NAME) {
let error_message = format!(
"You must specify either '--{}' or '--{}' argument!",
SIGN_TEXT_ARG_NAME, SIGN_ADDRESS_ARG_NAME
+46 -42
View File
@@ -8,6 +8,7 @@ use crate::node::MixNode;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use crypto::asymmetric::{encryption, identity};
use tokio::runtime::Runtime;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("init")
@@ -65,56 +66,59 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
)
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
// TODO: this should probably be made implicit by slapping `#[tokio::main]` on our main method
// and then removing runtime from mixnode itself in `run`
let id = matches.value_of(ID_ARG_NAME).unwrap();
println!("Initialising mixnode {}...", id);
let rt = Runtime::new().unwrap();
rt.block_on(async {
let id = matches.value_of(ID_ARG_NAME).unwrap();
println!("Initialising mixnode {}...", id);
let already_init = if Config::default_config_file_path(Some(id)).exists() {
println!("Mixnode \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
true
} else {
false
};
let already_init = if Config::default_config_file_path(Some(id)).exists() {
println!("Mixnode \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
true
} else {
false
};
let mut config = Config::new(id);
config = override_config(config, &matches);
let mut config = Config::new(id);
config = override_config(config, matches);
// if node was already initialised, don't generate new keys
if !already_init {
let mut rng = rand::rngs::OsRng;
// if node was already initialised, don't generate new keys
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);
let pathfinder = MixNodePathfinder::new_from_config(&config);
pemstore::store_keypair(
&identity_keys,
&pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
),
)
.expect("Failed to save identity keys");
let identity_keys = identity::KeyPair::new(&mut rng);
let sphinx_keys = encryption::KeyPair::new(&mut rng);
let pathfinder = MixNodePathfinder::new_from_config(&config);
pemstore::store_keypair(
&identity_keys,
&pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
),
)
.expect("Failed to save identity keys");
pemstore::store_keypair(
&sphinx_keys,
&pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
pathfinder.public_encryption_key().to_owned(),
),
)
.expect("Failed to save sphinx keys");
pemstore::store_keypair(
&sphinx_keys,
&pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
pathfinder.public_encryption_key().to_owned(),
),
)
.expect("Failed to save sphinx keys");
println!("Saved mixnet identity and sphinx keypairs");
}
println!("Saved mixnet identity and sphinx keypairs");
}
let config_save_location = config.get_config_file_save_location();
config
.save_to_file(None)
.expect("Failed to save the config file");
println!("Saved configuration file to {:?}", config_save_location);
println!("Mixnode configuration completed.\n\n\n");
let config_save_location = config.get_config_file_save_location();
config
.save_to_file(None)
.expect("Failed to save the config file");
println!("Saved configuration file to {:?}", config_save_location);
println!("Mixnode configuration completed.\n\n\n");
MixNode::new(config).print_node_details()
MixNode::new(config).print_node_details()
})
}
-19
View File
@@ -116,22 +116,3 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) {
process::exit(1);
}
}
// 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 version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_version();
if binary_version != config_version {
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
if version_checker::is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command");
true
} else {
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
false
}
} else {
true
}
}
+24 -3
View File
@@ -6,6 +6,8 @@ use crate::config::Config;
use crate::node::MixNode;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::warn;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("run")
@@ -71,7 +73,26 @@ fn special_addresses() -> Vec<&'static str> {
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
}
pub async fn execute(matches: ArgMatches<'static>) {
// this only checks compatibility between config the binary. It does not take into consideration
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_version();
if binary_version != config_version {
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
if is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command");
true
} else {
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
false
}
} else {
true
}
}
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of(ID_ARG_NAME).unwrap();
println!("Starting mixnode {}...", id);
@@ -84,7 +105,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
}
};
config = override_config(config, &matches);
config = override_config(config, matches);
if !version_check(&config) {
error!("failed the local version check");
@@ -102,5 +123,5 @@ pub async fn execute(matches: ArgMatches<'static>) {
Select the correct version and install it to your machine. You will need to provide the following: \n ");
mixnode.print_node_details();
mixnode.run().await
mixnode.run()
}
-6
View File
@@ -83,12 +83,6 @@ pub fn execute(matches: &ArgMatches) {
return;
}
};
if !version_check(&config) {
error!("failed the local version check");
return;
}
let pathfinder = MixNodePathfinder::new_from_config(&config);
let identity_keypair = load_identity_keys(&pathfinder);
+5 -6
View File
@@ -10,8 +10,7 @@ mod commands;
mod config;
mod node;
#[tokio::main]
async fn main() {
fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
@@ -29,14 +28,14 @@ async fn main() {
.subcommand(commands::node_details::command_args())
.get_matches();
execute(arg_matches).await;
execute(arg_matches);
}
async fn execute(matches: ArgMatches<'static>) {
fn execute(matches: ArgMatches) {
match matches.subcommand() {
("describe", Some(m)) => commands::describe::execute(m),
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("sign", Some(m)) => commands::sign::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
("node-details", Some(m)) => commands::node_details::execute(m),
+23 -19
View File
@@ -27,6 +27,7 @@ use rand::thread_rng;
use std::net::SocketAddr;
use std::process;
use std::sync::Arc;
use tokio::runtime::Runtime;
use version_checker::parse_version;
pub(crate) mod http;
@@ -278,30 +279,33 @@ impl MixNode {
);
}
pub async fn run(&mut self) {
pub fn run(&mut self) {
info!("Starting nym mixnode");
if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists().await {
if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() {
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
} else {
log::error!(
"Our announce-host is identical to an existing node's announce-host! (its key is {:?})",
duplicate_node_key
);
return;
let runtime = Runtime::new().unwrap();
runtime.block_on(async {
if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists().await {
if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() {
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
} else {
log::error!(
"Our announce-host is identical to an existing node's announce-host! (its key is {:?})",
duplicate_node_key
);
return;
}
}
}
let (node_stats_pointer, node_stats_update_sender) = self.start_node_stats_controller();
let delay_forwarding_channel =
self.start_packet_delay_forwarder(node_stats_update_sender.clone());
self.start_socket_listener(node_stats_update_sender, delay_forwarding_channel);
let (node_stats_pointer, node_stats_update_sender) = self.start_node_stats_controller();
let delay_forwarding_channel = self.start_packet_delay_forwarder(node_stats_update_sender.clone());
self.start_socket_listener(node_stats_update_sender, delay_forwarding_channel);
let atomic_verloc_results = self.start_verloc_measurements();
self.start_http_api(atomic_verloc_results, node_stats_pointer);
let atomic_verloc_results= self.start_verloc_measurements();
self.start_http_api(atomic_verloc_results, node_stats_pointer);
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
self.wait_for_interrupt().await
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
self.wait_for_interrupt().await
});
}
}