Feature/upgrade command (#381)

* Exposed version parsing from version checker

* Ability to upgrade mixnode from 0.8.X to 0.9.0

* Ability to upgrade gateway from 0.8.X to 0.9.0

* Ability to upgrade native client from 0.8.X to 0.9.0

* Ability to upgrade socks5 client from 0.8.X to 0.9.0

* Typo

* Minor renaming

* Preventing upgrade if current is not a release version

* Additional upgrade restrictions

* Corrected version bound
This commit is contained in:
Jędrzej Stuczyński
2020-10-14 10:36:10 +01:00
committed by GitHub
parent e7bd27a2d0
commit c8bf454ccc
23 changed files with 766 additions and 9 deletions
Generated
+4
View File
@@ -1550,6 +1550,7 @@ dependencies = [
"tokio-tungstenite",
"topology",
"url",
"version-checker",
"websocket-requests",
]
@@ -1601,6 +1602,7 @@ dependencies = [
"tokio-tungstenite",
"tokio-util",
"tungstenite",
"version-checker",
]
[[package]]
@@ -1627,6 +1629,7 @@ dependencies = [
"tokio",
"tokio-util",
"topology",
"version-checker",
]
[[package]]
@@ -1656,6 +1659,7 @@ dependencies = [
"tempfile",
"tokio",
"topology",
"version-checker",
]
[[package]]
+16
View File
@@ -21,6 +21,8 @@ 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'
@@ -39,6 +41,10 @@ const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000;
const ZERO_DELAY: Duration = Duration::from_nanos(0);
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config<T> {
@@ -140,6 +146,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()
}
@@ -251,6 +261,10 @@ impl<T: NymConfig> Config<T> {
),
}
}
pub fn get_version(&self) -> &str {
&self.client.version
}
}
impl<T: NymConfig> Default for Config<T> {
@@ -267,6 +281,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 +293,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.
+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
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;
+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
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};
+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()),
}
}
+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::*;
+1
View File
@@ -31,6 +31,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"
+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)
}
}
+16
View File
@@ -24,6 +24,8 @@ use std::time;
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;
@@ -90,6 +92,10 @@ impl NymConfig for Config {
}
}
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 +328,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())
@@ -402,12 +413,17 @@ impl Config {
pub fn get_cache_entry_ttl(&self) -> time::Duration {
time::Duration::from_millis(self.debug.cache_entry_ttl)
}
pub fn get_version(&self) -> &str {
&self.gateway.version
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[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.
+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()),
}
}
+1
View File
@@ -28,6 +28,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"
+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;
+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 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 {
// 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 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_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)
}
}
+16
View File
@@ -24,6 +24,8 @@ use std::time;
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";
@@ -84,6 +86,10 @@ impl NymConfig for Config {
}
}
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)
@@ -198,6 +204,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())
@@ -262,12 +273,17 @@ impl Config {
pub fn get_cache_entry_ttl(&self) -> time::Duration {
time::Duration::from_millis(self.debug.cache_entry_ttl)
}
pub fn get_version(&self) -> &str {
&self.mixnode.version
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[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.
+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()),
}
}