Feature/add node description api (#605)
* Starting to build out node descriptions * Renamed the mod to node_description instead of descriptor * Returning results loaded from file * Taking user input for node describe command * Readline without new dependencies * Adding input format hint * Making sure the node can still start even when the descriptor file isn't there yet * Adding some additional runtime checks * Removing -dev from minimum node version * Removing initial :: on serde * Fixing comment on the /description endpoint * Swapped json for toml * Being a bit more specific with my startup message. * Cleaning up path building * Nicer runtime checks * Put path building inside the file methods
This commit is contained in:
Generated
+5
-2
@@ -2,6 +2,8 @@
|
||||
# It is not intended for manual editing.
|
||||
# Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aes-ctr"
|
||||
version = "0.3.0"
|
||||
@@ -1733,6 +1735,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"topology",
|
||||
"validator-client",
|
||||
"version-checker",
|
||||
@@ -2819,9 +2822,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.4.2"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
|
||||
@@ -25,6 +25,7 @@ rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", rev="f442ad9
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.4", features = [ "rt-multi-thread", "net", "signal" ] }
|
||||
tokio-util = { version = "0.6", features = ["codec"] }
|
||||
toml = "0.5"
|
||||
|
||||
## internal
|
||||
config = { path = "../common/config" }
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::config::Config;
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use colored::Colorize;
|
||||
use config::NymConfig;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("describe")
|
||||
.about("Describe your mixnode and tell people why they should delegate stake to you")
|
||||
.arg(
|
||||
Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("The id of the mixnode you want to describe")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
// figure out which node the user is describing
|
||||
let id = matches
|
||||
.value_of("id")
|
||||
.expect("Please provide the id of your mixnode");
|
||||
|
||||
// ensure that the mixnode has in fact been initialized
|
||||
match Config::load_from_file(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
let node_description = NodeDescription {
|
||||
name,
|
||||
description,
|
||||
link,
|
||||
};
|
||||
|
||||
// save the struct
|
||||
NodeDescription::save_to_file(&node_description, Config::default_config_directory(id)).unwrap()
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use crate::config::Config;
|
||||
use clap::ArgMatches;
|
||||
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
|
||||
|
||||
pub(crate) mod describe;
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod sign;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::commands::override_config;
|
||||
use crate::config::{persistence::pathfinder::MixNodePathfinder, Config};
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use crate::node::MixNode;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
@@ -183,5 +184,7 @@ pub fn execute(matches: &ArgMatches) {
|
||||
config.get_version(),
|
||||
);
|
||||
|
||||
MixNode::new(config, identity_keypair, sphinx_keypair).run();
|
||||
let description =
|
||||
NodeDescription::load_from_file(Config::default_config_directory(id)).unwrap_or_default();
|
||||
MixNode::new(config, description, identity_keypair, sphinx_keypair).run();
|
||||
}
|
||||
|
||||
+3
-1
@@ -18,7 +18,8 @@ fn main() {
|
||||
let arg_matches = App::new("Nym Mixnode")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Loopix-based Mixnode")
|
||||
.about("Implementation of a Loopix-based Mixnode")
|
||||
.subcommand(commands::describe::command_args())
|
||||
.subcommand(commands::init::command_args())
|
||||
.subcommand(commands::run::command_args())
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
@@ -30,6 +31,7 @@ fn main() {
|
||||
|
||||
fn execute(matches: ArgMatches) {
|
||||
match matches.subcommand() {
|
||||
("describe", Some(m)) => commands::describe::execute(m),
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
("sign", Some(m)) => commands::sign::execute(m),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use rocket::State;
|
||||
use rocket_contrib::json::Json;
|
||||
|
||||
/// Returns a description of the node and why someone might want to delegate stake to it.
|
||||
#[get("/description")]
|
||||
pub(crate) fn description(description: State<NodeDescription>) -> Json<NodeDescription> {
|
||||
Json(description.inner().clone())
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
use rocket::Request;
|
||||
|
||||
pub(crate) mod description;
|
||||
pub(crate) mod verloc;
|
||||
|
||||
use rocket::Request;
|
||||
|
||||
#[catch(404)]
|
||||
pub(crate) fn not_found(req: &Request) -> String {
|
||||
format!("I couldn't find '{}'. Try something else?", req.uri())
|
||||
|
||||
+12
-4
@@ -3,12 +3,14 @@
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::node::http::{
|
||||
description::description,
|
||||
not_found,
|
||||
verloc::{verloc, VerlocState},
|
||||
};
|
||||
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
|
||||
use crate::node::listener::connection_handler::ConnectionHandler;
|
||||
use crate::node::listener::Listener;
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use log::{error, info, warn};
|
||||
@@ -21,11 +23,13 @@ use version_checker::parse_version;
|
||||
pub(crate) mod http;
|
||||
mod listener;
|
||||
mod metrics;
|
||||
pub(crate) mod node_description;
|
||||
pub(crate) mod packet_delayforwarder;
|
||||
|
||||
// the MixNode will live for whole duration of this program
|
||||
pub struct MixNode {
|
||||
config: Config,
|
||||
descriptor: NodeDescription,
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
}
|
||||
@@ -33,31 +37,35 @@ pub struct MixNode {
|
||||
impl MixNode {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
descriptor: NodeDescription,
|
||||
identity_keypair: identity::KeyPair,
|
||||
sphinx_keypair: encryption::KeyPair,
|
||||
) -> Self {
|
||||
MixNode {
|
||||
config,
|
||||
descriptor,
|
||||
identity_keypair: Arc::new(identity_keypair),
|
||||
sphinx_keypair: Arc::new(sphinx_keypair),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_http_api(&self, atomic_verloc_result: AtomicVerlocResult) {
|
||||
info!("Starting HTTP API on port 8000...");
|
||||
info!("Starting HTTP API on http://localhost:8000");
|
||||
|
||||
let mut config = rocket::config::Config::release_default();
|
||||
// bind to the same address as we are using for mixnodes
|
||||
config.address = self.config.get_listening_address().ip();
|
||||
|
||||
let state = VerlocState::new(atomic_verloc_result);
|
||||
let verloc_state = VerlocState::new(atomic_verloc_result);
|
||||
let descriptor = self.descriptor.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
rocket::build()
|
||||
.configure(config)
|
||||
.mount("/", routes![verloc])
|
||||
.mount("/", routes![verloc, description])
|
||||
.register("/", catchers![not_found])
|
||||
.manage(state)
|
||||
.manage(verloc_state)
|
||||
.manage(descriptor)
|
||||
.launch()
|
||||
.await
|
||||
});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, io};
|
||||
|
||||
pub(crate) const DESCRIPTION_FILE: &str = "description.toml";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct NodeDescription {
|
||||
pub(crate) name: String,
|
||||
pub(crate) description: String,
|
||||
pub(crate) link: String,
|
||||
}
|
||||
|
||||
impl Default for NodeDescription {
|
||||
fn default() -> Self {
|
||||
NodeDescription {
|
||||
name: "This node has not yet set a name".to_string(),
|
||||
description: "This node has not yet set a description".to_string(),
|
||||
link: "https://nymtech.net".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeDescription {
|
||||
pub(crate) fn load_from_file(config_path: PathBuf) -> io::Result<NodeDescription> {
|
||||
let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
|
||||
.iter()
|
||||
.collect();
|
||||
let toml = fs::read_to_string(description_file_path)?;
|
||||
toml::from_str(&toml).map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
}
|
||||
|
||||
pub(crate) fn save_to_file(
|
||||
description: &NodeDescription,
|
||||
config_path: PathBuf,
|
||||
) -> io::Result<()> {
|
||||
let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
|
||||
.iter()
|
||||
.collect();
|
||||
let description_toml =
|
||||
toml::to_string(description).expect("could not encode description to toml");
|
||||
fs::write(description_file_path, description_toml)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user