// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
use std::{io, env};
use std::io::{Write, Read, BufReader, BufRead};
use std::time::Duration;
use std::path::Path;
use std::fs::File;
use util::{clean_0x, U256, Uint, Address, path, CompactionProfile};
use util::journaldb::Algorithm;
use ethcore::client::{Mode, BlockID, VMType, DatabaseCompactionProfile, ClientConfig};
use ethcore::miner::PendingSet;
use cache::CacheConfig;
use dir::DatabaseDirectories;
use upgrade::upgrade;
use migration::migrate;
use ethsync::is_valid_node_url;
pub fn to_duration(s: &str) -> Result {
to_seconds(s).map(Duration::from_secs)
}
fn to_seconds(s: &str) -> Result {
let bad = |_| {
format!("{}: Invalid duration given. See parity --help for more information.", s)
};
match s {
"twice-daily" => Ok(12 * 60 * 60),
"half-hourly" => Ok(30 * 60),
"1second" | "1 second" | "second" => Ok(1),
"1minute" | "1 minute" | "minute" => Ok(60),
"hourly" | "1hour" | "1 hour" | "hour" => Ok(60 * 60),
"daily" | "1day" | "1 day" | "day" => Ok(24 * 60 * 60),
x if x.ends_with("seconds") => x[0..x.len() - 7].parse().map_err(bad),
x if x.ends_with("minutes") => x[0..x.len() -7].parse::().map_err(bad).map(|x| x * 60),
x if x.ends_with("hours") => x[0..x.len() - 5].parse::().map_err(bad).map(|x| x * 60 * 60),
x if x.ends_with("days") => x[0..x.len() - 4].parse::().map_err(bad).map(|x| x * 24 * 60 * 60),
x => x.parse().map_err(bad),
}
}
pub fn to_mode(s: &str, timeout: u64, alarm: u64) -> Result {
match s {
"active" => Ok(Mode::Active),
"passive" => Ok(Mode::Passive(Duration::from_secs(timeout), Duration::from_secs(alarm))),
"dark" => Ok(Mode::Dark(Duration::from_secs(timeout))),
_ => Err(format!("{}: Invalid address for --mode. Must be one of active, passive or dark.", s)),
}
}
pub fn to_block_id(s: &str) -> Result {
if s == "latest" {
Ok(BlockID::Latest)
} else if let Ok(num) = s.parse() {
Ok(BlockID::Number(num))
} else if let Ok(hash) = s.parse() {
Ok(BlockID::Hash(hash))
} else {
Err("Invalid block.".into())
}
}
pub fn to_u256(s: &str) -> Result {
if let Ok(decimal) = U256::from_dec_str(s) {
Ok(decimal)
} else if let Ok(hex) = clean_0x(s).parse() {
Ok(hex)
} else {
Err(format!("Invalid numeric value: {}", s))
}
}
pub fn to_pending_set(s: &str) -> Result {
match s {
"cheap" => Ok(PendingSet::AlwaysQueue),
"strict" => Ok(PendingSet::AlwaysSealing),
"lenient" => Ok(PendingSet::SealingOrElseQueue),
other => Err(format!("Invalid pending set value: {:?}", other)),
}
}
pub fn to_address(s: Option) -> Result {
match s {
Some(ref a) => clean_0x(a).parse().map_err(|_| format!("Invalid address: {:?}", a)),
None => Ok(Address::default())
}
}
pub fn to_addresses(s: &Option) -> Result, String> {
match *s {
Some(ref adds) if !adds.is_empty() => adds.split(',')
.map(|a| clean_0x(a).parse().map_err(|_| format!("Invalid address: {:?}", a)))
.collect(),
_ => Ok(Vec::new()),
}
}
/// Tries to parse string as a price.
pub fn to_price(s: &str) -> Result {
s.parse::().map_err(|_| format!("Invalid transaciton price 's' given. Must be a decimal number."))
}
/// Replaces `$HOME` str with home directory path.
pub fn replace_home(arg: &str) -> String {
// the $HOME directory on mac os should be `~/Library` or `~/Library/Application Support`
let r = arg.replace("$HOME", env::home_dir().unwrap().to_str().unwrap());
r.replace("/", &::std::path::MAIN_SEPARATOR.to_string() )
}
/// Flush output buffer.
pub fn flush_stdout() {
io::stdout().flush().expect("stdout is flushable; qed");
}
/// Returns default geth ipc path.
pub fn geth_ipc_path(testnet: bool) -> String {
// Windows path should not be hardcoded here.
// Instead it should be a part of path::ethereum
if cfg!(windows) {
return r"\\.\pipe\geth.ipc".to_owned();
}
if testnet {
path::ethereum::with_testnet("geth.ipc").to_str().unwrap().to_owned()
} else {
path::ethereum::with_default("geth.ipc").to_str().unwrap().to_owned()
}
}
/// Formats and returns parity ipc path.
pub fn parity_ipc_path(s: &str) -> String {
// Windows path should not be hardcoded here.
if cfg!(windows) {
return r"\\.\pipe\parity.jsonrpc".to_owned();
}
replace_home(s)
}
/// Validates and formats bootnodes option.
pub fn to_bootnodes(bootnodes: &Option) -> Result, String> {
match *bootnodes {
Some(ref x) if !x.is_empty() => x.split(',').map(|s| {
if is_valid_node_url(s) {
Ok(s.to_owned())
} else {
Err(format!("Invalid node address format given for a boot node: {}", s))
}
}).collect(),
Some(_) => Ok(vec![]),
None => Ok(vec![])
}
}
#[cfg(test)]
pub fn default_network_config() -> ::ethsync::NetworkConfiguration {
use ethsync::NetworkConfiguration;
NetworkConfiguration {
config_path: Some(replace_home("$HOME/.parity/network")),
net_config_path: Some(replace_home("$HOME/.parity/network/1")),
listen_address: Some("0.0.0.0:30303".into()),
public_address: None,
udp_port: None,
nat_enabled: true,
discovery_enabled: true,
boot_nodes: Vec::new(),
use_secret: None,
max_peers: 50,
min_peers: 25,
reserved_nodes: Vec::new(),
allow_non_reserved: true,
}
}
#[cfg_attr(feature = "dev", allow(too_many_arguments))]
pub fn to_client_config(
cache_config: &CacheConfig,
mode: Mode,
tracing: bool,
fat_db: bool,
compaction: DatabaseCompactionProfile,
wal: bool,
vm_type: VMType,
name: String,
pruning: Algorithm,
) -> ClientConfig {
let mut client_config = ClientConfig::default();
let mb = 1024 * 1024;
// in bytes
client_config.blockchain.max_cache_size = cache_config.blockchain() as usize * mb;
// in bytes
client_config.blockchain.pref_cache_size = cache_config.blockchain() as usize * 3 / 4 * mb;
// db blockchain cache size, in megabytes
client_config.blockchain.db_cache_size = Some(cache_config.db_blockchain_cache_size() as usize);
// db state cache size, in megabytes
client_config.db_cache_size = Some(cache_config.db_state_cache_size() as usize);
// db queue cache size, in bytes
client_config.queue.max_mem_use = cache_config.queue() as usize * mb;
// in bytes
client_config.tracing.max_cache_size = cache_config.traces() as usize * mb;
// in bytes
client_config.tracing.pref_cache_size = cache_config.traces() as usize * 3 / 4 * mb;
client_config.mode = mode;
client_config.tracing.enabled = tracing;
client_config.fat_db = fat_db;
client_config.pruning = pruning;
client_config.db_compaction = compaction;
client_config.db_wal = wal;
client_config.vm_type = vm_type;
client_config.name = name;
client_config
}
pub fn execute_upgrades(
dirs: &DatabaseDirectories,
pruning: Algorithm,
compaction_profile: CompactionProfile
) -> Result<(), String> {
match upgrade(Some(&dirs.path)) {
Ok(upgrades_applied) if upgrades_applied > 0 => {
debug!("Executed {} upgrade scripts - ok", upgrades_applied);
},
Err(e) => {
return Err(format!("Error upgrading parity data: {:?}", e));
},
_ => {},
}
let client_path = dirs.version_path(pruning);
migrate(&client_path, pruning, compaction_profile).map_err(|e| format!("{}", e))
}
/// Prompts user asking for password.
pub fn password_prompt() -> Result {
use rpassword::read_password;
println!("Please note that password is NOT RECOVERABLE.");
print!("Type password: ");
flush_stdout();
let password = read_password().unwrap();
print!("Repeat password: ");
flush_stdout();
let password_repeat = read_password().unwrap();
if password != password_repeat {
return Err("Passwords do not match!".into());
}
Ok(password)
}
/// Read a password from password file.
pub fn password_from_file