Merge branch 'develop' of github.com:nymtech/nym into develop

This commit is contained in:
Dave
2020-10-14 18:15:24 +01:00
69 changed files with 1445 additions and 2670 deletions
Generated
+149 -806
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -39,10 +39,9 @@ members = [
"common/wasm-utils",
"gateway",
"gateway/gateway-requests",
"service-providers/sphinx-socks",
"mixnode",
"network-monitor",
"validator",
"service-providers/sphinx-socks",
]
default-members = [
@@ -52,6 +51,5 @@ default-members = [
"gateway",
"service-providers/sphinx-socks",
"mixnode",
"validator",
"network-monitor",
]
+2 -3
View File
@@ -1,13 +1,12 @@
## The Nym Privacy Platform
This repository contains the full Nym platform.
This repository contains the Nym mixnet.
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
* nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
* nym-sfw-provider - a store-and-forward service provider. The provider acts sort of like a mailbox for mixnet messages.
* nym-validator - currently just starting development. Handles consensus ordering of transactions, mixmining, and coconut credential generation and validation.
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
[![Build Status](https://travis-ci.com/nymtech/nym.svg?branch=develop)](https://travis-ci.com/nymtech/nym)
+1
View File
@@ -9,6 +9,7 @@ edition = "2018"
[dependencies]
dirs = "2.0.2"
futures = "0.3.1"
humantime-serde = "1.0.1"
log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0.104", features = ["derive"] }
+140 -53
View File
@@ -13,32 +13,85 @@
// limitations under the License.
use config::NymConfig;
use serde::{Deserialize, Serialize};
use serde::{
de::{self, IntoDeserializer, Visitor},
Deserialize, Deserializer, Serialize,
};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::time;
use std::time::Duration;
pub mod persistence;
pub const MISSING_VALUE: &str = "MISSING VALUE";
// 'CLIENT'
const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
// all delays are in milliseconds
const DEFAULT_ACK_WAIT_ADDITION: u64 = 1_500;
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000;
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 100;
const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 100;
const DEFAULT_TOPOLOGY_REFRESH_RATE: u64 = 30_000;
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000;
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(1000);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(100);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(100);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_millis(30_000);
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000;
const ZERO_DELAY: Duration = Duration::from_nanos(0);
// custom function is defined to deserialize based on whether field contains a pre 0.9.0
// u64 interpreted as milliseconds or proper duration introduced in 0.9.0
//
// TODO: when we get to refactoring down the line, this code can just be removed
// and all Duration fields could just have #[serde(with = "humantime_serde")] instead
// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have,
// for argument sake, 0.11.0 out
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
struct DurationVisitor;
impl<'de> Visitor<'de> for DurationVisitor {
type Value = Duration;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("u64 or a duration")
}
fn visit_i64<E>(self, value: i64) -> Result<Duration, E>
where
E: de::Error,
{
self.visit_u64(value as u64)
}
fn visit_u64<E>(self, value: u64) -> Result<Duration, E>
where
E: de::Error,
{
Ok(Duration::from_millis(Deserialize::deserialize(
value.into_deserializer(),
)?))
}
fn visit_str<E>(self, value: &str) -> Result<Duration, E>
where
E: de::Error,
{
humantime_serde::deserialize(value.into_deserializer())
}
}
deserializer.deserialize_any(DurationVisitor)
}
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config<T> {
@@ -127,9 +180,9 @@ impl<T: NymConfig> Config<T> {
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.average_packet_delay = 10;
self.debug.loop_cover_traffic_average_delay = 20; // 50 cover messages / s
self.debug.message_sending_average_delay = 5; // 200 "real" messages / s
self.debug.average_packet_delay = Duration::from_millis(10);
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(100); // 10 cover messages / s
self.debug.message_sending_average_delay = Duration::from_millis(5); // 200 "real" messages / s
}
pub fn set_vpn_mode(&mut self, vpn_mode: bool) {
@@ -140,6 +193,10 @@ impl<T: NymConfig> Config<T> {
self.debug.vpn_key_reuse_limit = Some(reuse_limit)
}
pub fn set_custom_version(&mut self, version: &str) {
self.client.version = version.to_string();
}
pub fn get_id(&self) -> String {
self.client.id.clone()
}
@@ -189,19 +246,19 @@ impl<T: NymConfig> Config<T> {
}
// Debug getters
pub fn get_average_packet_delay(&self) -> time::Duration {
pub fn get_average_packet_delay(&self) -> Duration {
if self.client.vpn_mode {
ZERO_DELAY
} else {
time::Duration::from_millis(self.debug.average_packet_delay)
self.debug.average_packet_delay
}
}
pub fn get_average_ack_delay(&self) -> time::Duration {
pub fn get_average_ack_delay(&self) -> Duration {
if self.client.vpn_mode {
ZERO_DELAY
} else {
time::Duration::from_millis(self.debug.average_ack_delay)
self.debug.average_ack_delay
}
}
@@ -209,32 +266,32 @@ impl<T: NymConfig> Config<T> {
self.debug.ack_wait_multiplier
}
pub fn get_ack_wait_addition(&self) -> time::Duration {
time::Duration::from_millis(self.debug.ack_wait_addition)
pub fn get_ack_wait_addition(&self) -> Duration {
self.debug.ack_wait_addition
}
pub fn get_loop_cover_traffic_average_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.loop_cover_traffic_average_delay)
pub fn get_loop_cover_traffic_average_delay(&self) -> Duration {
self.debug.loop_cover_traffic_average_delay
}
pub fn get_message_sending_average_delay(&self) -> time::Duration {
pub fn get_message_sending_average_delay(&self) -> Duration {
if self.client.vpn_mode {
ZERO_DELAY
} else {
time::Duration::from_millis(self.debug.message_sending_average_delay)
self.debug.message_sending_average_delay
}
}
pub fn get_gateway_response_timeout(&self) -> time::Duration {
time::Duration::from_millis(self.debug.gateway_response_timeout)
pub fn get_gateway_response_timeout(&self) -> Duration {
self.debug.gateway_response_timeout
}
pub fn get_topology_refresh_rate(&self) -> time::Duration {
time::Duration::from_millis(self.debug.topology_refresh_rate)
pub fn get_topology_refresh_rate(&self) -> Duration {
self.debug.topology_refresh_rate
}
pub fn get_topology_resolution_timeout(&self) -> time::Duration {
time::Duration::from_millis(self.debug.topology_resolution_timeout)
pub fn get_topology_resolution_timeout(&self) -> Duration {
self.debug.topology_resolution_timeout
}
pub fn get_vpn_mode(&self) -> bool {
@@ -251,6 +308,10 @@ impl<T: NymConfig> Config<T> {
),
}
}
pub fn get_version(&self) -> &str {
&self.client.version
}
}
impl<T: NymConfig> Default for Config<T> {
@@ -267,6 +328,7 @@ impl<T: NymConfig> Default for Config<T> {
#[serde(deny_unknown_fields)]
pub struct Client<T> {
/// Version of the client for which this configuration was created.
#[serde(default = "missing_string_value")]
version: String,
/// ID specifies the human readable ID of this particular client.
@@ -278,6 +340,7 @@ pub struct Client<T> {
/// Special mode of the system such that all messages are sent as soon as they are received
/// and no cover traffic is generated. If set all message delays are set to 0 and overwriting
/// 'Debug' values will have no effect.
#[serde(default)]
vpn_mode: bool,
/// Path to file containing private identity key.
@@ -344,31 +407,31 @@ impl<T: NymConfig> Default for Client<T> {
impl<T: NymConfig> Client<T> {
fn default_private_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("private_identity.pem")
T::default_data_directory(id).join("private_identity.pem")
}
fn default_public_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("public_identity.pem")
T::default_data_directory(id).join("public_identity.pem")
}
fn default_private_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("private_encryption.pem")
T::default_data_directory(id).join("private_encryption.pem")
}
fn default_public_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("public_encryption.pem")
T::default_data_directory(id).join("public_encryption.pem")
}
fn default_gateway_shared_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("gateway_shared.pem")
T::default_data_directory(id).join("gateway_shared.pem")
}
fn default_ack_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("ack_key.pem")
T::default_data_directory(id).join("ack_key.pem")
}
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("reply_key_store")
T::default_data_directory(id).join("reply_key_store")
}
}
@@ -389,15 +452,21 @@ pub struct Debug {
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
/// The provided value is interpreted as milliseconds.
average_packet_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
/// The provided value is interpreted as milliseconds.
average_ack_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
@@ -407,36 +476,54 @@ pub struct Debug {
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
/// The provided value is interpreted as milliseconds.
ack_wait_addition: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
ack_wait_addition: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
/// The provided value is interpreted as milliseconds.
loop_cover_traffic_average_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
loop_cover_traffic_average_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
/// The provided value is interpreted as milliseconds.
message_sending_average_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
message_sending_average_delay: Duration,
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
/// The provided value is interpreted as milliseconds.
gateway_response_timeout: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
gateway_response_timeout: Duration,
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
/// The provided value is interpreted as milliseconds.
topology_refresh_rate: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
/// The provided value is interpreted as milliseconds.
topology_resolution_timeout: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
topology_resolution_timeout: Duration,
/// If the mode of the client is set to VPN it specifies number of packets created with the
/// same initial secret until it gets rotated.
+1
View File
@@ -40,6 +40,7 @@ nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" }
version-checker = { path = "../../common/version-checker" }
[dev-dependencies]
tempfile = "3.1.0"
+1 -4
View File
@@ -14,6 +14,7 @@
use crate::client::config::template::config_template;
use client_core::config::Config as BaseConfig;
pub use client_core::config::MISSING_VALUE;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
@@ -54,10 +55,6 @@ impl NymConfig for Config {
config_template()
}
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
+4 -4
View File
@@ -101,10 +101,10 @@ listening_port = {{ socket.listening_port }}
[debug]
average_packet_delay = {{ debug.average_packet_delay }}
average_ack_delay = {{ debug.average_ack_delay }}
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
message_sending_average_delay = {{ debug.message_sending_average_delay }}
average_packet_delay = '{{ debug.average_packet_delay }}'
average_ack_delay = '{{ debug.average_ack_delay }}'
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
"#
}
+8
View File
@@ -25,6 +25,7 @@ use gateway_requests::registration::handshake::SharedKeys;
use rand::rngs::OsRng;
use rand::seq::SliceRandom;
use std::convert::TryInto;
use std::process;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, NymTopology};
@@ -131,7 +132,14 @@ pub fn execute(matches: &ArgMatches) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
if Config::default_config_file_path(id).exists() {
eprintln!("Client \"{}\" was already initialised before! If you wanted to upgrade your client to most recent version, try `upgrade` command instead!", id);
process::exit(1);
}
let mut config = Config::new(id);
let mut rng = OsRng;
// TODO: ideally that should be the last thing that's being done to config.
+3 -2
View File
@@ -15,8 +15,9 @@
use crate::client::config::{Config, SocketType};
use clap::ArgMatches;
pub mod init;
pub mod run;
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
if let Some(directory) = matches.value_of("directory") {
+171
View File
@@ -0,0 +1,171 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client::config::{Config, MISSING_VALUE};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use std::fmt::Display;
use std::process;
use version_checker::{parse_version, Version};
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade mixnode from {} to {} ...",
from, to
);
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!(
"Upgrade from {} to {} failed!\n==================\n",
from, to
);
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"Upgrade from {} to {} was successful!\n==================\n",
from, to
);
}
fn pre_090_upgrade(from: &str, mut config: Config) -> Config {
// this is not extracted to separate function as you only have to manually pass version
// if upgrading from pre090 version
let from = match from.strip_prefix("v") {
Some(stripped) => stripped,
None => from,
};
let from = match from.strip_prefix("V") {
Some(stripped) => stripped,
None => from,
};
let from_version = parse_version(from).expect("invalid version provided!");
if from_version.major == 0 && from_version.minor < 8 {
// technically this could be implemented, but is there any point in that?
eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead");
process::exit(1)
}
if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 {
eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version");
process::exit(1)
}
// note: current is guaranteed to not have any `build` information suffix (nor pre-release
// information), as this was asserted at the beginning of this command)
//
// upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate
// step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0)
// this way we don't need to have all the crazy paths on how to upgrade from any version to any
// other version. We just upgrade one minor version at a time.
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let to_version = if current.major == 0 && current.minor == 9 {
current
} else {
Version::new(0, 9, 0)
};
print_start_upgrade(&from_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
});
print_successful_upgrade(from_version, to_version);
config
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the mixnode")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-client we want to upgrade")
.takes_value(true)
.required(true),
)
// the rest of arguments depend on the upgrade path
.arg(Arg::with_name("current version")
.long("current-version")
.help("REQUIRED FOR PRE-0.9.0 UPGRADES. Self provided version of the nym-client if none is available in the config. NOTE: if provided incorrectly, results may be catastrophic.")
.takes_value(true)
)
}
pub fn execute(matches: &ArgMatches) {
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
// technically this is not a correct way of checking it as a released version might contain valid build identifiers
// however, we are not using them ourselves at the moment and hence it should be fine.
// if we change our mind, we could easily tweak this code
if current.is_prerelease() || !current.build.is_empty() {
eprintln!(
"Trying to upgrade to a non-released version {}. This is not supported!",
current
);
process::exit(1)
}
let id = matches.value_of("id").unwrap();
let mut existing_config = Config::load_from_file(None, Some(id)).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
process::exit(1)
});
// versions fields were added in 0.9.0
if existing_config.get_base().get_version() == MISSING_VALUE {
let self_reported_version = matches.value_of("current version").unwrap_or_else(|| {
eprintln!(
"trying to upgrade from pre v0.9.0 without providing current system version!"
);
process::exit(1)
});
// upgrades up to 0.9.0
existing_config = pre_090_upgrade(self_reported_version, existing_config);
}
let config_version =
Version::parse(existing_config.get_base().get_version()).unwrap_or_else(|err| {
eprintln!("failed to parse node version! - {:?}", err);
process::exit(1)
});
if config_version.is_prerelease() || !config_version.build.is_empty() {
eprintln!(
"Trying to upgrade to from non-released version {}. This is not supported!",
current
);
process::exit(1)
}
// here be upgrade path to 0.10.0 and beyond based on version number from config
if config_version == current {
println!("You're using the most recent version!");
} else {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current);
process::exit(1)
}
}
+2
View File
@@ -29,6 +29,7 @@ fn main() {
.about("Implementation of the Nym Client")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
@@ -38,6 +39,7 @@ fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
+1
View File
@@ -33,6 +33,7 @@ ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
socks5-requests = { path = "../../common/socks5/requests" }
topology = { path = "../../common/topology" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
version-checker = { path = "../../common/version-checker" }
[dev-dependencies]
tempfile = "3.1.0"
+1 -4
View File
@@ -14,6 +14,7 @@
use crate::client::config::template::config_template;
use client_core::config::Config as BaseConfig;
pub use client_core::config::MISSING_VALUE;
use config::NymConfig;
use nymsphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
@@ -37,10 +38,6 @@ impl NymConfig for Config {
config_template()
}
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
+4 -4
View File
@@ -100,10 +100,10 @@ listening_port = {{ socks5.listening_port }}
[debug]
average_packet_delay = {{ debug.average_packet_delay }}
average_ack_delay = {{ debug.average_ack_delay }}
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
message_sending_average_delay = {{ debug.message_sending_average_delay }}
average_packet_delay = '{{ debug.average_packet_delay }}'
average_ack_delay = '{{ debug.average_ack_delay }}'
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
"#
}
+7
View File
@@ -24,6 +24,7 @@ use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use rand::{prelude::SliceRandom, rngs::OsRng};
use std::convert::TryInto;
use std::process;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, NymTopology};
@@ -134,7 +135,13 @@ pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap(); // required for now
let provider_address = matches.value_of("provider").unwrap();
if Config::default_config_file_path(id).exists() {
eprintln!("Socks5 client \"{}\" was already initialised before! If you wanted to upgrade your client to most recent version, try `upgrade` command instead!", id);
process::exit(1);
}
let mut config = Config::new(id, provider_address);
let mut rng = OsRng;
// TODO: ideally that should be the last thing that's being done to config.
+3 -2
View File
@@ -15,8 +15,9 @@
use crate::client::config::Config;
use clap::ArgMatches;
pub mod init;
pub mod run;
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
if let Some(directory) = matches.value_of("directory") {
+171
View File
@@ -0,0 +1,171 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client::config::{Config, MISSING_VALUE};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use std::fmt::Display;
use std::process;
use version_checker::{parse_version, Version};
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade mixnode from {} to {} ...",
from, to
);
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!(
"Upgrade from {} to {} failed!\n==================\n",
from, to
);
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"Upgrade from {} to {} was successful!\n==================\n",
from, to
);
}
fn pre_090_upgrade(from: &str, mut config: Config) -> Config {
// this is not extracted to separate function as you only have to manually pass version
// if upgrading from pre090 version
let from = match from.strip_prefix("v") {
Some(stripped) => stripped,
None => from,
};
let from = match from.strip_prefix("V") {
Some(stripped) => stripped,
None => from,
};
let from_version = parse_version(from).expect("invalid version provided!");
if from_version.major == 0 && from_version.minor < 8 {
// technically this could be implemented, but is there any point in that?
eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead");
process::exit(1)
}
if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 {
eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version");
process::exit(1)
}
// note: current is guaranteed to not have any `build` information suffix (nor pre-release
// information), as this was asserted at the beginning of this command)
//
// upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate
// step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0)
// this way we don't need to have all the crazy paths on how to upgrade from any version to any
// other version. We just upgrade one minor version at a time.
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let to_version = if current.major == 0 && current.minor == 9 {
current
} else {
Version::new(0, 9, 0)
};
print_start_upgrade(&from_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
});
print_successful_upgrade(from_version, to_version);
config
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the mixnode")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-socks5-client we want to upgrade")
.takes_value(true)
.required(true),
)
// the rest of arguments depend on the upgrade path
.arg(Arg::with_name("current version")
.long("current-version")
.help("REQUIRED FOR PRE-0.9.0 UPGRADES. Self provided version of the nym-socks5-client if none is available in the config. NOTE: if provided incorrectly, results may be catastrophic.")
.takes_value(true)
)
}
pub fn execute(matches: &ArgMatches) {
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
// technically this is not a correct way of checking it as a released version might contain valid build identifiers
// however, we are not using them ourselves at the moment and hence it should be fine.
// if we change our mind, we could easily tweak this code
if current.is_prerelease() || !current.build.is_empty() {
eprintln!(
"Trying to upgrade to a non-released version {}. This is not supported!",
current
);
process::exit(1)
}
let id = matches.value_of("id").unwrap();
let mut existing_config = Config::load_from_file(None, Some(id)).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
process::exit(1)
});
// versions fields were added in 0.9.0
if existing_config.get_base().get_version() == MISSING_VALUE {
let self_reported_version = matches.value_of("current version").unwrap_or_else(|| {
eprintln!(
"trying to upgrade from pre v0.9.0 without providing current system version!"
);
process::exit(1)
});
// upgrades up to 0.9.0
existing_config = pre_090_upgrade(self_reported_version, existing_config);
}
let config_version =
Version::parse(existing_config.get_base().get_version()).unwrap_or_else(|err| {
eprintln!("failed to parse node version! - {:?}", err);
process::exit(1)
});
if config_version.is_prerelease() || !config_version.build.is_empty() {
eprintln!(
"Trying to upgrade to from non-released version {}. This is not supported!",
current
);
process::exit(1)
}
// here be upgrade path to 0.10.0 and beyond based on version number from config
if config_version == current {
println!("You're using the most recent version!");
} else {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current);
process::exit(1)
}
}
+2
View File
@@ -29,6 +29,7 @@ fn main() {
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
@@ -38,6 +39,7 @@ fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
+23 -11
View File
@@ -21,21 +21,23 @@ use std::{fs, io};
pub trait NymConfig: Default + Serialize + DeserializeOwned {
fn template() -> &'static str;
fn config_file_name() -> String;
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf;
// default, most probable, implementations; can be easily overridden where required
fn default_config_directory(id: Option<&str>) -> PathBuf {
Self::default_root_directory()
.join(id.unwrap_or(""))
.join("config")
fn default_config_directory(id: &str) -> PathBuf {
Self::default_root_directory().join(id).join("config")
}
fn default_data_directory(id: Option<&str>) -> PathBuf {
Self::default_root_directory()
.join(id.unwrap_or(""))
.join("data")
fn default_data_directory(id: &str) -> PathBuf {
Self::default_root_directory().join(id).join("data")
}
fn default_config_file_path(id: &str) -> PathBuf {
Self::default_config_directory(id).join(Self::config_file_name())
}
fn root_directory(&self) -> PathBuf;
@@ -66,10 +68,20 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
)
}
// Hopefully should get simplified by https://github.com/nymtech/nym/issues/385
// so that `custom_location` could be completely removed
fn load_from_file(custom_location: Option<PathBuf>, id: Option<&str>) -> io::Result<Self> {
if custom_location.is_none() && id.is_none() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Both custom location and id are unspecified!",
));
}
// unwrap on id can't fail as we just checked whether at least one of custom location or id
// is not None
let config_contents = fs::read_to_string(
custom_location
.unwrap_or_else(|| Self::default_config_directory(id).join("config.toml")),
custom_location.unwrap_or_else(|| Self::default_config_file_path(id.unwrap())),
)?;
toml::from_str(&config_contents)
+6 -1
View File
@@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use semver::Version;
use semver::SemVerError;
pub use semver::Version;
/// Checks whether given `version` is compatible with a given semantic version requirement `req`
/// according to major-minor semver rules. The semantic version requirement can be passed as a full,
@@ -32,6 +33,10 @@ pub fn is_minor_version_compatible(version: &str, req: &str) -> bool {
expected_version.major == req_version.major && expected_version.minor == req_version.minor
}
pub fn parse_version(raw_version: &str) -> Result<Version, SemVerError> {
Version::parse(raw_version)
}
#[cfg(test)]
mod tests {
use super::*;
+2
View File
@@ -13,6 +13,7 @@ dirs = "2.0.2"
dashmap = "4.0.0-rc6"
dotenv = "0.15.0"
futures = "0.3"
humantime-serde = "1.0.1"
log = "0.4"
pretty_env_logger = "0.3"
rand = "0.7"
@@ -31,6 +32,7 @@ mixnet-client = { path = "../common/client-libs/mixnet-client" }
mixnode-common = { path = "../common/mixnode-common" }
nymsphinx = { path = "../common/nymsphinx" }
pemstore = { path = "../common/pemstore" }
version-checker = { path = "../common/version-checker" }
[dependencies.tungstenite]
version = "0.11"
+8 -1
View File
@@ -14,9 +14,11 @@
use crate::commands::override_config;
use crate::config::persistence::pathfinder::GatewayPathfinder;
use crate::config::Config;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use crypto::asymmetric::{encryption, identity};
use std::process;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("init")
@@ -116,7 +118,12 @@ pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
println!("Initialising gateway {}...", id);
let mut config = crate::config::Config::new(id);
if Config::default_config_file_path(id).exists() {
eprintln!("Gateway \"{}\" was already initialised before! If you wanted to upgrade your gateway to most recent version, try `upgrade` command instead!", id);
process::exit(1);
}
let mut config = Config::new(id);
config = override_config(config, matches);
+3 -2
View File
@@ -15,8 +15,9 @@
use crate::config::Config;
use clap::ArgMatches;
pub mod init;
pub mod run;
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
let mut was_mix_host_overridden = false;
+170
View File
@@ -0,0 +1,170 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::config::{Config, MISSING_VALUE};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use std::fmt::Display;
use std::process;
use version_checker::{parse_version, Version};
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade gateway from {} to {} ...",
from, to
);
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!(
"Upgrade from {} to {} failed!\n==================\n",
from, to
);
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"Upgrade from {} to {} was successful!\n==================\n",
from, to
);
}
fn pre_090_upgrade(from: &str, config: Config) -> Config {
// this is not extracted to separate function as you only have to manually pass version
// if upgrading from pre090 version
let from = match from.strip_prefix("v") {
Some(stripped) => stripped,
None => from,
};
let from = match from.strip_prefix("V") {
Some(stripped) => stripped,
None => from,
};
let from_version = parse_version(from).expect("invalid version provided!");
if from_version.major == 0 && from_version.minor < 8 {
// technically this could be implemented, but is there any point in that?
eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead");
process::exit(1)
}
if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 {
eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version");
process::exit(1)
}
// note: current is guaranteed to not have any `build` information suffix (nor pre-release
// information), as this was asserted at the beginning of this command)
//
// upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate
// step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0)
// this way we don't need to have all the crazy paths on how to upgrade from any version to any
// other version. We just upgrade one minor version at a time.
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let to_version = if current.major == 0 && current.minor == 9 {
current
} else {
Version::new(0, 9, 0)
};
print_start_upgrade(&from_version, &to_version);
let upgraded_config = config.with_custom_version(to_version.to_string().as_ref());
// TODO: THIS IS INCOMPLETE AS ONCE PRESENCE IS REMOVED IN 0.9.0 IT WILL ALSO NEED
// TO BE PURGED FROM CONFIG
upgraded_config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
});
print_successful_upgrade(from_version, to_version);
upgraded_config
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the gateway")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-gateway we want to upgrade")
.takes_value(true)
.required(true),
)
// the rest of arguments depend on the upgrade path
.arg(Arg::with_name("current version")
.long("current-version")
.help("REQUIRED FOR PRE-0.9.0 UPGRADES. Self provided version of the nym-gateway if none is available in the config. NOTE: if provided incorrectly, results may be catastrophic.")
.takes_value(true)
)
}
pub fn execute(matches: &ArgMatches) {
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
// technically this is not a correct way of checking it as a released version might contain valid build identifiers
// however, we are not using them ourselves at the moment and hence it should be fine.
// if we change our mind, we could easily tweak this code
if current.is_prerelease() || !current.build.is_empty() {
eprintln!(
"Trying to upgrade to a non-released version {}. This is not supported!",
current
);
process::exit(1)
}
let id = matches.value_of("id").unwrap();
let mut existing_config = Config::load_from_file(None, Some(id)).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
process::exit(1)
});
// versions fields were added in 0.9.0
if existing_config.get_version() == MISSING_VALUE {
let self_reported_version = matches.value_of("current version").unwrap_or_else(|| {
eprintln!(
"trying to upgrade from pre v0.9.0 without providing current system version!"
);
process::exit(1)
});
// upgrades up to 0.9.0
existing_config = pre_090_upgrade(self_reported_version, existing_config);
}
let config_version = Version::parse(existing_config.get_version()).unwrap_or_else(|err| {
eprintln!("failed to parse node version! - {:?}", err);
process::exit(1)
});
if config_version.is_prerelease() || !config_version.build.is_empty() {
eprintln!(
"Trying to upgrade to from non-released version {}. This is not supported!",
current
);
process::exit(1)
}
// here be upgrade path to 0.10.0 and beyond based on version number from config
if config_version == current {
println!("You're using the most recent version!");
} else {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current);
process::exit(1)
}
}
+114 -36
View File
@@ -15,15 +15,20 @@
use crate::config::template::config_template;
use config::NymConfig;
use log::*;
use serde::{Deserialize, Serialize};
use serde::{
de::{self, IntoDeserializer, Visitor},
Deserialize, Deserializer, Serialize,
};
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::str::FromStr;
use std::time;
use std::time::Duration;
pub mod persistence;
mod template;
pub(crate) const MISSING_VALUE: &str = "MISSING VALUE";
// 'GATEWAY'
const DEFAULT_MIX_LISTENING_PORT: u16 = 1789;
const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000;
@@ -31,11 +36,11 @@ const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 10_000; // 10s
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s
const DEFAULT_CACHE_ENTRY_TTL: u64 = 30_000;
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_CACHE_ENTRY_TTL: Duration = Duration::from_millis(30_000);
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5;
@@ -60,10 +65,6 @@ impl NymConfig for Config {
config_template()
}
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
@@ -90,6 +91,57 @@ impl NymConfig for Config {
}
}
// custom function is defined to deserialize based on whether field contains a pre 0.9.0
// u64 interpreted as milliseconds or proper duration introduced in 0.9.0
//
// TODO: when we get to refactoring down the line, this code can just be removed
// and all Duration fields could just have #[serde(with = "humantime_serde")] instead
// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have,
// for argument sake, 0.11.0 out
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
struct DurationVisitor;
impl<'de> Visitor<'de> for DurationVisitor {
type Value = Duration;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("u64 or a duration")
}
fn visit_i64<E>(self, value: i64) -> Result<Duration, E>
where
E: de::Error,
{
self.visit_u64(value as u64)
}
fn visit_u64<E>(self, value: u64) -> Result<Duration, E>
where
E: de::Error,
{
Ok(Duration::from_millis(Deserialize::deserialize(
value.into_deserializer(),
)?))
}
fn visit_str<E>(self, value: &str) -> Result<Duration, E>
where
E: de::Error,
{
humantime_serde::deserialize(value.into_deserializer())
}
}
deserializer.deserialize_any(DurationVisitor)
}
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
impl Config {
pub fn new<S: Into<String>>(id: S) -> Self {
Config::default().with_id(id)
@@ -322,6 +374,11 @@ impl Config {
self
}
pub fn with_custom_version(mut self, version: &str) -> Self {
self.gateway.version = version.to_string();
self
}
// getters
pub fn get_config_file_save_location(&self) -> PathBuf {
self.config_directory().join(Self::config_file_name())
@@ -351,8 +408,8 @@ impl Config {
self.gateway.presence_directory_server.clone()
}
pub fn get_presence_sending_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.presence_sending_delay)
pub fn get_presence_sending_delay(&self) -> Duration {
self.debug.presence_sending_delay
}
pub fn get_mix_listening_address(&self) -> SocketAddr {
@@ -379,16 +436,16 @@ impl Config {
self.clients_endpoint.ledger_path.clone()
}
pub fn get_packet_forwarding_initial_backoff(&self) -> time::Duration {
time::Duration::from_millis(self.debug.packet_forwarding_initial_backoff)
pub fn get_packet_forwarding_initial_backoff(&self) -> Duration {
self.debug.packet_forwarding_initial_backoff
}
pub fn get_packet_forwarding_maximum_backoff(&self) -> time::Duration {
time::Duration::from_millis(self.debug.packet_forwarding_maximum_backoff)
pub fn get_packet_forwarding_maximum_backoff(&self) -> Duration {
self.debug.packet_forwarding_maximum_backoff
}
pub fn get_initial_connection_timeout(&self) -> time::Duration {
time::Duration::from_millis(self.debug.initial_connection_timeout)
pub fn get_initial_connection_timeout(&self) -> Duration {
self.debug.initial_connection_timeout
}
pub fn get_message_retrieval_limit(&self) -> u16 {
@@ -399,8 +456,12 @@ impl Config {
self.debug.stored_messages_filename_length
}
pub fn get_cache_entry_ttl(&self) -> time::Duration {
time::Duration::from_millis(self.debug.cache_entry_ttl)
pub fn get_cache_entry_ttl(&self) -> Duration {
self.debug.cache_entry_ttl
}
pub fn get_version(&self) -> &str {
&self.gateway.version
}
}
@@ -408,6 +469,7 @@ impl Config {
#[serde(deny_unknown_fields)]
pub struct Gateway {
/// Version of the gateway for which this configuration was created.
#[serde(default = "missing_string_value")]
version: String,
/// ID specifies the human readable ID of this particular gateway.
@@ -441,19 +503,19 @@ pub struct Gateway {
impl Gateway {
fn default_private_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("private_sphinx.pem")
Config::default_data_directory(id).join("private_sphinx.pem")
}
fn default_public_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("public_sphinx.pem")
Config::default_data_directory(id).join("public_sphinx.pem")
}
fn default_private_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("private_identity.pem")
Config::default_data_directory(id).join("private_identity.pem")
}
fn default_public_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("public_identity.pem")
Config::default_data_directory(id).join("public_identity.pem")
}
fn default_location() -> String {
@@ -529,11 +591,11 @@ pub struct ClientsEndpoint {
impl ClientsEndpoint {
fn default_inboxes_directory(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("inboxes")
Config::default_data_directory(id).join("inboxes")
}
fn default_ledger_path(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("client_ledger.sled")
Config::default_data_directory(id).join("client_ledger.sled")
}
}
@@ -565,20 +627,33 @@ impl Default for Logging {
pub struct Debug {
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
/// The provided value is interpreted as milliseconds.
packet_forwarding_initial_backoff: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
/// The provided value is interpreted as milliseconds.
packet_forwarding_maximum_backoff: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
/// The provider value is interpreted as milliseconds.
initial_connection_timeout: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
initial_connection_timeout: Duration,
/// Delay between each subsequent presence data being sent.
presence_sending_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
presence_sending_delay: Duration,
/// Length of filenames for new client messages.
stored_messages_filename_length: u16,
@@ -589,8 +664,11 @@ pub struct Debug {
message_retrieval_limit: u16,
/// Duration for which a cached vpn processing result is going to get stored for.
/// The provided value is interpreted as milliseconds.
cache_entry_ttl: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
cache_entry_ttl: Duration,
}
impl Default for Debug {
+2
View File
@@ -29,6 +29,7 @@ fn main() {
.about("Implementation of the Nym Mixnet Gateway")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
@@ -38,6 +39,7 @@ fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
+2
View File
@@ -13,6 +13,7 @@ curve25519-dalek = "2.0.0"
dirs = "2.0.2"
dotenv = "0.15.0"
futures = "0.3.1"
humantime-serde = "1.0.1"
log = "0.4"
pretty_env_logger = "0.3"
serde = { version = "1.0.104", features = ["derive"] }
@@ -28,6 +29,7 @@ mixnode-common = { path = "../common/mixnode-common" }
nymsphinx = {path = "../common/nymsphinx" }
pemstore = {path = "../common/pemstore"}
topology = {path = "../common/topology"}
version-checker = { path = "../common/version-checker" }
[dev-dependencies]
tempfile = "3.1.0"
+22 -3
View File
@@ -14,13 +14,15 @@
use crate::commands::override_config;
use crate::config::persistence::pathfinder::MixNodePathfinder;
use crate::config::Config;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use crypto::asymmetric::encryption;
use crypto::asymmetric::{encryption, identity};
use directory_client::DirectoryClient;
use log::*;
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
use std::convert::TryInto;
use std::process;
use tokio::runtime::Runtime;
use topology::NymTopology;
@@ -134,7 +136,13 @@ pub fn execute(matches: &ArgMatches) {
rt.block_on(async {
let id = matches.value_of("id").unwrap();
println!("Initialising mixnode {}...", id);
let mut config = crate::config::Config::new(id);
if Config::default_config_file_path(id).exists() {
eprintln!("Mixnode \"{}\" was already initialised before! If you wanted to upgrade your node to most recent version, try `upgrade` command instead!", id);
process::exit(1);
}
let mut config = Config::new(id);
config = override_config(config, matches);
let layer = choose_layer(matches, config.get_presence_directory_server()).await;
// TODO: I really don't like how we override config and are presumably done with it
@@ -142,8 +150,18 @@ pub fn execute(matches: &ArgMatches) {
config = config.with_layer(layer);
debug!("Choosing layer {}", config.get_layer());
let identity_keys = identity::KeyPair::new();
let sphinx_keys = encryption::KeyPair::new();
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(
@@ -152,7 +170,8 @@ pub fn execute(matches: &ArgMatches) {
),
)
.expect("Failed to save sphinx keys");
println!("Saved mixnet sphinx keypair");
println!("Saved mixnet identity and sphinx keypairs");
let config_save_location = config.get_config_file_save_location();
config
.save_to_file(None)
+3 -2
View File
@@ -16,8 +16,9 @@ use crate::config::Config;
use clap::ArgMatches;
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
pub mod init;
pub mod run;
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
let max_layer = DEFAULT_NUM_MIX_HOPS;
+16 -2
View File
@@ -17,7 +17,7 @@ use crate::config::{persistence::pathfinder::MixNodePathfinder, Config};
use crate::node::MixNode;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use crypto::asymmetric::encryption;
use crypto::asymmetric::{encryption, identity};
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("run")
@@ -95,6 +95,19 @@ fn special_addresses() -> Vec<&'static str> {
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
}
fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair {
let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
))
.expect("Failed to read stored identity key files");
println!(
"Public identity key: {}\n",
identity_keypair.public_key().to_base58_string()
);
identity_keypair
}
fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair {
let sphinx_keypair: encryption::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
@@ -120,6 +133,7 @@ pub fn execute(matches: &ArgMatches) {
config = override_config(config, matches);
let pathfinder = MixNodePathfinder::new_from_config(&config);
let identity_keypair = load_identity_keys(&pathfinder);
let sphinx_keypair = load_sphinx_keys(&pathfinder);
let listening_ip_string = config.get_listening_address().ip().to_string();
@@ -145,5 +159,5 @@ pub fn execute(matches: &ArgMatches) {
config.get_announce_address()
);
MixNode::new(config, sphinx_keypair).run();
MixNode::new(config, identity_keypair, sphinx_keypair).run();
}
+198
View File
@@ -0,0 +1,198 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::config::{missing_string_value, Config};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use crypto::asymmetric::identity;
use std::fmt::Display;
use std::path::PathBuf;
use std::process;
use version_checker::{parse_version, Version};
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade mixnode from {} to {} ...",
from, to
);
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!(
"Upgrade from {} to {} failed!\n==================\n",
from, to
);
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"Upgrade from {} to {} was successful!\n==================\n",
from, to
);
}
fn pre_090_upgrade(from: &str, config: Config) -> Config {
// note: current is guaranteed to not have any `build` information suffix (nor pre-release
// information), as this was asserted at the beginning of this command)
//
// upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate
// step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0)
// this way we don't need to have all the crazy paths on how to upgrade from any version to any
// other version. We just upgrade one minor version at a time.
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let to_version = if current.major == 0 && current.minor == 9 {
current
} else {
Version::new(0, 9, 0)
};
print_start_upgrade(&from, &to_version);
// this is not extracted to separate function as you only have to manually pass version
// if upgrading from pre090 version
let from = match from.strip_prefix("v") {
Some(stripped) => stripped,
None => from,
};
let from = match from.strip_prefix("V") {
Some(stripped) => stripped,
None => from,
};
let from_version = parse_version(from).expect("invalid version provided!");
if from_version.major == 0 && from_version.minor < 8 {
// technically this could be implemented, but is there any point in that?
eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead");
print_failed_upgrade(&from_version, &to_version);
process::exit(1)
}
if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 {
eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version");
print_failed_upgrade(&from_version, &to_version);
process::exit(1)
}
if config.get_private_identity_key_file() != missing_string_value::<PathBuf>()
|| config.get_public_identity_key_file() != missing_string_value::<PathBuf>()
{
eprintln!("existing config seems to have specified identity keys which were only introduced in 0.9.0! Can't perform upgrade.");
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
}
let mut upgraded_config = config.with_custom_version(to_version.to_string().as_ref());
println!("Generating new identity...");
let identity_keys = identity::KeyPair::new();
upgraded_config.set_default_identity_keypair_paths();
if let Err(err) = pemstore::store_keypair(
&identity_keys,
&pemstore::KeyPairPath::new(
upgraded_config.get_private_identity_key_file(),
upgraded_config.get_public_identity_key_file(),
),
) {
eprintln!("Failed to save new identity key files! - {}", err);
process::exit(1);
}
// TODO: THIS IS INCOMPLETE AS ONCE PRESENCE IS REMOVED IN 0.9.0 IT WILL ALSO NEED
// TO BE PURGED FROM CONFIG
upgraded_config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
});
print_successful_upgrade(from_version, to_version);
upgraded_config
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the mixnode")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-mixnode we want to upgrade")
.takes_value(true)
.required(true),
)
// the rest of arguments depend on the upgrade path
.arg(Arg::with_name("current version")
.long("current-version")
.help("REQUIRED FOR PRE-0.9.0 UPGRADES. Self provided version of the nym-mixnode if none is available in the config. NOTE: if provided incorrectly, results may be catastrophic.")
.takes_value(true)
)
}
pub fn execute(matches: &ArgMatches) {
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
// technically this is not a correct way of checking it as a released version might contain valid build identifiers
// however, we are not using them ourselves at the moment and hence it should be fine.
// if we change our mind, we could easily tweak this code
if current.is_prerelease() || !current.build.is_empty() {
eprintln!(
"Trying to upgrade to a non-released version {}. This is not supported!",
current
);
process::exit(1)
}
let id = matches.value_of("id").unwrap();
let mut existing_config = Config::load_from_file(None, Some(id)).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
process::exit(1)
});
// versions fields were added in 0.9.0
if existing_config.get_version() == missing_string_value::<String>() {
let self_reported_version = matches.value_of("current version").unwrap_or_else(|| {
eprintln!(
"trying to upgrade from pre v0.9.0 without providing current system version!"
);
process::exit(1)
});
// upgrades up to 0.9.0
existing_config = pre_090_upgrade(self_reported_version, existing_config);
}
let config_version = Version::parse(existing_config.get_version()).unwrap_or_else(|err| {
eprintln!("failed to parse node version! - {:?}", err);
process::exit(1)
});
if config_version.is_prerelease() || !config_version.build.is_empty() {
eprintln!(
"Trying to upgrade to from non-released version {}. This is not supported!",
current
);
process::exit(1)
}
// here be upgrade path to 0.10.0 and beyond based on version number from config
if config_version == current {
println!("You're using the most recent version!");
} else {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current);
process::exit(1)
}
}
+175 -44
View File
@@ -15,28 +15,32 @@
use crate::config::template::config_template;
use config::NymConfig;
use log::*;
use serde::{Deserialize, Serialize};
use serde::{
de::{self, IntoDeserializer, Visitor},
Deserialize, Deserializer, Serialize,
};
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::str::FromStr;
use std::time;
use std::time::Duration;
pub mod persistence;
mod template;
pub(crate) const MISSING_VALUE: &str = "MISSING VALUE";
// 'MIXNODE'
const DEFAULT_LISTENING_PORT: u16 = 1789;
const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 10_000; // 10s
const DEFAULT_METRICS_SENDING_DELAY: u64 = 5_000; // 10s
const DEFAULT_METRICS_RUNNING_STATS_LOGGING_DELAY: u64 = 60_000; // 1min
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s
const DEFAULT_CACHE_ENTRY_TTL: u64 = 30_000;
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
const DEFAULT_METRICS_SENDING_DELAY: Duration = Duration::from_millis(5_000);
const DEFAULT_METRICS_RUNNING_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_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_CACHE_ENTRY_TTL: Duration = Duration::from_millis(30_000);
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
@@ -54,10 +58,6 @@ impl NymConfig for Config {
config_template()
}
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
@@ -84,6 +84,57 @@ impl NymConfig for Config {
}
}
// custom function is defined to deserialize based on whether field contains a pre 0.9.0
// u64 interpreted as milliseconds or proper duration introduced in 0.9.0
//
// TODO: when we get to refactoring down the line, this code can just be removed
// and all Duration fields could just have #[serde(with = "humantime_serde")] instead
// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have,
// for argument sake, 0.11.0 out
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
struct DurationVisitor;
impl<'de> Visitor<'de> for DurationVisitor {
type Value = Duration;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("u64 or a duration")
}
fn visit_i64<E>(self, value: i64) -> Result<Duration, E>
where
E: de::Error,
{
self.visit_u64(value as u64)
}
fn visit_u64<E>(self, value: u64) -> Result<Duration, E>
where
E: de::Error,
{
Ok(Duration::from_millis(Deserialize::deserialize(
value.into_deserializer(),
)?))
}
fn visit_str<E>(self, value: &str) -> Result<Duration, E>
where
E: de::Error,
{
humantime_serde::deserialize(value.into_deserializer())
}
}
deserializer.deserialize_any(DurationVisitor)
}
pub fn missing_string_value<T: From<String>>() -> T {
MISSING_VALUE.to_string().into()
}
impl Config {
pub fn new<S: Into<String>>(id: S) -> Self {
Config::default().with_id(id)
@@ -92,6 +143,20 @@ impl Config {
// builder methods
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self {
let id = id.into();
if self
.mixnode
.private_identity_key_file
.as_os_str()
.is_empty()
{
self.mixnode.private_identity_key_file =
self::MixNode::default_private_identity_key_file(&id);
}
if self.mixnode.public_identity_key_file.as_os_str().is_empty() {
self.mixnode.public_identity_key_file =
self::MixNode::default_public_identity_key_file(&id);
}
if self.mixnode.private_sphinx_key_file.as_os_str().is_empty() {
self.mixnode.private_sphinx_key_file =
self::MixNode::default_private_sphinx_key_file(&id);
@@ -100,6 +165,7 @@ impl Config {
self.mixnode.public_sphinx_key_file =
self::MixNode::default_public_sphinx_key_file(&id);
}
self.mixnode.id = id;
self
}
@@ -198,6 +264,11 @@ impl Config {
self
}
pub fn with_custom_version(mut self, version: &str) -> Self {
self.mixnode.version = version.to_string();
self
}
// getters
pub fn get_config_file_save_location(&self) -> PathBuf {
self.config_directory().join(Self::config_file_name())
@@ -207,6 +278,14 @@ impl Config {
self.mixnode.location.clone()
}
pub fn get_private_identity_key_file(&self) -> PathBuf {
self.mixnode.private_identity_key_file.clone()
}
pub fn get_public_identity_key_file(&self) -> PathBuf {
self.mixnode.public_identity_key_file.clone()
}
pub fn get_private_sphinx_key_file(&self) -> PathBuf {
self.mixnode.private_sphinx_key_file.clone()
}
@@ -219,20 +298,20 @@ impl Config {
self.mixnode.presence_directory_server.clone()
}
pub fn get_presence_sending_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.presence_sending_delay)
pub fn get_presence_sending_delay(&self) -> Duration {
self.debug.presence_sending_delay
}
pub fn get_metrics_directory_server(&self) -> String {
self.mixnode.metrics_directory_server.clone()
}
pub fn get_metrics_sending_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.metrics_sending_delay)
pub fn get_metrics_sending_delay(&self) -> Duration {
self.debug.metrics_sending_delay
}
pub fn get_metrics_running_stats_logging_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.metrics_running_stats_logging_delay)
pub fn get_metrics_running_stats_logging_delay(&self) -> Duration {
self.debug.metrics_running_stats_logging_delay
}
pub fn get_layer(&self) -> u64 {
@@ -247,20 +326,32 @@ impl Config {
self.mixnode.announce_address.clone()
}
pub fn get_packet_forwarding_initial_backoff(&self) -> time::Duration {
time::Duration::from_millis(self.debug.packet_forwarding_initial_backoff)
pub fn get_packet_forwarding_initial_backoff(&self) -> Duration {
self.debug.packet_forwarding_initial_backoff
}
pub fn get_packet_forwarding_maximum_backoff(&self) -> time::Duration {
time::Duration::from_millis(self.debug.packet_forwarding_maximum_backoff)
pub fn get_packet_forwarding_maximum_backoff(&self) -> Duration {
self.debug.packet_forwarding_maximum_backoff
}
pub fn get_initial_connection_timeout(&self) -> time::Duration {
time::Duration::from_millis(self.debug.initial_connection_timeout)
pub fn get_initial_connection_timeout(&self) -> Duration {
self.debug.initial_connection_timeout
}
pub fn get_cache_entry_ttl(&self) -> time::Duration {
time::Duration::from_millis(self.debug.cache_entry_ttl)
pub fn get_cache_entry_ttl(&self) -> Duration {
self.debug.cache_entry_ttl
}
pub fn get_version(&self) -> &str {
&self.mixnode.version
}
// upgrade-specific
pub(crate) fn set_default_identity_keypair_paths(&mut self) {
self.mixnode.private_identity_key_file =
self::MixNode::default_private_identity_key_file(&self.mixnode.id);
self.mixnode.public_identity_key_file =
self::MixNode::default_public_identity_key_file(&self.mixnode.id);
}
}
@@ -268,6 +359,7 @@ impl Config {
#[serde(deny_unknown_fields)]
pub struct MixNode {
/// Version of the mixnode for which this configuration was created.
#[serde(default = "missing_string_value")]
version: String,
/// ID specifies the human readable ID of this particular mixnode.
@@ -293,6 +385,14 @@ pub struct MixNode {
/// `listening_address`.
announce_address: String,
/// Path to file containing private identity key.
#[serde(default = "missing_string_value")]
private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
#[serde(default = "missing_string_value")]
public_identity_key_file: PathBuf,
/// Path to file containing private sphinx key.
private_sphinx_key_file: PathBuf,
@@ -313,12 +413,20 @@ pub struct MixNode {
}
impl MixNode {
fn default_private_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(id).join("private_identity.pem")
}
fn default_public_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(id).join("public_identity.pem")
}
fn default_private_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("private_sphinx.pem")
Config::default_data_directory(id).join("private_sphinx.pem")
}
fn default_public_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(Some(id)).join("public_sphinx.pem")
Config::default_data_directory(id).join("public_sphinx.pem")
}
fn default_location() -> String {
@@ -337,6 +445,8 @@ impl Default for MixNode {
.parse()
.unwrap(),
announce_address: format!("127.0.0.1:{}", DEFAULT_LISTENING_PORT),
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_sphinx_key_file: Default::default(),
public_sphinx_key_file: Default::default(),
presence_directory_server: DEFAULT_DIRECTORY_SERVER.to_string(),
@@ -360,34 +470,55 @@ impl Default for Logging {
#[serde(default, deny_unknown_fields)]
pub struct Debug {
/// Delay between each subsequent presence data being sent.
/// The provided value is interpreted as milliseconds.
presence_sending_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
presence_sending_delay: Duration,
/// Delay between each subsequent metrics data being sent.
/// The provided value is interpreted as milliseconds.
metrics_sending_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
metrics_sending_delay: Duration,
/// Delay between each subsequent running metrics statistics being logged.
/// The provided value is interpreted as milliseconds.
metrics_running_stats_logging_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
metrics_running_stats_logging_delay: Duration,
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
/// The provided value is interpreted as milliseconds.
packet_forwarding_initial_backoff: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
/// The provided value is interpreted as milliseconds.
packet_forwarding_maximum_backoff: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
/// The provider value is interpreted as milliseconds.
initial_connection_timeout: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
initial_connection_timeout: Duration,
/// Duration for which a cached vpn processing result is going to get stored for.
/// The provided value is interpreted as milliseconds.
cache_entry_ttl: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
cache_entry_ttl: Duration,
}
impl Default for Debug {
+12 -2
View File
@@ -17,7 +17,8 @@ use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct MixNodePathfinder {
config_dir: PathBuf,
identity_private_key: PathBuf,
identity_public_key: PathBuf,
private_sphinx_key: PathBuf,
public_sphinx_key: PathBuf,
}
@@ -25,12 +26,21 @@ pub struct MixNodePathfinder {
impl MixNodePathfinder {
pub fn new_from_config(config: &Config) -> Self {
MixNodePathfinder {
config_dir: config.get_config_file_save_location(),
identity_private_key: config.get_private_identity_key_file(),
identity_public_key: config.get_public_identity_key_file(),
private_sphinx_key: config.get_private_sphinx_key_file(),
public_sphinx_key: config.get_public_sphinx_key_file(),
}
}
pub fn private_identity_key(&self) -> &Path {
&self.identity_private_key
}
pub fn public_identity_key(&self) -> &Path {
&self.identity_public_key
}
pub fn private_encryption_key(&self) -> &Path {
&self.private_sphinx_key
}
+6
View File
@@ -42,6 +42,12 @@ layer = {{ mixnode.layer }}
# Socket address to which this mixnode will bind to and will be listening for packets.
listening_address = '{{ mixnode.listening_address }}'
# Path to file containing private identity key.
private_identity_key_file = '{{ mixnode.private_identity_key_file }}'
# Path to file containing public identity key.
public_identity_key_file = '{{ mixnode.public_identity_key_file }}'
# Path to file containing private identity key.
private_sphinx_key_file = '{{ mixnode.private_sphinx_key_file }}'
+2
View File
@@ -29,6 +29,7 @@ fn main() {
.about("Implementation of the Loopix-based Mixnode")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
@@ -38,6 +39,7 @@ fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
+9 -2
View File
@@ -16,7 +16,7 @@ use crate::config::Config;
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
use crate::node::listener::connection_handler::ConnectionHandler;
use crate::node::listener::Listener;
use crypto::asymmetric::encryption;
use crypto::asymmetric::{encryption, identity};
use directory_client::DirectoryClient;
use log::*;
use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder};
@@ -30,13 +30,20 @@ mod presence;
// the MixNode will live for whole duration of this program
pub struct MixNode {
config: Config,
#[allow(dead_code)]
identity_keypair: Arc<identity::KeyPair>,
sphinx_keypair: Arc<encryption::KeyPair>,
}
impl MixNode {
pub fn new(config: Config, sphinx_keypair: encryption::KeyPair) -> Self {
pub fn new(
config: Config,
identity_keypair: identity::KeyPair,
sphinx_keypair: encryption::KeyPair,
) -> Self {
MixNode {
config,
identity_keypair: Arc::new(identity_keypair),
sphinx_keypair: Arc::new(sphinx_keypair),
}
}
-29
View File
@@ -1,29 +0,0 @@
[package]
name = "nym-validator"
version = "0.9.0-dev"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
abci = "0.6.4"
bodyparser = "0.8.0"
byteorder = "1.3.2"
clap = "2.33.0"
dirs = "2.0.2"
dotenv = "0.15.0"
futures = "0.3.1"
iron = "0.6.1"
log = "0.4"
pretty_env_logger = "0.3"
router = "0.6.0"
serde = "1.0.104"
serde_json = "1.0.48"
tokio = { version = "0.2", features = ["full"] }
## internal
config = { path = "../common/config" }
[dev-dependencies]
tempfile = "3.1.0"
-21
View File
@@ -1,21 +0,0 @@
Nym Validator
=============
The Nym Validator has several jobs:
* use Tendermint (v0.33.0) to maintain a total global ordering of incoming transactions
* rewards + stake slashing based quality of service measurements for mixnet nodes (aka "mixmining")
* generate Coconut credentials and ensure they're not double spent
* maintain a decentralized directory of all Nym nodes that have staked into the system
Some of these functions may be moved away to their own node types in the future, for example to increase scalability or performance. At the moment, we'd like to keep deployments simple, so they're all in the validator node.
Running the validator on your local machine
-------------------------------------------
1. Download and install [Tendermint 0.32.7](https://github.com/tendermint/tendermint/releases/tag/v0.32.7)
2. `tendermint init` sets up Tendermint for use
3. `tendermint node` runs Tendermint. You'll get errors until you run the Nym validator, this is normal :).
4. `cp sample-configs/validator-config.toml.sample sample-configs/validator-config.toml`
5. `cargo run -- run --config ../sample-configs/validator-config.toml` builds the Nym Validator and runs it
-5
View File
@@ -1,5 +0,0 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"
View File
-58
View File
@@ -1,58 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("init")
.about("Initialise the validator")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-validator we want to create config for.")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("location")
.long("location")
.help("Optional geographical location of this node")
.takes_value(true),
)
.arg(
Arg::with_name("directory")
.long("directory")
.help("Address of the directory server the validator is sending presence to and uses for mix mining")
.takes_value(true),
)
}
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
println!("Initialising validator {}...", id);
let mut config = crate::config::Config::new(id);
config = override_config(config, matches);
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!("Validator configuration completed.\n\n\n")
}
-31
View File
@@ -1,31 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::config::Config;
use clap::ArgMatches;
pub mod init;
pub mod run;
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
if let Some(directory) = matches.value_of("directory") {
config = config.with_custom_directory(directory);
}
if let Some(location) = matches.value_of("location") {
config = config.with_location(location);
}
config
}
-66
View File
@@ -1,66 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Commenting config out temporarily, we'll undoubtedly need it back soon.
// use crate::commands::override_config;
// use crate::config::Config;
// use config::NymConfig;
use crate::validator::Validator;
use clap::{App, Arg, ArgMatches};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("run")
.about("Starts the validator")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-validator we want to run")
.takes_value(true)
.required(true),
)
// the rest of arguments are optional, they are used to override settings in config file
.arg(
Arg::with_name("location")
.long("location")
.help("Optional geographical location of this node")
.takes_value(true),
)
.arg(
Arg::with_name("config")
.long("config")
.help("Custom path to the nym-validator configuration file")
.takes_value(true),
)
.arg(
Arg::with_name("directory")
.long("directory")
.help("Address of the directory server the validator is sending presence to and uses for mix mining")
.takes_value(true),
)
}
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
println!("Starting validator {}...", id);
// let mut config =
// Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id))
// .expect("Failed to load config file");
// config = override_config(config, matches);
let validator = Validator::new();
validator.start()
}
-263
View File
@@ -1,263 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::config::template::config_template;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time;
mod template;
// where applicable, the below are defined in milliseconds
const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
// 'MIXMINING'
const DEFAULT_MIX_MINING_DELAY: u64 = 10_000;
const DEFAULT_MIX_MINING_RESOLUTION_TIMEOUT: u64 = 5_000;
const DEFAULT_MIX_MINING_CONNECTION_TIMEOUT: u64 = 1_500;
const DEFAULT_NUMBER_OF_MIX_MINING_TEST_PACKETS: u64 = 2;
// 'DEBUG'
const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 3000;
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
validator: Validator,
mix_mining: MixMining,
tendermint: Tendermint,
#[serde(default)]
logging: Logging,
#[serde(default)]
debug: Debug,
}
impl NymConfig for Config {
fn template() -> &'static str {
config_template()
}
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("validators")
}
fn root_directory(&self) -> PathBuf {
self.validator.nym_root_directory.clone()
}
fn config_directory(&self) -> PathBuf {
self.validator
.nym_root_directory
.join(&self.validator.id)
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.validator
.nym_root_directory
.join(&self.validator.id)
.join("data")
}
}
impl Config {
pub fn new<S: Into<String>>(id: S) -> Self {
Config::default().with_id(id)
}
// builder methods
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self {
let id = id.into();
// calls to any defaults requiring id (see: client, mixnode, provider):
self.validator.id = id;
self
}
pub fn with_custom_directory<S: Into<String>>(mut self, directory_server: S) -> Self {
let directory_server_string = directory_server.into();
self.debug.presence_directory_server = directory_server_string.clone();
self.mix_mining.directory_server = directory_server_string;
self
}
pub fn with_location<S: Into<String>>(mut self, location: S) -> Self {
self.validator.location = location.into();
self
}
// getters
pub fn get_config_file_save_location(&self) -> PathBuf {
self.config_directory().join(Self::config_file_name())
}
#[allow(dead_code)]
pub fn get_location(&self) -> String {
self.validator.location.clone()
}
// dead_code until validator actually sends the presence data
#[allow(dead_code)]
pub fn get_presence_directory_server(&self) -> String {
self.debug.presence_directory_server.clone()
}
#[allow(dead_code)]
pub fn get_presence_sending_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.presence_sending_delay)
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Validator {
/// ID specifies the human readable ID of this particular validator.
id: String,
/// Completely optional value specifying geographical location of this particular node.
/// Currently it's used entirely for debug purposes, as there are no mechanisms implemented
/// to verify correctness of the information provided. However, feel free to fill in
/// this field with as much accuracy as you wish to share.
location: String,
/// nym_home_directory specifies absolute path to the home nym MixNodes directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
}
impl Validator {
fn default_location() -> String {
"unknown".into()
}
}
impl Default for Validator {
fn default() -> Self {
Validator {
id: "".to_string(),
location: Self::default_location(),
nym_root_directory: Config::default_root_directory(),
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MixMining {
/// Directory server from which the validator will obtain initial topology.
directory_server: String,
/// The uniform delay every which validator are running their mix-mining procedure.
/// The provided value is interpreted as milliseconds.
run_delay: u64,
/// During the mix-mining process, test packets are sent through various network
/// paths. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
/// The provided value is interpreted as milliseconds.
resolution_timeout: u64,
/// Timeout for trying to establish connection to node endpoints.
/// The provided value is interpreted as milliseconds.
connection_timeout: u64,
/// How many packets should be sent through each path during the mix-mining procedure.
number_of_test_packets: u64,
}
impl Default for MixMining {
fn default() -> Self {
MixMining {
directory_server: "https://directory.nymtech.net".to_string(),
run_delay: DEFAULT_MIX_MINING_DELAY,
resolution_timeout: DEFAULT_MIX_MINING_RESOLUTION_TIMEOUT,
number_of_test_packets: DEFAULT_NUMBER_OF_MIX_MINING_TEST_PACKETS,
connection_timeout: DEFAULT_MIX_MINING_CONNECTION_TIMEOUT,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Tendermint {}
impl Default for Tendermint {
fn default() -> Self {
Tendermint {}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Logging {}
impl Default for Logging {
fn default() -> Self {
Logging {}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Debug {
/// Directory server to which the server will be reporting their presence data.
presence_directory_server: String,
/// Delay between each subsequent presence data being sent.
presence_sending_delay: u64,
}
impl Debug {}
impl Default for Debug {
fn default() -> Self {
Debug {
presence_directory_server: DEFAULT_DIRECTORY_SERVER.to_string(),
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
}
}
}
#[cfg(test)]
mod validator_config {
use super::*;
#[test]
fn after_saving_default_config_the_loaded_one_is_identical() {
// need to figure out how to do something similar but without touching the disk
// or the file system at all...
let temp_location = tempfile::tempdir().unwrap().path().join("config.toml");
let default_config = Config::default().with_id("foomp".to_string());
default_config
.save_to_file(Some(temp_location.clone()))
.unwrap();
let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap();
assert_eq!(default_config, loaded_config);
}
}
-81
View File
@@ -1,81 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs in mod.rs.
r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base mixnode config options #####
[validator]
# Human readable ID of this particular validator.
id = '{{ validator.id }}'
# Completely optional value specifying geographical location of this particular node.
# Currently it's used entirely for debug purposes, as there are no mechanisms implemented
# to verify correctness of the information provided. However, feel free to fill in
# this field with as much accuracy as you wish to share.
location = '{{ validator.location }}'
##### advanced configuration options #####
# nym_home_directory specifies absolute path to the home nym validators directory.
# It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory = '{{ validator.nym_root_directory }}'
##### mix mining config options #####
[mix_mining]
# Directory server from which the validator will obtain initial topology.
directory_server = '{{ mix_mining.directory_server }}'
# The uniform delay every which validator are running their mix-mining procedure.
# The provided value is interpreted as milliseconds.
run_delay = {{ mix_mining.run_delay }}
# During the mix-mining process, test packets are sent through various network
# paths. This timeout determines waiting period until it is decided that the packet
# did not reach its destination.
# The provided value is interpreted as milliseconds.
resolution_timeout = {{ mix_mining.resolution_timeout }}
# Timeout for trying to establish connection to node endpoints.
# The provided value is interpreted as milliseconds.
connection_timeout = {{ mix_mining.connection_timeout }}
# How many packets should be sent through each path during the mix-mining procedure.
number_of_test_packets = {{ mix_mining.number_of_test_packets }}
##### tendermint config options #####
[tendermint]
##### logging configuration options #####
[logging]
# TODO
"#
}
-84
View File
@@ -1,84 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use clap::{App, ArgMatches};
mod commands;
mod config;
mod network;
mod services;
mod validator;
fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
let arg_matches = App::new("Nym Validator")
.version(env!("CARGO_PKG_VERSION"))
.author("Nymtech")
.about("Implementation of Nym Validator")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.get_matches();
execute(arg_matches);
}
fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
_ => println!("{}", usage()),
}
}
fn usage() -> &'static str {
"usage: --help to see available options.\n\n"
}
fn banner() -> String {
format!(
r#"
_ __ _ _ _ __ ___
| '_ \| | | | '_ \ _ \
| | | | |_| | | | | | |
|_| |_|\__, |_| |_| |_|
|___/
(validator - version {:})
"#,
env!("CARGO_PKG_VERSION")
)
}
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.init();
}
@@ -1,15 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// placeholder for Ethereum / ERC20 bridge integration
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The `network` module provides interfaces to external systems via network
//! connectivity.
//!
pub mod rest;
pub mod tendermint;
@@ -1,59 +0,0 @@
use serde::{Deserialize, Serialize};
use super::*;
use bodyparser::Struct;
use iron::mime::Mime;
use iron::status;
use iron::Handler;
/// Holds data for a capacity update (json)
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Capacity {
value: usize,
}
pub struct Update {
service: Arc<Mutex<mixmining::Service>>,
}
impl Update {
pub fn new(service: Arc<Mutex<mixmining::Service>>) -> Update {
Update { service }
}
}
impl Handler for Update {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let json_parse = req.get::<Struct<Capacity>>();
match json_parse {
Ok(capacity) => {
let capacity = capacity.expect("Unexpected JSON parsing problem").value;
self.service.lock().unwrap().set_capacity(capacity);
Ok(Response::with(status::Created))
}
Err(err) => Ok(Response::with((status::BadRequest, err.detail))),
}
}
}
pub struct Get {
service: Arc<Mutex<mixmining::Service>>,
}
impl Get {
pub fn new(service: Arc<Mutex<mixmining::Service>>) -> Get {
Get { service }
}
}
impl Handler for Get {
fn handle(&self, _: &mut Request) -> IronResult<Response> {
let content_type = "application/json".parse::<Mime>().unwrap();
let value = self.service.lock().unwrap().capacity();
let c = Capacity { value };
let json = serde_json::to_string(&c).unwrap();
Ok(Response::with((content_type, status::Ok, json)))
}
}
-72
View File
@@ -1,72 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::services::mixmining;
use iron::prelude::*;
use presence::mixnode;
use presence::topology;
use router::Router;
use std::sync::{Arc, Mutex};
mod capacity;
mod presence;
mod staking;
pub struct Api {
mixmining_service: Arc<Mutex<mixmining::Service>>,
}
impl Api {
pub fn new(mixmining_service: mixmining::Service) -> Api {
let service = Arc::new(Mutex::new(mixmining_service));
Api {
mixmining_service: service,
}
}
/// Run the REST API.
pub async fn run(self) {
let port = 3000; // TODO: make this configurable
let address = format!("localhost:{}", port);
println!("* starting REST API on http://{}", address);
let router = self.setup_router();
Iron::new(router).http(address).unwrap();
}
/// Tie together URL route paths with handler functions.
fn setup_router(self) -> Router {
// define a Router to hold our routes
let mut router = Router::new();
// set up handlers
let capacity_update = capacity::Update::new(Arc::clone(&self.mixmining_service));
let capacity_get = capacity::Get::new(Arc::clone(&self.mixmining_service));
let presence_mixnode_create =
mixnode::CreatePresence::new(Arc::clone(&self.mixmining_service));
let topology_get = topology::GetTopology::new(Arc::clone(&self.mixmining_service));
// tie routes to handlers
router.get("/capacity", capacity_get, "capacity_get");
router.post("/capacity", capacity_update, "capacity_update");
router.get("/topology", topology_get, "topology_get");
router.post(
"/presence/mixnodes",
presence_mixnode_create,
"presence_mixnodes_post",
);
router
}
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod presence;
@@ -1,50 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use serde::{Deserialize, Serialize};
// Topology shows us the current state of the overall Nym network
#[derive(Serialize, Deserialize, Debug)]
pub struct Topology {
pub validators: Vec<Validator>,
pub mix_nodes: Vec<MixNode>,
pub service_providers: Vec<ServiceProvider>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Validator {
host: String,
public_key: String,
version: String,
last_seen: u64,
location: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MixNode {
host: String,
public_key: String,
version: String,
last_seen: u64,
location: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ServiceProvider {
host: String,
public_key: String,
version: String,
last_seen: u64,
location: String,
}
@@ -1,135 +0,0 @@
use super::models::Timestamp;
use crate::network::rest::presence::models::Mixnode as RestMixnode;
use crate::network::rest::presence::models::Topology as RestTopology;
use crate::services::mixmining::models::Mixnode as ServiceMixnode;
use crate::services::mixmining::models::Topology as ServiceTopology;
use std::convert::From;
impl ServiceMixnode {
pub fn from_rest_mixnode_with_timestamp(
rest_mix: RestMixnode,
timestamp: Timestamp,
) -> ServiceMixnode {
ServiceMixnode {
host: rest_mix.host,
last_seen: timestamp.into(),
location: rest_mix.location,
public_key: rest_mix.public_key,
stake: 0,
version: rest_mix.version,
}
}
}
impl From<ServiceMixnode> for RestMixnode {
fn from(value: ServiceMixnode) -> RestMixnode {
RestMixnode {
host: value.host,
location: value.location,
public_key: value.public_key,
version: value.version,
}
}
}
impl ServiceTopology {
#[cfg(test)] // un-testify this when you need it for real code, this kills warning
pub fn from_rest_topology_with_timestamp(
rest_topology: RestTopology,
timestamp: Timestamp,
) -> ServiceTopology {
let mut converted_mixnodes: Vec<ServiceMixnode> = Vec::new();
for mixnode in rest_topology.mixnodes {
converted_mixnodes.push(ServiceMixnode::from_rest_mixnode_with_timestamp(
mixnode, timestamp,
));
}
ServiceTopology {
mixnodes: converted_mixnodes.to_vec(),
service_providers: vec![], // add these when conversions exist
validators: vec![], // add these when conversions exist
}
}
}
impl From<ServiceTopology> for RestTopology {
fn from(value: ServiceTopology) -> RestTopology {
let mut converted_mixnodes: Vec<RestMixnode> = Vec::new();
for mixnode in value.mixnodes {
converted_mixnodes.push(mixnode.into());
}
RestTopology {
mixnodes: converted_mixnodes.to_vec(),
service_providers: vec![], // add these when conversions exist
validators: vec![], // add these when conversions exist
}
}
}
#[cfg(test)]
mod test_presence_conversions_for_mixmining_service {
fn rest_mixnode_fixture() -> RestMixnode {
RestMixnode {
host: "foo.org".to_owned(),
public_key: "abc".to_owned(),
location: "London".to_owned(),
version: "1.0.0".to_owned(),
}
}
fn service_mixnode_fixture() -> ServiceMixnode {
ServiceMixnode {
host: "foo.org".to_owned(),
public_key: "abc".to_owned(),
last_seen: 1234,
location: "London".to_owned(),
stake: 0,
version: "1.0.0".to_owned(),
}
}
use super::*;
#[test]
fn test_building_service_mixnode_from_rest_mixnode() {
let rest_mixnode = rest_mixnode_fixture();
let timestamp = Timestamp::default();
let service_mixnode =
ServiceMixnode::from_rest_mixnode_with_timestamp(rest_mixnode.clone(), timestamp);
assert_eq!(service_mixnode.host, rest_mixnode.host);
assert_eq!(service_mixnode.public_key, rest_mixnode.public_key);
assert_eq!(service_mixnode.location, rest_mixnode.location);
assert_eq!(service_mixnode.stake, 0);
assert_eq!(service_mixnode.version, rest_mixnode.version);
// I'm not going to test the last_seen timestamp as I can't be bothered
// setting up a fake clock right now.
// The behaviour is: it should set time to SystemTime::now().
}
#[test]
fn test_building_rest_mixnode_from_service_mixnode() {
let service_mixnode = service_mixnode_fixture();
let rest_mixnode = RestMixnode::from(service_mixnode.clone());
assert_eq!(rest_mixnode.host, service_mixnode.host);
assert_eq!(rest_mixnode.public_key, service_mixnode.public_key);
assert_eq!(rest_mixnode.location, service_mixnode.location);
assert_eq!(rest_mixnode.version, service_mixnode.version);
}
#[test]
fn test_building_service_topology_from_rest_topology() {
let rest_mixnode = rest_mixnode_fixture();
let rest_topology = RestTopology {
mixnodes: vec![rest_mixnode.clone()],
service_providers: vec![],
validators: vec![],
};
let timestamp = Timestamp::default();
let service_topology =
ServiceTopology::from_rest_topology_with_timestamp(rest_topology, timestamp);
let service_mixnode =
ServiceMixnode::from_rest_mixnode_with_timestamp(rest_mixnode, timestamp);
assert_eq!(service_mixnode, service_topology.mixnodes[0]);
}
}
@@ -1,38 +0,0 @@
use super::*;
use crate::network::rest::presence::models::Mixnode as PresenceMixnode;
use crate::services::mixmining::models::Mixnode as ServiceMixnode;
use bodyparser::Struct;
use iron::status;
use iron::Handler;
use models::Timestamp;
pub struct CreatePresence {
service: Arc<Mutex<mixmining::Service>>,
}
impl CreatePresence {
pub fn new(service: Arc<Mutex<mixmining::Service>>) -> CreatePresence {
CreatePresence { service }
}
}
impl Handler for CreatePresence {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let json_parse = req.get::<Struct<PresenceMixnode>>();
match json_parse {
Ok(mixnode) => {
let mixnode = mixnode.expect("Unexpected JSON parsing problem");
self.service
.lock()
.unwrap()
.add(ServiceMixnode::from_rest_mixnode_with_timestamp(
mixnode,
Timestamp::default(),
));
Ok(Response::with(status::Created))
}
Err(err) => Ok(Response::with((status::BadRequest, err.detail))),
}
}
}
@@ -1,6 +0,0 @@
use super::*;
mod conversions;
pub mod mixnode;
mod models;
pub mod topology;
@@ -1,66 +0,0 @@
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Mixnode {
pub host: String,
pub public_key: String,
pub version: String,
pub location: String,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ServiceProvider {
host: String,
public_key: String,
version: String,
last_seen: u64,
location: String,
}
/// Topology shows us the current state of the overall Nym network
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Topology {
pub mixnodes: Vec<Mixnode>,
pub service_providers: Vec<ServiceProvider>,
pub validators: Vec<Validator>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Validator {
host: String,
public_key: String,
version: String,
last_seen: u64,
location: String,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
pub struct Timestamp(u64);
impl Default for Timestamp {
fn default() -> Timestamp {
Timestamp(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
)
}
}
impl From<u64> for Timestamp {
fn from(v: u64) -> Timestamp {
Timestamp(v)
}
}
impl Into<u64> for Timestamp {
fn into(self) -> u64 {
self.0
}
}
@@ -1,23 +0,0 @@
use super::*;
use iron::status;
use iron::Handler;
pub struct GetTopology {
service: Arc<Mutex<mixmining::Service>>,
}
impl GetTopology {
pub fn new(service: Arc<Mutex<mixmining::Service>>) -> GetTopology {
GetTopology { service }
}
}
impl Handler for GetTopology {
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
println!("Getting topology!...");
let service_topology = self.service.lock().unwrap().topology();
let topology = models::Topology::from(service_topology);
let response = serde_json::to_string_pretty(&topology).unwrap();
Ok(Response::with((status::Ok, response)))
}
}
-18
View File
@@ -1,18 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use iron::prelude::*;
use iron::status;
pub mod topology;
@@ -1,26 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::*;
use crate::network::rest::models::presence::{MixNode, ServiceProvider, Topology, Validator};
pub fn get(_req: &mut Request) -> IronResult<Response> {
let topology = Topology {
mix_nodes: Vec::<MixNode>::new(),
service_providers: Vec::<ServiceProvider>::new(),
validators: Vec::<Validator>::new(),
};
let response = serde_json::to_string_pretty(&topology).unwrap();
Ok(Response::with((status::Ok, response)))
}
@@ -1 +0,0 @@
// pub struct StakeUpdate {}
-82
View File
@@ -1,82 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use abci::*;
use byteorder::{BigEndian, ByteOrder};
// Convert incoming tx network data to the proper BigEndian size. txs.len() > 8 will return 0
fn convert_tx(tx: &[u8]) -> u64 {
if tx.len() < 8 {
let pad = 8 - tx.len();
let mut x = vec![0; pad];
x.extend_from_slice(tx);
return BigEndian::read_u64(x.as_slice());
}
BigEndian::read_u64(tx)
}
pub struct Abci {
count: u64,
}
impl Abci {
pub fn new() -> Abci {
Abci { count: 0 }
}
pub async fn run(self) {
println!("* starting Tendermint abci");
abci::run_local(self);
}
}
impl abci::Application for Abci {
// Validate transactions. Rule: Transactions must be incremental: 1,2,3,4...
fn check_tx(&mut self, req: &RequestCheckTx) -> ResponseCheckTx {
// Get the Tx [u8] and convert to u64
let c = convert_tx(req.get_tx());
let mut response = ResponseCheckTx::new();
// Validation logic
if c != self.count + 1 {
response.set_code(1);
response.set_log(String::from("Count must be incremental!"));
return response;
}
// Update state to keep state correct for next check_tx call
self.count = c;
response
}
fn deliver_tx(&mut self, req: &RequestDeliverTx) -> ResponseDeliverTx {
// Get the Tx [u8]
let c = convert_tx(req.get_tx());
// Update state
self.count = c;
// Return default code 0 == bueno
ResponseDeliverTx::new()
}
fn commit(&mut self, _req: &RequestCommit) -> ResponseCommit {
// Create the response
let mut response = ResponseCommit::new();
// Convert count to bits
let mut buf = [0; 8];
BigEndian::write_u64(&mut buf, self.count);
// Set data so last state is included in the block
response.set_data(buf.to_vec());
response
}
}
-1
View File
@@ -1 +0,0 @@
-105
View File
@@ -1,105 +0,0 @@
use super::Mixnode;
/// A (currently RAM-based) data store to keep tabs on which nodes have what
/// stake assigned to them.
#[derive(Clone, Debug, PartialEq)]
pub struct MixminingDb {
mixnodes: Vec<Mixnode>,
capacity: usize,
}
impl MixminingDb {
pub fn new() -> MixminingDb {
let mixnodes = Vec::<Mixnode>::new();
MixminingDb {
capacity: 6,
mixnodes,
}
}
pub fn add(&mut self, mixnode: Mixnode) {
self.mixnodes.push(mixnode);
}
pub fn get_mixnodes(&self) -> &Vec<Mixnode> {
&self.mixnodes
}
pub fn set_capacity(&mut self, capacity: usize) {
self.capacity = capacity;
}
pub fn capacity(&self) -> usize {
self.capacity
}
}
#[cfg(test)]
mod capacity {
use super::*;
#[test]
fn starts_at_6() {
let db = MixminingDb::new();
assert_eq!(6, db.capacity());
}
#[test]
fn setting_and_getting_work() {
let mut db = MixminingDb::new();
db.set_capacity(1);
assert_eq!(1, db.capacity());
}
}
#[cfg(test)]
mod adding_and_retrieving_mixnodes {
use super::*;
#[test]
fn add_and_retrieve_one_works() {
let node = fake_mixnode("London, UK");
let mut db = MixminingDb::new();
db.add(node.clone());
assert_eq!(&node, db.get_mixnodes().first().unwrap());
}
#[test]
fn add_and_retrieve_two_works() {
let node1 = fake_mixnode("London, UK");
let node2 = fake_mixnode("Neuchatel");
let mut db = MixminingDb::new();
db.add(node1.clone());
db.add(node2.clone());
assert_eq!(node1, db.get_mixnodes()[0]);
assert_eq!(node2, db.get_mixnodes()[1]);
}
#[test]
fn starts_empty() {
let db = MixminingDb::new();
assert_eq!(0, db.mixnodes.len());
}
#[test]
fn calling_list_when_empty_returns_empty_vec() {
let db = MixminingDb::new();
let empty: Vec<Mixnode> = vec![];
assert_eq!(&empty, db.get_mixnodes());
}
fn fake_mixnode(location: &str) -> Mixnode {
Mixnode {
host: String::from("foo.com"),
last_seen: 123,
location: String::from(location),
public_key: String::from("abc123"),
stake: 8,
version: String::from("1.0"),
}
}
}
-176
View File
@@ -1,176 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use db::MixminingDb;
use models::*;
pub mod db;
pub mod models;
mod tests;
pub struct Service {
db: MixminingDb,
}
/// The mixmining::Service provides logic for updating and slashing mixnode
/// stake, retrieving lists of mixnodes based on stake, and adding/removing
/// mixnodes from the active set. It monitors mixnodes and rewards or slashes
/// based on the observed quality of service provided by a given mixnode.
///
/// Mixing and staking interact. Mixnodes first need to announce
/// their presence to the validators.
///
/// The mixnode then goes into the stack of available mixnodes.
///
/// However, it's not necessarily going to start actively mixing traffic.
/// That depends on how much stake is riding on it, and how much capacity the
/// network requires right now. We depend on the wisdom of stakers to put their
/// money on trustworthy mixnodes.
///
/// The active set of mixnodes will be able to expand or contract based on capacity.
/// For now, we simply take the top <capacity> nodes available, ordered by
/// <node.stake desc>.
///
/// A lot is going to need to change here. Commented code is here mainly to
/// quickly sketch out the guts of the mixmining and staking service. This is not the basis
/// of our real staking system quite yet - it's a way to start getting the system
/// to function with all the different node types to start talking to each other,
/// and will be dramatically reworked over the next few months.
impl Service {
pub fn new(db: MixminingDb) -> Service {
Service { db }
}
// Add a mixnode so that it becomes part of the possible mixnode set.
pub fn add(&mut self, mixnode: Mixnode) {
self.db.add(mixnode);
}
pub fn topology(&self) -> Topology {
let mixnodes = self.db.get_mixnodes();
let service_providers: Vec<ServiceProvider> = vec![];
let validators: Vec<Validator> = vec![];
Topology::new(mixnodes.to_vec(), service_providers, validators)
}
pub fn set_capacity(&mut self, capacity: usize) {
self.db.set_capacity(capacity);
}
/// A fake capacity, so we can take the top n mixnodes based on stake
pub fn capacity(&self) -> usize {
self.db.capacity()
}
/*
/// Update (or create) a given mixnode stake, identified by the mixnode's public key
fn update(&self, public_key: &str, amount: u64) {
// retrieve the given Mixnode from the database and update its stake
}
/// For now, we have no notion of measuring capacity. For now just use capacity().
fn active_mixnodes(&self) -> Vec<Mixnode> {
Vec::<Mixnode>::new()
// hit the database
}
/// Remove a mixnode from the active set in a way that does not impact its stake.
/// In a more built-out system, this method would mean:
/// "mixnode x has done its job well and requested to leave, so it can be removed
/// at the end of an epoch."
fn remove(&self, public_key: &str) {
// free locked up stake back to originating stakeholder
// remove the mixnode from the database
}
/// Add the given amount of stake to the given Mixnode. Presumably it has done
/// its job well.
fn reward(&self, public_key: &str, amount: u64) {}
/// Slash a mixnode's stake based on bad performance or detected malign intent.
fn slash(&self, public_key: &str, amount: u64) {
// transfer slashed stake amount to reserve fund
// retrieve the mixnode from the database, and decrement its stake amount
// by the amount given.
}
/// Slash a mixnode's stake and immediately remove it from the mixnode set.
fn slash_remove(&self, public_key: String, amount: u64) {
// call slash (the method, not the guitarist)
// remove the mixnode from the database
}
*/
}
#[cfg(test)]
mod mixnodes {
use super::*;
#[test]
fn adding_and_retrieving_works() {
let mock_db = MixminingDb::new();
let mut service = Service::new(mock_db);
let node1 = tests::fake_mixnode("London, UK");
service.add(node1.clone());
let nodes = service.topology().mixnodes;
assert_eq!(1, nodes.len());
assert_eq!(node1.clone(), nodes[0]);
let node2 = tests::fake_mixnode("Neuchatel");
service.add(node2.clone());
let nodes = service.topology().mixnodes;
assert_eq!(2, nodes.len());
assert_eq!(node1, nodes[0]);
assert_eq!(node2, nodes[1]);
}
}
#[cfg(test)]
mod constructor {
use super::*;
#[test]
fn sets_database() {
let db = db::MixminingDb::new();
let service = Service::new(db.clone());
assert_eq!(db, service.db);
}
}
#[cfg(test)]
mod capacity {
use super::*;
#[test]
fn setting_capacity_sends_correct_value_to_datastore() {
let mock_db = db::MixminingDb::new();
let mut service = Service::new(mock_db);
service.set_capacity(3);
assert_eq!(3, service.capacity());
}
#[test]
fn getting_capacity_works() {
let mut mock_db = db::MixminingDb::new();
mock_db.set_capacity(3);
let service = Service::new(mock_db);
assert_eq!(3, service.capacity());
}
}
@@ -1,51 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Mixnode {
pub host: String,
pub public_key: String,
pub last_seen: u64,
pub location: String,
pub stake: u64,
pub version: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ServiceProvider {
host: String,
public_key: String,
version: String,
last_seen: u64,
location: String,
}
/// Topology shows us the current state of the overall Nym network
#[derive(Serialize, Deserialize, Debug)]
pub struct Topology {
pub mixnodes: Vec<Mixnode>,
pub service_providers: Vec<ServiceProvider>,
pub validators: Vec<Validator>,
}
impl Topology {
pub fn new(
mixnodes: Vec<Mixnode>,
service_providers: Vec<ServiceProvider>,
validators: Vec<Validator>,
) -> Topology {
Topology {
mixnodes,
service_providers,
validators,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Validator {
host: String,
public_key: String,
version: String,
last_seen: u64,
location: String,
}
@@ -1,11 +0,0 @@
#[cfg(test)]
pub fn fake_mixnode(location: &str) -> super::Mixnode {
super::Mixnode {
host: String::from("foo.com"),
last_seen: 123,
location: String::from(location),
public_key: String::from("abc123"),
stake: 8,
version: String::from("1.0"),
}
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod mixmining;
-57
View File
@@ -1,57 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// use crate::config::Config;
use crate::network::rest;
use crate::network::tendermint;
use crate::services::mixmining;
use tokio::runtime::Runtime;
pub struct Validator {
// when you re-introduce keys, check which ones you want:
// MixIdentityKeyPair (like 'nym-client' ) <- probably that one (after maybe renaming to just identity::KeyPair)
// encryption::KeyPair (like 'nym-mixnode' or 'sfw-provider')
tendermint_abci: tendermint::Abci,
rest_api: rest::Api,
}
impl Validator {
pub fn new() -> Self {
let mixmining_db = mixmining::db::MixminingDb::new();
let mixmining_service = mixmining::Service::new(mixmining_db);
let rest_api = rest::Api::new(mixmining_service);
Validator {
rest_api,
// perhaps you might want to pass &config to the constructor
// there to get the config.tendermint (assuming you create appropriate fields + getters)
tendermint_abci: tendermint::Abci::new(),
}
}
// TODO: Fix Tendermint startup here, see https://github.com/nymtech/nym/issues/147
pub fn start(self) {
let mut rt = Runtime::new().unwrap();
rt.spawn(self.rest_api.run());
rt.spawn(self.tendermint_abci.run());
// TODO: this message is going to come out of order (if at all), as spawns are async, see issue above
println!("Validator startup complete.");
rt.block_on(blocker());
}
}
pub async fn blocker() {} // once Tendermint unblocks us, make this block forever.