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:
Dave Hrycyszyn
2021-05-19 09:25:11 -04:00
committed by GitHub
parent ea62d01e65
commit 190d3c4972
10 changed files with 149 additions and 10 deletions
Generated
+5 -2
View File
@@ -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"
+1
View File
@@ -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" }
+65
View File
@@ -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()
}
+1
View File
@@ -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;
+4 -1
View File
@@ -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
View File
@@ -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),
+9
View File
@@ -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())
}
+3 -2
View File
@@ -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
View File
@@ -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
});
+46
View File
@@ -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(())
}
}