Merge commit '6d44fe818ea4c74f476cc6d79434cc8619b45c0c' into simon/instrumented
This commit is contained in:
+17
-20
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.4"
|
||||
version = "1.1.14"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
@@ -18,20 +18,20 @@ rust-version = "1.58.1"
|
||||
[dependencies]
|
||||
anyhow = "1.0.40"
|
||||
bs58 = "0.4.0"
|
||||
clap = { version = "3.2", features = ["cargo", "derive"] }
|
||||
clap = { version = "4.0", features = ["cargo", "derive"] }
|
||||
colored = "2.0"
|
||||
cupid = "0.6.1"
|
||||
dirs = "4.0"
|
||||
dotenv = "0.15.0"
|
||||
futures = "0.3.0"
|
||||
humantime-serde = "1.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.0"
|
||||
log = { workspace = true }
|
||||
pretty_env_logger = "0.4.0"
|
||||
rand = "0.7.3"
|
||||
rocket = { version = "0.5.0-rc.2", features = ["json"] }
|
||||
serde = { version="1.0", features = ["derive"] }
|
||||
sysinfo = "0.24.1"
|
||||
serde_json = "1.0"
|
||||
sysinfo = "0.27.7"
|
||||
tokio = { version="1.21.2", features = ["rt-multi-thread", "net", "signal"] }
|
||||
tokio-util = { version="0.7.3", features = ["codec"] }
|
||||
toml = "0.5.8"
|
||||
@@ -42,27 +42,24 @@ tracing-opentelemetry = "0.18.0"
|
||||
opentelemetry-jaeger = {version = "0.17.0", features = ["collector_client","rt-tokio","isahc_collector_client"]}
|
||||
opentelemetry = {version = "0.18.0", features = ["rt-tokio"]}
|
||||
tracing-flame = "0.2.0"
|
||||
atty = "0.2"
|
||||
|
||||
## internal
|
||||
config = { path="../common/config" }
|
||||
crypto = { path="../common/crypto" }
|
||||
completions = { path="../common/completions" }
|
||||
logging = { path="../common/logging" }
|
||||
nym-config = { path="../common/config" }
|
||||
nym-crypto = { path="../common/crypto" }
|
||||
mixnet-client = { path="../common/client-libs/mixnet-client" }
|
||||
mixnode-common = { path="../common/mixnode-common" }
|
||||
nonexhaustive-delayqueue = { path="../common/nonexhaustive-delayqueue" }
|
||||
nymsphinx = { path="../common/nymsphinx" }
|
||||
pemstore = { path="../common/pemstore" }
|
||||
task = { path = "../common/task" }
|
||||
topology = { path="../common/topology" }
|
||||
nym-nonexhaustive-delayqueue = { path="../common/nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path="../common/nymsphinx" }
|
||||
nym-pemstore = { path = "../common/pemstore", version = "0.2.0" }
|
||||
nym-task = { path = "../common/task" }
|
||||
nym-types = { path = "../common/types" }
|
||||
nym-topology = { path="../common/topology" }
|
||||
validator-client = { path="../common/client-libs/validator-client" }
|
||||
version-checker = { path="../common/version-checker" }
|
||||
nym-bin-common = { path="../common/bin-common" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version="1.21.2", features = ["rt-multi-thread", "net", "signal", "test-util"] }
|
||||
|
||||
nymsphinx-types = { path = "../common/nymsphinx/types" }
|
||||
nymsphinx-params = { path = "../common/nymsphinx/params" }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
nym-sphinx-types = { path = "../common/nymsphinx/types" }
|
||||
nym-sphinx-params = { path = "../common/nymsphinx/params" }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vergen::{vergen, Config};
|
||||
|
||||
fn main() {
|
||||
vergen(Config::default()).expect("failed to extract build metadata")
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use clap::Args;
|
||||
use colored::Colorize;
|
||||
use config::NymConfig;
|
||||
use nym_config::NymConfig;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
@@ -11,45 +14,64 @@ pub(crate) struct Describe {
|
||||
/// The id of the mixnode you want to describe
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
/// Human readable name of this node
|
||||
#[clap(long)]
|
||||
name: Option<String>,
|
||||
|
||||
/// Description of this node
|
||||
#[clap(long)]
|
||||
description: Option<String>,
|
||||
|
||||
/// Link associated with this node, for example `https://mixnode.yourdomain.com`
|
||||
#[clap(long)]
|
||||
link: Option<String>,
|
||||
|
||||
/// Physical location of this node, for example `City: London, Country: UK`
|
||||
#[clap(long)]
|
||||
location: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &Describe) {
|
||||
fn read_user_input() -> String {
|
||||
io::stdout().flush().unwrap();
|
||||
let mut buf = String::new();
|
||||
io::stdin().read_line(&mut buf).unwrap();
|
||||
buf.trim().to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: Describe) {
|
||||
// ensure that the mixnode has in fact been initialized
|
||||
match Config::load_from_file(Some(&args.id)) {
|
||||
match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", &args.id, err);
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// get input from the user
|
||||
print!("name: ");
|
||||
io::stdout().flush().unwrap();
|
||||
let mut name_buf = String::new();
|
||||
io::stdin().read_line(&mut name_buf).unwrap();
|
||||
let name = name_buf.trim().to_string();
|
||||
|
||||
print!("description: ");
|
||||
io::stdout().flush().unwrap();
|
||||
let mut desc_buf = String::new();
|
||||
io::stdin().read_line(&mut desc_buf).unwrap();
|
||||
let description = desc_buf.trim().to_string();
|
||||
|
||||
let example_url = "https://mixnode.yourdomain.com".bright_cyan();
|
||||
let example_location = "City: London, Country: UK";
|
||||
|
||||
print!("link, e.g. {}: ", example_url);
|
||||
io::stdout().flush().unwrap();
|
||||
let mut link_buf = String::new();
|
||||
io::stdin().read_line(&mut link_buf).unwrap();
|
||||
let link = link_buf.trim().to_string();
|
||||
// get input from the user if not provided via the arguments
|
||||
let name = args.name.unwrap_or_else(|| {
|
||||
print!("name: ");
|
||||
read_user_input()
|
||||
});
|
||||
|
||||
print!("location, e.g. {}: ", example_location);
|
||||
io::stdout().flush().unwrap();
|
||||
let mut location_buf = String::new();
|
||||
io::stdin().read_line(&mut location_buf).unwrap();
|
||||
let location = location_buf.trim().to_string();
|
||||
let description = args.description.unwrap_or_else(|| {
|
||||
print!("description: ");
|
||||
read_user_input()
|
||||
});
|
||||
|
||||
let link = args.link.unwrap_or_else(|| {
|
||||
print!("link, e.g. {example_url}: ");
|
||||
read_user_input()
|
||||
});
|
||||
|
||||
let location = args.location.unwrap_or_else(|| {
|
||||
print!("location, e.g. {example_location}: ");
|
||||
read_user_input()
|
||||
});
|
||||
|
||||
let node_description = NodeDescription {
|
||||
name,
|
||||
@@ -61,7 +83,7 @@ pub(crate) fn execute(args: &Describe) {
|
||||
// save the struct
|
||||
NodeDescription::save_to_file(
|
||||
&node_description,
|
||||
Config::default_config_directory(Some(&args.id)),
|
||||
Config::default_config_directory(&args.id),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::OverrideConfig;
|
||||
use crate::config::Config;
|
||||
use crate::node::MixNode;
|
||||
use crate::OutputFormat;
|
||||
use crate::{commands::override_config, config::persistence::pathfinder::MixNodePathfinder};
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
|
||||
use super::OverrideConfig;
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use std::net::IpAddr;
|
||||
use validator_client::nyxd;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Init {
|
||||
@@ -18,11 +20,11 @@ pub(crate) struct Init {
|
||||
|
||||
/// The host on which the mixnode will be running
|
||||
#[clap(long)]
|
||||
host: String,
|
||||
host: IpAddr,
|
||||
|
||||
/// The wallet address you will use to bond this mixnode, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9
|
||||
#[clap(long)]
|
||||
wallet_address: String,
|
||||
wallet_address: nyxd::AccountId,
|
||||
|
||||
/// The port on which the mixnode will be listening for mix packets
|
||||
#[clap(long)]
|
||||
@@ -40,9 +42,10 @@ pub(crate) struct Init {
|
||||
#[clap(long)]
|
||||
announce_host: Option<String>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the validators
|
||||
#[clap(long)]
|
||||
validators: Option<String>,
|
||||
/// Comma separated list of nym-api endpoints of the validators
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
#[clap(long, alias = "validators", value_delimiter = ',')]
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
}
|
||||
|
||||
impl From<Init> for OverrideConfig {
|
||||
@@ -55,18 +58,18 @@ impl From<Init> for OverrideConfig {
|
||||
verloc_port: init_config.verloc_port,
|
||||
http_api_port: init_config.http_api_port,
|
||||
announce_host: init_config.announce_host,
|
||||
validators: init_config.validators,
|
||||
nym_apis: init_config.nym_apis,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &Init) {
|
||||
pub(crate) fn execute(args: &Init, output: OutputFormat) {
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
let id = &override_config_fields.id;
|
||||
println!("Initialising mixnode {}...", id);
|
||||
eprintln!("Initialising mixnode {id}...");
|
||||
|
||||
let already_init = if Config::default_config_file_path(Some(id)).exists() {
|
||||
println!("Mixnode \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
|
||||
let already_init = if Config::default_config_file_path(id).exists() {
|
||||
eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!");
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -82,33 +85,32 @@ pub(crate) fn execute(args: &Init) {
|
||||
let identity_keys = identity::KeyPair::new(&mut rng);
|
||||
let sphinx_keys = encryption::KeyPair::new(&mut rng);
|
||||
let pathfinder = MixNodePathfinder::new_from_config(&config);
|
||||
pemstore::store_keypair(
|
||||
nym_pemstore::store_keypair(
|
||||
&identity_keys,
|
||||
&pemstore::KeyPairPath::new(
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save identity keys");
|
||||
|
||||
pemstore::store_keypair(
|
||||
nym_pemstore::store_keypair(
|
||||
&sphinx_keys,
|
||||
&pemstore::KeyPairPath::new(
|
||||
&nym_pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save sphinx keys");
|
||||
|
||||
println!("Saved mixnet identity and sphinx keypairs");
|
||||
eprintln!("Saved mixnet identity and sphinx keypairs");
|
||||
}
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Mixnode configuration completed.\n\n\n");
|
||||
eprintln!("Saved configuration file to {config_save_location:?}");
|
||||
eprintln!("Mixnode configuration completed.\n\n\n");
|
||||
|
||||
MixNode::new(config).print_node_details()
|
||||
MixNode::new(config).print_node_details(output)
|
||||
}
|
||||
|
||||
+45
-53
@@ -1,19 +1,18 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::process;
|
||||
|
||||
use crate::{config::Config, Cli};
|
||||
use clap::CommandFactory;
|
||||
use clap::Subcommand;
|
||||
use colored::Colorize;
|
||||
use completions::{fig_generate, ArgShell};
|
||||
use config::defaults::mainnet::read_var_if_not_default;
|
||||
use config::{
|
||||
defaults::var_names::{API_VALIDATOR, BECH32_PREFIX, CONFIGURED},
|
||||
parse_validators,
|
||||
};
|
||||
use crypto::bech32_address_validation;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_bin_common::version_checker;
|
||||
use nym_config::defaults::var_names::{BECH32_PREFIX, NYM_API};
|
||||
use nym_config::OptionalSet;
|
||||
use nym_crypto::bech32_address_validation;
|
||||
use std::net::IpAddr;
|
||||
use std::process;
|
||||
use validator_client::nyxd;
|
||||
|
||||
mod describe;
|
||||
mod init;
|
||||
@@ -52,71 +51,64 @@ pub(crate) enum Commands {
|
||||
// Configuration that can be overridden.
|
||||
struct OverrideConfig {
|
||||
id: String,
|
||||
host: Option<String>,
|
||||
wallet_address: Option<String>,
|
||||
host: Option<IpAddr>,
|
||||
wallet_address: Option<nyxd::AccountId>,
|
||||
mix_port: Option<u16>,
|
||||
verloc_port: Option<u16>,
|
||||
http_api_port: Option<u16>,
|
||||
announce_host: Option<String>,
|
||||
validators: Option<String>,
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) {
|
||||
let bin_name = "nym-mixnode";
|
||||
|
||||
match &args.command {
|
||||
let output = args.output();
|
||||
|
||||
match args.command {
|
||||
Commands::Describe(m) => describe::execute(m),
|
||||
Commands::Init(m) => init::execute(m),
|
||||
Commands::Run(m) => run::execute(m).await,
|
||||
Commands::Sign(m) => sign::execute(m),
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
Commands::NodeDetails(m) => node_details::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name),
|
||||
Commands::Init(m) => init::execute(&m, output),
|
||||
Commands::Run(m) => run::execute(&m, output).await,
|
||||
Commands::Sign(m) => sign::execute(&m),
|
||||
Commands::Upgrade(m) => upgrade::execute(&m),
|
||||
Commands::NodeDetails(m) => node_details::execute(&m, output),
|
||||
Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name),
|
||||
}
|
||||
}
|
||||
|
||||
fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
// special case that I'm not sure could be easily handled with the trait
|
||||
let mut was_host_overridden = false;
|
||||
if let Some(host) = args.host {
|
||||
config = config.with_listening_address(host);
|
||||
was_host_overridden = true;
|
||||
}
|
||||
|
||||
if let Some(port) = args.mix_port {
|
||||
config = config.with_mix_port(port);
|
||||
}
|
||||
|
||||
if let Some(port) = args.verloc_port {
|
||||
config = config.with_verloc_port(port);
|
||||
}
|
||||
|
||||
if let Some(port) = args.http_api_port {
|
||||
config = config.with_http_api_port(port);
|
||||
}
|
||||
|
||||
if let Some(ref raw_validators) = args.validators {
|
||||
config = config.with_custom_validator_apis(parse_validators(raw_validators));
|
||||
} else if std::env::var(CONFIGURED).is_ok() {
|
||||
if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) {
|
||||
config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators))
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref announce_host) = args.announce_host {
|
||||
if let Some(announce_host) = args.announce_host {
|
||||
config = config.with_announce_address(announce_host);
|
||||
} else if was_host_overridden {
|
||||
// make sure our 'announce-host' always defaults to 'host'
|
||||
config = config.announce_address_from_listening_address()
|
||||
}
|
||||
|
||||
if let Some(ref wallet_address) = args.wallet_address {
|
||||
let trimmed = wallet_address.trim();
|
||||
validate_bech32_address_or_exit(trimmed);
|
||||
config = config.with_wallet_address(trimmed);
|
||||
}
|
||||
|
||||
config
|
||||
.with_optional(Config::with_mix_port, args.mix_port)
|
||||
.with_optional(Config::with_verloc_port, args.verloc_port)
|
||||
.with_optional(Config::with_http_api_port, args.http_api_port)
|
||||
.with_optional_custom_env(
|
||||
Config::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
NYM_API,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional(
|
||||
|cfg, wallet_address| {
|
||||
validate_bech32_address_or_exit(wallet_address.as_ref());
|
||||
cfg.with_wallet_address(wallet_address)
|
||||
},
|
||||
args.wallet_address,
|
||||
)
|
||||
}
|
||||
|
||||
/// Ensures that a given bech32 address is valid, or exits
|
||||
@@ -125,18 +117,18 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) {
|
||||
if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) =
|
||||
bech32_address_validation::try_bech32_decode(address)
|
||||
{
|
||||
let error_message = format!("Error: wallet address decoding failed: {}", err).red();
|
||||
println!("{}", error_message);
|
||||
println!("Exiting...");
|
||||
let error_message = format!("Error: wallet address decoding failed: {err}").red();
|
||||
error!("{}", error_message);
|
||||
error!("Exiting...");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) =
|
||||
bech32_address_validation::validate_bech32_prefix(&prefix, address)
|
||||
{
|
||||
let error_message = format!("Error: wallet address type is wrong, {}", err).red();
|
||||
println!("{}", error_message);
|
||||
println!("Exiting...");
|
||||
let error_message = format!("Error: wallet address type is wrong, {err}").red();
|
||||
error!("{}", error_message);
|
||||
error!("Exiting...");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::node::MixNode;
|
||||
use crate::OutputFormat;
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use nym_config::NymConfig;
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct NodeDetails {
|
||||
@@ -13,8 +14,8 @@ pub(crate) struct NodeDetails {
|
||||
id: String,
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &NodeDetails) {
|
||||
let config = match Config::load_from_file(Some(&args.id)) {
|
||||
pub(crate) fn execute(args: &NodeDetails, output: OutputFormat) {
|
||||
let config = match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -26,5 +27,5 @@ pub(crate) fn execute(args: &NodeDetails) {
|
||||
}
|
||||
};
|
||||
|
||||
MixNode::new(config).print_node_details()
|
||||
MixNode::new(config).print_node_details(output)
|
||||
}
|
||||
|
||||
+19
-17
@@ -1,13 +1,15 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::OverrideConfig;
|
||||
use crate::commands::{override_config, version_check};
|
||||
use crate::config::Config;
|
||||
use crate::node::MixNode;
|
||||
use crate::OutputFormat;
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
|
||||
use super::OverrideConfig;
|
||||
use nym_config::NymConfig;
|
||||
use std::net::IpAddr;
|
||||
use validator_client::nyxd;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Run {
|
||||
@@ -17,11 +19,11 @@ pub(crate) struct Run {
|
||||
|
||||
/// The custom host on which the mixnode will be running
|
||||
#[clap(long)]
|
||||
host: Option<String>,
|
||||
host: Option<IpAddr>,
|
||||
|
||||
/// The wallet address you will use to bond this mixnode, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9
|
||||
#[clap(long)]
|
||||
wallet_address: Option<String>,
|
||||
wallet_address: Option<nyxd::AccountId>,
|
||||
|
||||
/// The port on which the mixnode will be listening for mix packets
|
||||
#[clap(long)]
|
||||
@@ -39,9 +41,10 @@ pub(crate) struct Run {
|
||||
#[clap(long)]
|
||||
announce_host: Option<String>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the validators
|
||||
#[clap(long)]
|
||||
validators: Option<String>,
|
||||
/// Comma separated list of nym-api endpoints of the validators
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
#[clap(long, alias = "validators", value_delimiter = ',')]
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
}
|
||||
|
||||
impl From<Run> for OverrideConfig {
|
||||
@@ -54,7 +57,7 @@ impl From<Run> for OverrideConfig {
|
||||
verloc_port: run_config.verloc_port,
|
||||
http_api_port: run_config.http_api_port,
|
||||
announce_host: run_config.announce_host,
|
||||
validators: run_config.validators,
|
||||
nym_apis: run_config.nym_apis,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +65,9 @@ impl From<Run> for OverrideConfig {
|
||||
fn show_binding_warning(address: &str) {
|
||||
println!("\n##### NOTE #####");
|
||||
println!(
|
||||
"\nYou are trying to bind to {} - you might not be accessible to other nodes\n\
|
||||
"\nYou are trying to bind to {address} - you might not be accessible to other nodes\n\
|
||||
You can ignore this note if you're running setup on a local network \n\
|
||||
or have set a custom 'announce-host'",
|
||||
address
|
||||
or have set a custom 'announce-host'"
|
||||
);
|
||||
println!("\n\n");
|
||||
}
|
||||
@@ -74,10 +76,10 @@ fn special_addresses() -> Vec<&'static str> {
|
||||
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) {
|
||||
println!("Starting mixnode {}...", args.id);
|
||||
pub(crate) async fn execute(args: &Run, output: OutputFormat) {
|
||||
eprintln!("Starting mixnode {}...", args.id);
|
||||
|
||||
let mut config = match Config::load_from_file(Some(&args.id)) {
|
||||
let mut config = match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -102,10 +104,10 @@ pub(crate) async fn execute(args: &Run) {
|
||||
|
||||
let mut mixnode = MixNode::new(config);
|
||||
|
||||
println!(
|
||||
eprintln!(
|
||||
"\nTo bond your mixnode you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\
|
||||
Select the correct version and install it to your machine. You will need to provide the following: \n ");
|
||||
mixnode.print_node_details();
|
||||
mixnode.print_node_details(output);
|
||||
|
||||
mixnode.run().await
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryFrom;
|
||||
@@ -8,22 +8,24 @@ use crate::config::{persistence::pathfinder::MixNodePathfinder, Config};
|
||||
use crate::node::MixNode;
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::{ArgGroup, Args};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use log::error;
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use validator_client::nyxd;
|
||||
|
||||
use super::version_check;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
#[clap(group(ArgGroup::new("sign").required(true).args(&["address", "text"])))]
|
||||
#[clap(group(ArgGroup::new("sign").required(true).args(&["wallet_address", "text"])))]
|
||||
pub(crate) struct Sign {
|
||||
/// The id of the mixnode you want to sign with
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
/// Signs your blockchain address with your identity key
|
||||
#[clap(long)]
|
||||
address: Option<String>,
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
#[clap(long, alias = "address")]
|
||||
wallet_address: Option<nyxd::AccountId>,
|
||||
|
||||
/// Signs an arbitrary piece of text with your identity key
|
||||
#[clap(long)]
|
||||
@@ -32,7 +34,7 @@ pub(crate) struct Sign {
|
||||
|
||||
enum SignedTarget {
|
||||
Text(String),
|
||||
Address(String),
|
||||
Address(nyxd::AccountId),
|
||||
}
|
||||
|
||||
impl TryFrom<Sign> for SignedTarget {
|
||||
@@ -41,7 +43,7 @@ impl TryFrom<Sign> for SignedTarget {
|
||||
fn try_from(args: Sign) -> Result<Self, Self::Error> {
|
||||
if let Some(text) = args.text {
|
||||
Ok(SignedTarget::Text(text))
|
||||
} else if let Some(address) = args.address {
|
||||
} else if let Some(address) = args.wallet_address {
|
||||
Ok(SignedTarget::Address(address))
|
||||
} else {
|
||||
// This is unreachable, and hopefully clap will support it explicitly by outputting an
|
||||
@@ -52,33 +54,24 @@ impl TryFrom<Sign> for SignedTarget {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) {
|
||||
let trimmed = raw_address.trim();
|
||||
validate_bech32_address_or_exit(trimmed);
|
||||
let signature = private_key.sign_text(trimmed);
|
||||
fn print_signed_address(private_key: &identity::PrivateKey, wallet_address: nyxd::AccountId) {
|
||||
// perform extra validation to ensure we have correct prefix
|
||||
validate_bech32_address_or_exit(wallet_address.as_ref());
|
||||
|
||||
println!(
|
||||
"The base58-encoded signature on '{}' is: {}",
|
||||
trimmed, signature
|
||||
);
|
||||
let signature = private_key.sign_text(wallet_address.as_ref());
|
||||
println!("The base58-encoded signature on '{wallet_address}' is: {signature}",);
|
||||
}
|
||||
|
||||
fn print_signed_text(private_key: &identity::PrivateKey, text: &str) {
|
||||
println!(
|
||||
"Signing the text {:?} using your mixnode's Ed25519 identity key...",
|
||||
text
|
||||
);
|
||||
println!("Signing the text {text:?} using your mixnode's Ed25519 identity key...");
|
||||
|
||||
let signature = private_key.sign_text(text);
|
||||
|
||||
println!(
|
||||
"The base58-encoded signature on '{}' is: {}",
|
||||
text, signature
|
||||
);
|
||||
println!("The base58-encoded signature on '{text}' is: {signature}");
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &Sign) {
|
||||
let config = match Config::load_from_file(Some(&args.id)) {
|
||||
let config = match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -107,6 +100,6 @@ pub(crate) fn execute(args: &Sign) {
|
||||
|
||||
match signed_target {
|
||||
SignedTarget::Text(text) => print_signed_text(identity_keypair.private_key(), &text),
|
||||
SignedTarget::Address(addr) => print_signed_address(identity_keypair.private_key(), &addr),
|
||||
SignedTarget::Address(addr) => print_signed_address(identity_keypair.private_key(), addr),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
use crate::config::{missing_string_value, Config};
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use nym_bin_common::version_checker::Version;
|
||||
use nym_config::NymConfig;
|
||||
use std::fmt::Display;
|
||||
use std::process;
|
||||
use version_checker::Version;
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct Upgrade {
|
||||
@@ -22,49 +22,38 @@ fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> !
|
||||
}
|
||||
|
||||
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
println!(
|
||||
"\n==================\nTrying to upgrade mixnode from {} to {} ...",
|
||||
from, to
|
||||
);
|
||||
println!("\n==================\nTrying to upgrade mixnode from {from} to {to} ...");
|
||||
}
|
||||
|
||||
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
eprintln!(
|
||||
"Upgrade from {} to {} failed!\n==================\n",
|
||||
from, to
|
||||
);
|
||||
eprintln!("Upgrade from {from} to {to} failed!\n==================\n");
|
||||
}
|
||||
|
||||
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
|
||||
println!(
|
||||
"Upgrade from {} to {} was successful!\n==================\n",
|
||||
from, to
|
||||
);
|
||||
println!("Upgrade from {from} to {to} was successful!\n==================\n");
|
||||
}
|
||||
|
||||
fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! {
|
||||
eprintln!(
|
||||
"Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!",
|
||||
config_version, package_version
|
||||
"Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!"
|
||||
);
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn unsupported_upgrade(config_version: &Version, package_version: &Version) -> ! {
|
||||
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, package_version);
|
||||
eprintln!("Cannot perform upgrade from {config_version} to {package_version}. Please let the developers know about this issue if you expected it to work!");
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn parse_config_version(config: &Config) -> Version {
|
||||
let version = Version::parse(config.get_version()).unwrap_or_else(|err| {
|
||||
eprintln!("failed to parse client version! - {:?}", err);
|
||||
eprintln!("failed to parse client version! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
if version.is_prerelease() || !version.build.is_empty() {
|
||||
eprintln!(
|
||||
"Trying to upgrade from a non-released version {}. This is not supported!",
|
||||
version
|
||||
"Trying to upgrade from a non-released version {version}. This is not supported!"
|
||||
);
|
||||
process::exit(1)
|
||||
}
|
||||
@@ -79,10 +68,7 @@ fn parse_package_version() -> Version {
|
||||
// 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 version.is_prerelease() || !version.build.is_empty() {
|
||||
eprintln!(
|
||||
"Trying to upgrade to a non-released version {}. This is not supported!",
|
||||
version
|
||||
);
|
||||
eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!");
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
@@ -106,7 +92,7 @@ fn minor_0_12_upgrade(
|
||||
let upgraded_config = config.with_custom_version(to_version.to_string().as_ref());
|
||||
|
||||
upgraded_config.save_to_file(None).unwrap_or_else(|err| {
|
||||
eprintln!("failed to overwrite config file! - {:?}", err);
|
||||
eprintln!("failed to overwrite config file! - {err}");
|
||||
print_failed_upgrade(config_version, &to_version);
|
||||
process::exit(1);
|
||||
});
|
||||
@@ -139,8 +125,8 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) {
|
||||
pub(crate) fn execute(args: &Upgrade) {
|
||||
let package_version = parse_package_version();
|
||||
|
||||
let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {:?}", err);
|
||||
let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
|
||||
+24
-30
@@ -2,17 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::template::config_template;
|
||||
use config::defaults::mainnet::API_VALIDATOR;
|
||||
use config::defaults::{
|
||||
use nym_config::defaults::mainnet::NYM_API;
|
||||
use nym_config::defaults::{
|
||||
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||
};
|
||||
use config::NymConfig;
|
||||
use nym_config::NymConfig;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
use validator_client::nyxd;
|
||||
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
@@ -155,21 +156,13 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec<Url>) -> Self {
|
||||
self.mixnode.validator_api_urls = validator_api_urls;
|
||||
pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self {
|
||||
self.mixnode.nym_api_urls = nym_api_urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_listening_address<S: Into<String>>(mut self, listening_address: S) -> Self {
|
||||
let listening_address_string = listening_address.into();
|
||||
if let Ok(ip_addr) = listening_address_string.parse() {
|
||||
self.mixnode.listening_address = ip_addr
|
||||
} else {
|
||||
error!(
|
||||
"failed to change listening address. the provided value ({}) was invalid",
|
||||
listening_address_string
|
||||
)
|
||||
}
|
||||
pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self {
|
||||
self.mixnode.listening_address = listening_address;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -203,8 +196,8 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_wallet_address(mut self, wallet_address: &str) -> Self {
|
||||
self.mixnode.wallet_address = wallet_address.to_string();
|
||||
pub fn with_wallet_address(mut self, wallet_address: nyxd::AccountId) -> Self {
|
||||
self.mixnode.wallet_address = Some(wallet_address);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -233,8 +226,8 @@ impl Config {
|
||||
self.mixnode.public_sphinx_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_validator_api_endpoints(&self) -> Vec<Url> {
|
||||
self.mixnode.validator_api_urls.clone()
|
||||
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
|
||||
self.mixnode.nym_api_urls.clone()
|
||||
}
|
||||
|
||||
pub fn get_node_stats_logging_delay(&self) -> Duration {
|
||||
@@ -317,8 +310,8 @@ impl Config {
|
||||
self.verloc.retry_timeout
|
||||
}
|
||||
|
||||
pub fn get_wallet_address(&self) -> &str {
|
||||
&self.mixnode.wallet_address
|
||||
pub fn get_wallet_address(&self) -> Option<nyxd::AccountId> {
|
||||
self.mixnode.wallet_address.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,32 +362,33 @@ struct MixNode {
|
||||
/// Path to file containing public sphinx key.
|
||||
public_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Addresses to APIs running on validator from which the node gets the view of the network.
|
||||
validator_api_urls: Vec<Url>,
|
||||
/// Addresses to nym APIs from which the node gets the view of the network.
|
||||
nym_api_urls: Vec<Url>,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// The Cosmos wallet address that will control this mixnode
|
||||
wallet_address: String,
|
||||
// the only reason this is an Option is because of the lack of existence of a sane default value
|
||||
wallet_address: Option<nyxd::AccountId>,
|
||||
}
|
||||
|
||||
impl MixNode {
|
||||
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_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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,9 +406,9 @@ impl Default for MixNode {
|
||||
public_identity_key_file: Default::default(),
|
||||
private_sphinx_key_file: Default::default(),
|
||||
public_sphinx_key_file: Default::default(),
|
||||
validator_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")],
|
||||
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
|
||||
nym_root_directory: Config::default_root_directory(),
|
||||
wallet_address: "nymXXXXXXXX".to_string(),
|
||||
wallet_address: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ verloc_port = {{ mixnode.verloc_port }}
|
||||
http_api_port = {{ mixnode.http_api_port }}
|
||||
|
||||
# Addresses to APIs running on validator from which the node gets the view of the network.
|
||||
validator_api_urls = [
|
||||
{{#each mixnode.validator_api_urls }}
|
||||
nym_api_urls = [
|
||||
{{#each mixnode.nym_api_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
+41
-60
@@ -1,41 +1,68 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use ::config::defaults::setup_env;
|
||||
use clap::{crate_version, Parser};
|
||||
use ::nym_config::defaults::setup_env;
|
||||
use clap::{crate_name, crate_version, Parser, ValueEnum};
|
||||
use lazy_static::lazy_static;
|
||||
use logging::setup_logging;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::{EnvFilter, Registry, filter};
|
||||
use tracing_flame::FlameLayer;
|
||||
use nym_bin_common::logging::setup_logging;
|
||||
use nym_bin_common::{build_information::BinaryBuildInformation, logging::banner};
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
mod node;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref LONG_VERSION: String = long_version();
|
||||
pub static ref PRETTY_BUILD_INFORMATION: String =
|
||||
BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print();
|
||||
}
|
||||
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn long_version_static() -> &'static str {
|
||||
&LONG_VERSION
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
&PRETTY_BUILD_INFORMATION
|
||||
}
|
||||
|
||||
#[derive(Clone, ValueEnum)]
|
||||
enum OutputFormat {
|
||||
Json,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl Default for OutputFormat {
|
||||
fn default() -> Self {
|
||||
OutputFormat::Text
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
|
||||
#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the mixnode.
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(short, long)]
|
||||
pub(crate) output: Option<OutputFormat>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: commands::Commands,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
fn output(&self) -> OutputFormat {
|
||||
if let Some(ref output) = self.output {
|
||||
output.clone()
|
||||
} else {
|
||||
OutputFormat::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
//setup_logging();
|
||||
@@ -64,62 +91,16 @@ async fn main() {
|
||||
tracing::subscriber::set_global_default(subscriber)
|
||||
.expect("Failed to set global subscriber");
|
||||
|
||||
println!("{}", banner());
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
println!("{}", banner(crate_name!(), crate_version!()));
|
||||
}
|
||||
|
||||
let args = Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
setup_env(args.config_env_file.as_ref());
|
||||
commands::execute(args).await;
|
||||
opentelemetry::global::shutdown_tracer_provider();
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
format!(
|
||||
r#"
|
||||
|
||||
_ __ _ _ _ __ ___
|
||||
| '_ \| | | | '_ \ _ \
|
||||
| | | | |_| | | | | | |
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(mixnode - version {:})
|
||||
|
||||
"#,
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE")
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -5,15 +5,15 @@ use crate::node::listener::connection_handler::packet_processing::{
|
||||
MixProcessingResult, PacketProcessor,
|
||||
};
|
||||
use crate::node::packet_delayforwarder::PacketDelayForwardSender;
|
||||
use crate::node::ShutdownListener;
|
||||
use crate::node::TaskClient;
|
||||
use futures::StreamExt;
|
||||
use tracing::{error, info, trace, debug, warn};
|
||||
use tracing::*;
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use nymsphinx::framing::codec::SphinxCodec;
|
||||
use nymsphinx::framing::packet::FramedSphinxPacket;
|
||||
use nymsphinx::Delay as SphinxDelay;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::framing::codec::SphinxCodec;
|
||||
use nym_sphinx::framing::packet::FramedSphinxPacket;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
@@ -63,7 +63,7 @@ impl ConnectionHandler {
|
||||
// all processing such, key caching, etc. was done.
|
||||
// however, if it was a forward hop, we still need to delay it
|
||||
match self.packet_processor.process_received(framed_sphinx_packet) {
|
||||
Err(e) => debug!("We failed to process received sphinx packet - {:?}", e),
|
||||
Err(err) => debug!("We failed to process received sphinx packet - {err}"),
|
||||
Ok(res) => match res {
|
||||
MixProcessingResult::ForwardHop(forward_packet, delay) => {
|
||||
self.delay_and_forward_packet(forward_packet, delay)
|
||||
@@ -79,12 +79,17 @@ impl ConnectionHandler {
|
||||
self,
|
||||
conn: TcpStream,
|
||||
remote: SocketAddr,
|
||||
mut shutdown: ShutdownListener,
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
trace!("ConnectionHandler: received shutdown");
|
||||
}
|
||||
Some(framed_sphinx_packet) = framed_conn.next() => {
|
||||
match framed_sphinx_packet {
|
||||
Ok(framed_sphinx_packet) => {
|
||||
@@ -102,16 +107,12 @@ impl ConnectionHandler {
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"The socket connection got corrupted with error: {:?}. Closing the socket",
|
||||
err
|
||||
"The socket connection got corrupted with error: {err}. Closing the socket",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
trace!("ConnectionHandler: received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::node_statistics;
|
||||
use crypto::asymmetric::encryption;
|
||||
use mixnode_common::packet_processor::error::MixProcessingError;
|
||||
pub use mixnode_common::packet_processor::processor::MixProcessingResult;
|
||||
use mixnode_common::packet_processor::processor::SphinxPacketProcessor;
|
||||
use nymsphinx::framing::packet::FramedSphinxPacket;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_sphinx::framing::packet::FramedSphinxPacket;
|
||||
|
||||
// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -9,17 +9,17 @@ use std::process;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::ShutdownListener;
|
||||
use super::TaskClient;
|
||||
|
||||
pub(crate) mod connection_handler;
|
||||
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(address: SocketAddr, shutdown: ShutdownListener) -> Self {
|
||||
pub(crate) fn new(address: SocketAddr, shutdown: TaskClient) -> Self {
|
||||
Listener { address, shutdown }
|
||||
}
|
||||
#[instrument(level="info", skip_all, "Mixnet Listener")]
|
||||
@@ -28,25 +28,26 @@ impl Listener {
|
||||
let listener = match TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
error!("Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address, err);
|
||||
error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
}
|
||||
connection = listener.accept() => {
|
||||
match connection {
|
||||
Ok((socket, remote_addr)) => {
|
||||
let handler = connection_handler.clone();
|
||||
tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone()).instrument(info_span!("Connection handling", address = %remote_addr)));
|
||||
}
|
||||
Err(err) => warn!("Failed to accept incoming connection - {:?}", err),
|
||||
Err(err) => warn!("Failed to accept incoming connection - {err}"),
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
}
|
||||
};
|
||||
}
|
||||
trace!("Listener: Exiting");
|
||||
|
||||
+52
-55
@@ -17,18 +17,20 @@ use crate::node::listener::Listener;
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use crate::node::node_statistics::SharedNodeStats;
|
||||
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
|
||||
use ::crypto::asymmetric::{encryption, identity};
|
||||
use config::NymConfig;
|
||||
use tracing::*;
|
||||
use tracing::{info, error, warn};
|
||||
use crate::OutputFormat;
|
||||
use colored::Colorize;
|
||||
use mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer};
|
||||
use nym_bin_common::version_checker::parse_version;
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||
use version_checker::parse_version;
|
||||
|
||||
mod http;
|
||||
mod listener;
|
||||
@@ -57,14 +59,14 @@ impl MixNode {
|
||||
}
|
||||
|
||||
fn load_node_description(config: &Config) -> NodeDescription {
|
||||
NodeDescription::load_from_file(Config::default_config_directory(Some(&config.get_id())))
|
||||
NodeDescription::load_from_file(Config::default_config_directory(&config.get_id()))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Loads identity keys stored on disk
|
||||
pub(crate) fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
@@ -75,7 +77,7 @@ impl MixNode {
|
||||
/// Loads Sphinx keys stored on disk
|
||||
fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
@@ -88,39 +90,41 @@ impl MixNode {
|
||||
fn generate_owner_signature(&self) -> String {
|
||||
let pathfinder = MixNodePathfinder::new_from_config(&self.config);
|
||||
let identity_keypair = Self::load_identity_keys(&pathfinder);
|
||||
let address = self.config.get_wallet_address();
|
||||
validate_bech32_address_or_exit(address);
|
||||
let verification_code = identity_keypair.private_key().sign_text(address);
|
||||
let Some(address) = self.config.get_wallet_address() else {
|
||||
let error_message = "Error: mixnode hasn't set its wallet address".red();
|
||||
println!("{error_message}");
|
||||
println!("Exiting...");
|
||||
process::exit(1);
|
||||
};
|
||||
// perform extra validation to ensure we have correct prefix
|
||||
validate_bech32_address_or_exit(address.as_ref());
|
||||
let verification_code = identity_keypair.private_key().sign_text(address.as_ref());
|
||||
verification_code
|
||||
}
|
||||
|
||||
/// Prints relevant node details to the console
|
||||
pub(crate) fn print_node_details(&self) {
|
||||
println!(
|
||||
"Identity Key: {}",
|
||||
self.identity_keypair.public_key().to_base58_string()
|
||||
);
|
||||
println!(
|
||||
"Sphinx Key: {}",
|
||||
self.sphinx_keypair.public_key().to_base58_string()
|
||||
);
|
||||
println!("Owner Signature: {}", self.generate_owner_signature());
|
||||
println!(
|
||||
"Host: {} (bind address: {})",
|
||||
self.config.get_announce_address(),
|
||||
self.config.get_listening_address()
|
||||
);
|
||||
println!("Version: {}", self.config.get_version());
|
||||
println!(
|
||||
"Mix Port: {}, Verloc port: {}, Http Port: {}\n",
|
||||
self.config.get_mix_port(),
|
||||
self.config.get_verloc_port(),
|
||||
self.config.get_http_api_port()
|
||||
);
|
||||
println!(
|
||||
"You are bonding to wallet address: {}\n\n",
|
||||
self.config.get_wallet_address()
|
||||
);
|
||||
pub(crate) fn print_node_details(&self, output: OutputFormat) {
|
||||
let node_details = nym_types::mixnode::MixnodeNodeDetailsResponse {
|
||||
identity_key: self.identity_keypair.public_key().to_base58_string(),
|
||||
sphinx_key: self.sphinx_keypair.public_key().to_base58_string(),
|
||||
owner_signature: self.generate_owner_signature(),
|
||||
announce_address: self.config.get_announce_address(),
|
||||
bind_address: self.config.get_listening_address().to_string(),
|
||||
version: self.config.get_version().to_string(),
|
||||
mix_port: self.config.get_mix_port(),
|
||||
http_api_port: self.config.get_http_api_port(),
|
||||
verloc_port: self.config.get_verloc_port(),
|
||||
wallet_address: self.config.get_wallet_address().map(|x| x.to_string()),
|
||||
};
|
||||
|
||||
match output {
|
||||
OutputFormat::Json => println!(
|
||||
"{}",
|
||||
serde_json::to_string(&node_details)
|
||||
.unwrap_or_else(|_| "Could not serialize node details".to_string())
|
||||
),
|
||||
OutputFormat::Text => println!("{node_details}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_http_api(
|
||||
@@ -154,7 +158,7 @@ impl MixNode {
|
||||
|
||||
fn start_node_stats_controller(
|
||||
&self,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> (SharedNodeStats, node_statistics::UpdateSender) {
|
||||
info!("Starting node stats controller...");
|
||||
let controller = node_statistics::Controller::new(
|
||||
@@ -172,7 +176,7 @@ impl MixNode {
|
||||
&self,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
delay_forwarding_channel: PacketDelayForwardSender,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting socket listener...");
|
||||
|
||||
@@ -192,7 +196,7 @@ impl MixNode {
|
||||
fn start_packet_delay_forwarder(
|
||||
&mut self,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> PacketDelayForwardSender {
|
||||
info!("Starting packet delay-forwarder...");
|
||||
|
||||
@@ -216,7 +220,7 @@ impl MixNode {
|
||||
packet_sender
|
||||
}
|
||||
|
||||
fn start_verloc_measurements(&self, shutdown: ShutdownListener) -> AtomicVerlocResult {
|
||||
fn start_verloc_measurements(&self, shutdown: TaskClient) -> AtomicVerlocResult {
|
||||
info!("Starting the round-trip-time measurer...");
|
||||
|
||||
// this is a sanity check to make sure we didn't mess up with the minimum version at some point
|
||||
@@ -246,7 +250,7 @@ impl MixNode {
|
||||
.tested_nodes_batch_size(self.config.get_measurement_tested_nodes_batch_size())
|
||||
.testing_interval(self.config.get_measurement_testing_interval())
|
||||
.retry_timeout(self.config.get_measurement_retry_timeout())
|
||||
.validator_api_urls(self.config.get_validator_api_endpoints())
|
||||
.nym_api_urls(self.config.get_nym_api_endpoints())
|
||||
.build();
|
||||
|
||||
let mut verloc_measurer =
|
||||
@@ -256,13 +260,13 @@ impl MixNode {
|
||||
atomic_verloc_results
|
||||
}
|
||||
|
||||
fn random_api_client(&self) -> validator_client::client::ApiClient {
|
||||
let endpoints = self.config.get_validator_api_endpoints();
|
||||
let validator_api = endpoints
|
||||
fn random_api_client(&self) -> validator_client::NymApiClient {
|
||||
let endpoints = self.config.get_nym_api_endpoints();
|
||||
let nym_api = endpoints
|
||||
.choose(&mut thread_rng())
|
||||
.expect("The list of validator apis is empty");
|
||||
|
||||
validator_client::client::ApiClient::new(validator_api.clone())
|
||||
validator_client::NymApiClient::new(nym_api.clone())
|
||||
}
|
||||
|
||||
// TODO: ask DH whether this function still makes sense in ^0.10
|
||||
@@ -289,15 +293,8 @@ impl MixNode {
|
||||
.map(|node| node.bond_information.mix_node.identity_key.clone())
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(&self, mut shutdown: ShutdownNotifier) {
|
||||
wait_for_signal().await;
|
||||
|
||||
info!("Sending shutdown");
|
||||
shutdown.signal_shutdown().ok();
|
||||
|
||||
info!("Waiting for tasks to finish... (Press ctrl-c to force)");
|
||||
shutdown.wait_for_shutdown().await;
|
||||
|
||||
async fn wait_for_interrupt(&self, shutdown: TaskManager) {
|
||||
let _res = shutdown.catch_interrupt().await;
|
||||
info!("Stopping nym mixnode");
|
||||
}
|
||||
|
||||
@@ -316,7 +313,7 @@ impl MixNode {
|
||||
}
|
||||
}
|
||||
|
||||
let shutdown = ShutdownNotifier::default();
|
||||
let shutdown = TaskManager::default();
|
||||
|
||||
let (node_stats_pointer, node_stats_update_sender) =
|
||||
self.start_node_stats_controller(shutdown.subscribe());
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
use super::ShutdownListener;
|
||||
use super::TaskClient;
|
||||
|
||||
// convenience aliases
|
||||
type PacketsMap = HashMap<String, u64>;
|
||||
@@ -211,14 +211,14 @@ impl CurrentPacketData {
|
||||
struct UpdateHandler {
|
||||
current_data: CurrentPacketData,
|
||||
update_receiver: PacketDataReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl UpdateHandler {
|
||||
fn new(
|
||||
current_data: CurrentPacketData,
|
||||
update_receiver: PacketDataReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
UpdateHandler {
|
||||
current_data,
|
||||
@@ -293,7 +293,7 @@ struct StatsUpdater {
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: SharedNodeStats,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl StatsUpdater {
|
||||
@@ -301,7 +301,7 @@ impl StatsUpdater {
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: SharedNodeStats,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
StatsUpdater {
|
||||
updating_delay,
|
||||
@@ -335,11 +335,11 @@ impl StatsUpdater {
|
||||
struct PacketStatsConsoleLogger {
|
||||
logging_delay: Duration,
|
||||
stats: SharedNodeStats,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl PacketStatsConsoleLogger {
|
||||
fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: ShutdownListener) -> Self {
|
||||
fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: TaskClient) -> Self {
|
||||
PacketStatsConsoleLogger {
|
||||
logging_delay,
|
||||
stats,
|
||||
@@ -451,7 +451,7 @@ impl Controller {
|
||||
pub(crate) fn new(
|
||||
logging_delay: Duration,
|
||||
stats_updating_delay: Duration,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
let shared_packet_data = CurrentPacketData::new();
|
||||
@@ -503,13 +503,13 @@ impl Controller {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use task::ShutdownNotifier;
|
||||
use nym_task::TaskManager;
|
||||
|
||||
#[tokio::test]
|
||||
async fn node_stats_reported_are_received() {
|
||||
let logging_delay = Duration::from_millis(20);
|
||||
let stats_updating_delay = Duration::from_millis(10);
|
||||
let shutdown = ShutdownNotifier::default();
|
||||
let shutdown = TaskManager::default();
|
||||
let node_stats_controller =
|
||||
Controller::new(logging_delay, stats_updating_delay, shutdown.subscribe());
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
use crate::node::node_statistics::UpdateSender;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use nymsphinx::params::packet_sizes::PacketSize;
|
||||
use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue};
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::packet_sizes::PacketSize;
|
||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use std::io;
|
||||
use tokio::time::Instant;
|
||||
use tracing::*;
|
||||
use tracing::{trace};
|
||||
|
||||
use super::ShutdownListener;
|
||||
use super::TaskClient;
|
||||
|
||||
// Delay + MixPacket vs Instant + MixPacket
|
||||
|
||||
@@ -31,7 +31,7 @@ where
|
||||
packet_sender: PacketDelayForwardSender,
|
||||
packet_receiver: PacketDelayForwardReceiver,
|
||||
node_stats_update_sender: UpdateSender,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl<C> DelayForwarder<C>
|
||||
@@ -41,7 +41,7 @@ where
|
||||
pub(crate) fn new(
|
||||
client: C,
|
||||
node_stats_update_sender: UpdateSender,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> DelayForwarder<C> {
|
||||
let (packet_sender, packet_receiver) = mpsc::unbounded();
|
||||
|
||||
@@ -139,13 +139,13 @@ mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use task::ShutdownNotifier;
|
||||
use nym_task::TaskManager;
|
||||
|
||||
use nymsphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx_params::packet_sizes::PacketSize;
|
||||
use nymsphinx_params::PacketMode;
|
||||
use nymsphinx_types::builder::SphinxPacketBuilder;
|
||||
use nymsphinx_types::{
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::PacketMode;
|
||||
use nym_sphinx_types::builder::SphinxPacketBuilder;
|
||||
use nym_sphinx_types::{
|
||||
crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes,
|
||||
SphinxPacket, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH,
|
||||
};
|
||||
@@ -210,7 +210,7 @@ mod tests {
|
||||
let node_stats_update_sender = UpdateSender::new(stats_sender);
|
||||
let client = TestClient::default();
|
||||
let client_packets_sent = client.packets_sent.clone();
|
||||
let shutdown = ShutdownNotifier::default();
|
||||
let shutdown = TaskManager::default();
|
||||
let mut delay_forwarder =
|
||||
DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe());
|
||||
let packet_sender = delay_forwarder.sender();
|
||||
|
||||
Reference in New Issue
Block a user