use anyhow::{bail, Error};
use exonum::{
blockchain::ValidatorKeys,
keys::{generate_keys, Keys},
merkledb::DbOptions,
};
use exonum_node::{ConnectListConfig, MemoryPoolConfig, NetworkConfiguration, NodeApiConfig};
use serde_derive::{Deserialize, Serialize};
use structopt::StructOpt;
use std::{
fs,
net::{IpAddr, SocketAddr},
path::{Path, PathBuf},
};
use crate::{
command::{ExonumCommand, StandardResult},
config::{NodePrivateConfig, NodePublicConfig},
io::{load_config_file, save_config_file},
password::{PassInputMethod, Passphrase, PassphraseUsage},
};
pub const PUBLIC_CONFIG_FILE_NAME: &str = "pub.toml";
pub const PRIVATE_CONFIG_FILE_NAME: &str = "sec.toml";
pub const MASTER_KEY_FILE_NAME: &str = "master.key.toml";
pub const DEFAULT_EXONUM_LISTEN_PORT: u16 = 6333;
#[derive(StructOpt, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GenerateConfig {
pub common_config: PathBuf,
pub output_dir: PathBuf,
#[structopt(
long,
short = "a",
parse(try_from_str = GenerateConfig::parse_external_address)
)]
pub peer_address: SocketAddr,
#[structopt(long, short = "l")]
pub listen_address: Option<SocketAddr>,
#[structopt(long, short = "n")]
pub no_password: bool,
#[structopt(long)]
pub master_key_pass: Option<PassInputMethod>,
#[structopt(long)]
pub master_key_path: Option<PathBuf>,
}
impl GenerateConfig {
fn get_passphrase(no_password: bool, method: PassInputMethod) -> Result<Passphrase, Error> {
if no_password {
Ok(Passphrase::default())
} else {
method.get_passphrase(PassphraseUsage::SettingUp)
}
}
fn parse_external_address(input: &str) -> Result<SocketAddr, Error> {
if let Ok(address) = input.parse() {
Ok(address)
} else {
let ip_address = input.parse()?;
Ok(SocketAddr::new(ip_address, DEFAULT_EXONUM_LISTEN_PORT))
}
}
fn get_listen_address(
provided: Option<SocketAddr>,
external_address: SocketAddr,
) -> SocketAddr {
if let Some(provided) = provided {
provided
} else {
let ip_address = match external_address.ip() {
IpAddr::V4(_) => "0.0.0.0".parse().unwrap(),
IpAddr::V6(_) => "::".parse().unwrap(),
};
SocketAddr::new(ip_address, external_address.port())
}
}
}
impl ExonumCommand for GenerateConfig {
fn execute(self) -> Result<StandardResult, Error> {
let common_config: NodePublicConfig = load_config_file(&self.common_config)?;
let public_config_path = self.output_dir.join(PUBLIC_CONFIG_FILE_NAME);
let private_config_path = self.output_dir.join(PRIVATE_CONFIG_FILE_NAME);
let master_key_path = get_master_key_path(self.master_key_path.clone())?;
let listen_address = Self::get_listen_address(self.listen_address, self.peer_address);
let keys = {
let passphrase =
Self::get_passphrase(self.no_password, self.master_key_pass.unwrap_or_default())?;
create_keys_and_files(
&self.output_dir.join(master_key_path.clone()),
passphrase.as_bytes(),
)
}?;
let validator_keys = ValidatorKeys::new(keys.consensus_pk(), keys.service_pk());
let public_config = NodePublicConfig {
validator_keys: Some(validator_keys),
address: Some(self.peer_address.to_string()),
..common_config
};
save_config_file(&public_config, &public_config_path)?;
let private_config = NodePrivateConfig {
listen_address,
external_address: self.peer_address.to_string(),
master_key_path: master_key_path.clone(),
api: NodeApiConfig::default(),
network: NetworkConfiguration::default(),
mempool: MemoryPoolConfig::default(),
database: DbOptions::default(),
thread_pool_size: None,
connect_list: ConnectListConfig::default(),
consensus_public_key: keys.consensus_pk(),
};
save_config_file(&private_config, &private_config_path)?;
Ok(StandardResult::GenerateConfig {
public_config_path,
private_config_path,
master_key_path,
})
}
}
fn get_master_key_path(path: Option<PathBuf>) -> Result<PathBuf, Error> {
let path = path.map_or_else(|| Ok(PathBuf::new()), |path| path.canonicalize())?;
Ok(path.join(MASTER_KEY_FILE_NAME))
}
fn create_keys_and_files(
secret_key_path: impl AsRef<Path>,
passphrase: impl AsRef<[u8]>,
) -> anyhow::Result<Keys> {
let secret_key_path = secret_key_path.as_ref();
if secret_key_path.exists() {
bail!(
"Failed to create secret key file. File exists: {}",
secret_key_path.to_string_lossy(),
)
} else {
if let Some(dir) = secret_key_path.parent() {
fs::create_dir_all(dir)?;
}
generate_keys(&secret_key_path, passphrase.as_ref())
}
}