#![allow(missing_debug_implementations)]
use std::fs;
use std::path::{Path, PathBuf};
use std::net::SocketAddr;
use std::collections::{BTreeMap, HashMap};
use toml::Value;
use blockchain::GenesisConfig;
use blockchain::config::ValidatorKeys;
use helpers::generate_testnet_config;
use helpers::config::ConfigFile;
use node::{NodeApiConfig, NodeConfig};
use storage::Database;
use crypto;
use super::internal::{CollectedCommand, Command, Feedback};
use super::{Argument, CommandName, Context};
use super::shared::{AbstractConfig, CommonConfigTemplate, NodePrivateConfig, NodePublicConfig,
SharedConfig};
use super::DEFAULT_EXONUM_LISTEN_PORT;
use super::keys;
const DATABASE_PATH: &str = "DATABASE_PATH";
const OUTPUT_DIR: &str = "OUTPUT_DIR";
const PEER_ADDRESS: &str = "PEER_ADDRESS";
pub struct Run;
impl Run {
pub fn name() -> CommandName {
"run"
}
pub fn db_helper(ctx: &Context) -> Box<Database> {
use storage::{RocksDB, RocksDBOptions};
let path = ctx.arg::<String>(DATABASE_PATH).expect(&format!(
"{} not found.",
DATABASE_PATH
));
let mut options = RocksDBOptions::default();
options.create_if_missing(true);
Box::new(RocksDB::open(Path::new(&path), &options).unwrap())
}
fn node_config(ctx: &Context) -> NodeConfig {
let path = ctx.arg::<String>("NODE_CONFIG_PATH").expect(
"NODE_CONFIG_PATH not found.",
);
ConfigFile::load(path).unwrap()
}
fn public_api_address(ctx: &Context) -> Option<SocketAddr> {
ctx.arg("PUBLIC_API_ADDRESS").ok()
}
fn private_api_address(ctx: &Context) -> Option<SocketAddr> {
ctx.arg("PRIVATE_API_ADDRESS").ok()
}
}
impl Command for Run {
fn args(&self) -> Vec<Argument> {
vec![
Argument::new_named(
"NODE_CONFIG_PATH",
true,
"Path to node configuration file.",
"c",
"node-config",
false
),
Argument::new_named(
DATABASE_PATH,
true,
"Use database with the given path.",
"d",
"db-path",
false
),
Argument::new_named(
"PUBLIC_API_ADDRESS",
false,
"Listen address for public api.",
None,
"public-api-address",
false
),
Argument::new_named(
"PRIVATE_API_ADDRESS",
false,
"Listen address for private api.",
None,
"private-api-address",
false
),
]
}
fn name(&self) -> CommandName {
Self::name()
}
fn about(&self) -> &str {
"Run application"
}
fn execute(
&self,
_commands: &HashMap<CommandName, CollectedCommand>,
mut context: Context,
exts: &Fn(Context) -> Context,
) -> Feedback {
let config = Self::node_config(&context);
let public_addr = Self::public_api_address(&context);
let private_addr = Self::private_api_address(&context);
context.set(keys::NODE_CONFIG, config);
let mut new_context = exts(context);
let mut config = new_context.get(keys::NODE_CONFIG).expect(
"cant load node_config",
);
if let Some(public_addr) = public_addr {
config.api.public_api_address = Some(public_addr);
}
if let Some(private_api_address) = private_addr {
config.api.private_api_address = Some(private_api_address);
}
new_context.set(keys::NODE_CONFIG, config);
Feedback::RunNode(new_context)
}
}
pub struct RunDev;
impl RunDev {
pub fn name() -> CommandName {
"run-dev"
}
fn artifacts_directory(ctx: &Context) -> PathBuf {
let directory = ctx.arg::<String>("ARTIFACTS_DIR").unwrap_or_else(
|_| ".exonum".into(),
);
PathBuf::from(&directory)
}
fn artifacts_path(inner_path: &str, ctx: &Context) -> String {
let mut path = Self::artifacts_directory(ctx);
path.push(inner_path);
path.to_str().expect("Expected correct path").into()
}
fn generate_config(commands: &HashMap<CommandName, CollectedCommand>, ctx: &Context) -> String {
let common_config_path = Self::artifacts_path("common.toml", ctx);
let peer_addr = "127.0.0.1";
let pub_config_path = Self::artifacts_path("public.toml", ctx);
let sec_config_path = Self::artifacts_path("secret.toml", ctx);
let output_config_path = Self::artifacts_path("output.toml", ctx);
let mut common_config_ctx = ctx.clone();
common_config_ctx.set_arg("COMMON_CONFIG", common_config_path.clone());
let common_config_command = commands.get(GenerateCommonConfig::name()).expect(
"Expected GenerateCommonConfig in the commands list.",
);
common_config_command.execute(commands, common_config_ctx);
let mut node_config_ctx = ctx.clone();
node_config_ctx.set_arg("COMMON_CONFIG", common_config_path.clone());
node_config_ctx.set_arg("PUB_CONFIG", pub_config_path.clone());
node_config_ctx.set_arg("SEC_CONFIG", sec_config_path.clone());
node_config_ctx.set_arg(PEER_ADDRESS, peer_addr.into());
let node_config_command = commands.get(GenerateNodeConfig::name()).expect(
"Expected GenerateNodeConfig in the commands list.",
);
node_config_command.execute(commands, node_config_ctx);
let mut finalize_ctx = ctx.clone();
finalize_ctx.set_arg_multiple("PUBLIC_CONFIGS", vec![pub_config_path.clone()]);
finalize_ctx.set_arg("SECRET_CONFIG", sec_config_path.clone());
finalize_ctx.set_arg("OUTPUT_CONFIG_PATH", output_config_path.clone());
let finalize_command = commands.get(Finalize::name()).expect(
"Expected Finalize in the commands list.",
);
finalize_command.execute(commands, finalize_ctx);
output_config_path
}
fn cleanup(ctx: &Context) {
let database_dir_path = ctx.arg::<String>(DATABASE_PATH).expect(
"Expected DATABASE_PATH being set.",
);
let database_dir = Path::new(&database_dir_path);
if database_dir.exists() {
fs::remove_dir_all(Self::artifacts_directory(ctx)).expect(
"Expected DATABASE_PATH folder being removable.",
);
}
}
}
impl Command for RunDev {
fn args(&self) -> Vec<Argument> {
vec![
Argument::new_named(
"ARTIFACTS_DIR",
false,
"The path where configuration and db files will be generated.",
"a",
"artifacts-dir",
false
),
]
}
fn name(&self) -> CommandName {
Self::name()
}
fn about(&self) -> &str {
"Run application in development mode (generate configuration and db files automatically)"
}
fn execute(
&self,
commands: &HashMap<CommandName, CollectedCommand>,
mut context: Context,
exts: &Fn(Context) -> Context,
) -> Feedback {
let db_path = Self::artifacts_path("db", &context);
context.set_arg(DATABASE_PATH, db_path);
Self::cleanup(&context);
let node_config_path = Self::generate_config(commands, &context);
context.set_arg("NODE_CONFIG_PATH", node_config_path);
let new_context = exts(context);
commands
.get(Run::name())
.expect("Expected Run in the commands list.")
.execute(commands, new_context.clone())
}
}
pub struct GenerateCommonConfig;
impl GenerateCommonConfig {
pub fn name() -> CommandName {
"generate-template"
}
}
impl Command for GenerateCommonConfig {
fn args(&self) -> Vec<Argument> {
vec![Argument::new_positional("COMMON_CONFIG", true, "Path to common config.")]
}
fn name(&self) -> CommandName {
Self::name()
}
fn about(&self) -> &str {
"Generate basic config template."
}
fn execute(
&self,
_commands: &HashMap<CommandName, CollectedCommand>,
mut context: Context,
exts: &Fn(Context) -> Context,
) -> Feedback {
let template_path = context.arg::<String>("COMMON_CONFIG").expect(
"COMMON_CONFIG not found",
);
context.set(keys::SERVICES_CONFIG, AbstractConfig::default());
let new_context = exts(context);
let services_config = new_context.get(keys::SERVICES_CONFIG).unwrap_or_default();
let template = CommonConfigTemplate {
services_config,
..CommonConfigTemplate::default()
};
ConfigFile::save(&template, template_path).expect("Could not write template file.");
Feedback::None
}
}
pub struct GenerateNodeConfig;
impl GenerateNodeConfig {
pub fn name() -> CommandName {
"generate-config"
}
fn addr(context: &Context) -> (SocketAddr, SocketAddr) {
let addr = context.arg::<String>(PEER_ADDRESS).unwrap_or_default();
let mut addr_parts = addr.split(':');
let ip = addr_parts.next().expect("Expected ip address");
if ip.len() < 8 {
panic!("Expected ip address in {}.", PEER_ADDRESS);
}
let port = addr_parts.next().map_or(DEFAULT_EXONUM_LISTEN_PORT, |s| {
s.parse().expect("could not parse port")
});
let external_address = format!("{}:{}", ip, port);
let listen_address = format!("0.0.0.0:{}", port);
(
external_address.parse().unwrap(),
listen_address.parse().unwrap(),
)
}
}
impl Command for GenerateNodeConfig {
fn args(&self) -> Vec<Argument> {
vec![
Argument::new_positional("COMMON_CONFIG", true, "Path to common config."),
Argument::new_positional("PUB_CONFIG", true, "Path where save public config."),
Argument::new_positional("SEC_CONFIG", true, "Path where save private config."),
Argument::new_named(
PEER_ADDRESS,
true,
"Remote peer address",
"a",
"peer-address",
false
),
]
}
fn name(&self) -> CommandName {
Self::name()
}
fn about(&self) -> &str {
"Generate node secret and public configs."
}
fn execute(
&self,
_commands: &HashMap<CommandName, CollectedCommand>,
mut context: Context,
exts: &Fn(Context) -> Context,
) -> Feedback {
let common_config_path = context.arg::<String>("COMMON_CONFIG").expect(
"expected common config path",
);
let pub_config_path = context.arg::<String>("PUB_CONFIG").expect(
"expected public config path",
);
let private_config_path = context.arg::<String>("SEC_CONFIG").expect(
"expected secret config path",
);
let addr = Self::addr(&context);
let common: CommonConfigTemplate =
ConfigFile::load(&common_config_path).expect("Could not load common config");
context.set(keys::COMMON_CONFIG, common.clone());
context.set(
keys::SERVICES_PUBLIC_CONFIGS,
BTreeMap::<String, Value>::default(),
);
context.set(
keys::SERVICES_SECRET_CONFIGS,
BTreeMap::<String, Value>::default(),
);
let new_context = exts(context);
let services_public_configs = new_context.get(keys::SERVICES_PUBLIC_CONFIGS).unwrap();
let services_secret_configs = new_context.get(keys::SERVICES_SECRET_CONFIGS);
let (consensus_public_key, consensus_secret_key) = crypto::gen_keypair();
let (service_public_key, service_secret_key) = crypto::gen_keypair();
let validator_keys = ValidatorKeys {
consensus_key: consensus_public_key,
service_key: service_public_key,
};
let node_pub_config = NodePublicConfig {
addr: addr.0,
validator_keys,
services_public_configs,
};
let shared_config = SharedConfig {
node: node_pub_config,
common: common,
};
ConfigFile::save(&shared_config, &pub_config_path).expect(
"Could not write public config file.",
);
let private_config = NodePrivateConfig {
listen_addr: addr.1,
consensus_public_key,
consensus_secret_key,
service_public_key,
service_secret_key,
services_secret_configs: services_secret_configs.expect(
"services_secret_configs not found after exts call",
),
};
ConfigFile::save(&private_config, private_config_path)
.expect("Could not write secret config file.");
Feedback::None
}
}
pub struct Finalize;
impl Finalize {
pub fn name() -> CommandName {
"finalize"
}
fn genesis_from_template(
template: CommonConfigTemplate,
configs: &[NodePublicConfig],
) -> GenesisConfig {
GenesisConfig::new_with_consensus(
template.consensus_config,
configs.iter().map(|c| c.validator_keys),
)
}
fn reduce_configs(
public_configs: Vec<SharedConfig>,
our_config: &NodePrivateConfig,
) -> (CommonConfigTemplate, Vec<NodePublicConfig>, Option<NodePublicConfig>) {
let mut map = BTreeMap::new();
let mut config_iter = public_configs.into_iter();
let first = config_iter.next().expect(
"Expected at least one config in PUBLIC_CONFIGS",
);
let common = first.common;
map.insert(first.node.validator_keys.consensus_key, first.node);
for config in config_iter {
if common != config.common {
panic!("Found config with different common part.");
};
if map.insert(config.node.validator_keys.consensus_key, config.node)
.is_some()
{
panic!("Found duplicate consensus keys in PUBLIC_CONFIGS");
}
}
(
common,
map.iter().map(|(_, c)| c.clone()).collect(),
map.get(&our_config.consensus_public_key).cloned(),
)
}
}
impl Command for Finalize {
fn args(&self) -> Vec<Argument> {
vec![
Argument::new_named(
"PUBLIC_CONFIGS",
true,
"Path to validators public configs",
"p",
"public-configs",
true
),
Argument::new_named(
"PUBLIC_API_ADDRESS",
false,
"Listen address for public api.",
None,
"public-api-address",
false
),
Argument::new_named(
"PRIVATE_API_ADDRESS",
false,
"Listen address for private api.",
None,
"private-api-address",
false
),
Argument::new_positional("SECRET_CONFIG", true, "Path to our secret config."),
Argument::new_positional("OUTPUT_CONFIG_PATH", true, "Path to output node config."),
]
}
fn name(&self) -> CommandName {
Self::name()
}
fn about(&self) -> &str {
"Collect public and secret configs into node config."
}
fn execute(
&self,
_commands: &HashMap<CommandName, CollectedCommand>,
mut context: Context,
exts: &Fn(Context) -> Context,
) -> Feedback {
let public_configs_path = context.arg_multiple::<String>("PUBLIC_CONFIGS").expect(
"public config path not found",
);
let secret_config_path = context.arg::<String>("SECRET_CONFIG").expect(
"private config path not found",
);
let output_config_path = context.arg::<String>("OUTPUT_CONFIG_PATH").expect(
"output config path not found",
);
let public_addr = Run::public_api_address(&context);
let private_addr = Run::private_api_address(&context);
let secret_config: NodePrivateConfig =
ConfigFile::load(secret_config_path).expect("Failed to load key config.");
let public_configs: Vec<SharedConfig> = public_configs_path
.into_iter()
.map(|path| {
ConfigFile::load(path).expect("Failed to load validator public config.")
})
.collect();
let (common, list, our) = Self::reduce_configs(public_configs, &secret_config);
context.set(keys::AUDITOR_MODE, our.is_none());
let peers = list.iter().map(|c| c.addr).collect();
let genesis = Self::genesis_from_template(common.clone(), &list);
let config = {
NodeConfig {
listen_address: secret_config.listen_addr,
external_address: our.map(|o| o.addr),
network: Default::default(),
whitelist: Default::default(),
peers: peers,
consensus_public_key: secret_config.consensus_public_key,
consensus_secret_key: secret_config.consensus_secret_key,
service_public_key: secret_config.service_public_key,
service_secret_key: secret_config.service_secret_key,
genesis: genesis,
api: NodeApiConfig {
public_api_address: public_addr,
private_api_address: private_addr,
..Default::default()
},
mempool: Default::default(),
services_configs: Default::default(),
}
};
context.set(keys::PUBLIC_CONFIG_LIST, list);
context.set(keys::NODE_CONFIG, config);
context.set(keys::COMMON_CONFIG, common);
context.set(
keys::SERVICES_SECRET_CONFIGS,
secret_config.services_secret_configs,
);
let new_context = exts(context);
let config = new_context.get(keys::NODE_CONFIG).expect(
"Could not create config from template, services return error",
);
ConfigFile::save(&config, output_config_path).expect("Could not write config file.");
Feedback::None
}
}
pub struct GenerateTestnet;
impl GenerateTestnet {
pub fn name() -> CommandName {
"generate-testnet"
}
}
impl Command for GenerateTestnet {
fn args(&self) -> Vec<Argument> {
vec![
Argument::new_named(
OUTPUT_DIR,
true,
"Path to directory where save configs.",
"o",
"output-dir",
false
),
Argument::new_named(
"START_PORT",
false,
"Port number started from which should validators listen.",
"p",
"start",
false
),
Argument::new_positional("COUNT", true, "Count of validators in testnet."),
]
}
fn name(&self) -> CommandName {
Self::name()
}
fn about(&self) -> &str {
"Generates genesis configuration for testnet"
}
fn execute(
&self,
_commands: &HashMap<CommandName, CollectedCommand>,
mut context: Context,
exts: &Fn(Context) -> Context,
) -> Feedback {
let dir = context.arg::<String>(OUTPUT_DIR).expect("output dir");
let count: u8 = context.arg("COUNT").expect("count as int");
let start_port = context.arg::<u16>("START_PORT").unwrap_or(
DEFAULT_EXONUM_LISTEN_PORT,
);
if count == 0 {
panic!("Can't generate testnet with zero nodes count.");
}
let dir = Path::new(&dir);
let dir = dir.join("validators");
if !dir.exists() {
fs::create_dir_all(&dir).unwrap();
}
let configs = generate_testnet_config(count, start_port);
context.set(keys::CONFIGS, configs);
let new_context = exts(context);
let configs = new_context.get(keys::CONFIGS).expect(
"Couldn't read testnet configs after exts call.",
);
for (idx, cfg) in configs.into_iter().enumerate() {
let file_name = format!("{}.toml", idx);
ConfigFile::save(&cfg, &dir.join(file_name)).unwrap();
}
Feedback::None
}
}