// 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::time::Duration;
use std::io::Read;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::cmp::max;
use cli::{USAGE, Args};
use docopt::{Docopt, Error as DocoptError};
use util::{Hashable, NetworkConfiguration, U256, Uint, is_valid_node_url, Bytes, version_data, Secret, Address};
use util::network_settings::NetworkSettings;
use util::log::Colour;
use ethcore::client::{VMType, Mode};
use ethcore::miner::MinerOptions;
use rpc::{IpcConfiguration, HttpConfiguration};
use cache::CacheConfig;
use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home,
geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address};
use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras};
use ethcore_logger::Config as LogConfig;
use dir::Directories;
use dapps::Configuration as DappsConfiguration;
use signer::Configuration as SignerConfiguration;
use run::RunCmd;
use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain};
use presale::ImportWallet;
use account::{AccountCmd, NewAccount, ImportAccounts};
#[derive(Debug, PartialEq)]
pub enum Cmd {
Run(RunCmd),
Version,
Account(AccountCmd),
ImportPresaleWallet(ImportWallet),
Blockchain(BlockchainCmd),
SignerToken(String),
}
#[derive(Debug, PartialEq)]
pub struct Configuration {
pub args: Args,
}
impl Configuration {
pub fn parse(command: I) -> Result where I: IntoIterator- , S: AsRef {
let args = try!(Docopt::new(USAGE).and_then(|d| d.argv(command).decode()));
let config = Configuration {
args: args,
};
Ok(config)
}
pub fn into_command(self) -> Result {
let dirs = self.directories();
let pruning = try!(self.args.flag_pruning.parse());
let vm_type = try!(self.vm_type());
let mode = try!(to_mode(&self.args.flag_mode, self.args.flag_mode_timeout, self.args.flag_mode_alarm));
let miner_options = try!(self.miner_options());
let logger_config = self.logger_config();
let http_conf = try!(self.http_config());
let ipc_conf = try!(self.ipc_config());
let net_conf = try!(self.net_config());
let network_id = try!(self.network_id());
let cache_config = self.cache_config();
let spec = try!(self.chain().parse());
let tracing = try!(self.args.flag_tracing.parse());
let compaction = try!(self.args.flag_db_compaction.parse());
let wal = !self.args.flag_fast_and_loose;
let enable_network = self.enable_network(&mode);
let geth_compatibility = self.args.flag_geth;
let signer_port = self.signer_port();
let dapps_conf = self.dapps_config();
let signer_conf = self.signer_config();
let cmd = if self.args.flag_version {
Cmd::Version
} else if self.args.cmd_signer {
Cmd::SignerToken(dirs.signer)
} else if self.args.cmd_account {
let account_cmd = if self.args.cmd_new {
let new_acc = NewAccount {
iterations: self.args.flag_keys_iterations,
path: dirs.keys,
password_file: self.args.flag_password.first().cloned(),
};
AccountCmd::New(new_acc)
} else if self.args.cmd_list {
AccountCmd::List(dirs.keys)
} else if self.args.cmd_import {
let import_acc = ImportAccounts {
from: self.args.arg_path.clone(),
to: dirs.keys,
};
AccountCmd::Import(import_acc)
} else {
unreachable!();
};
Cmd::Account(account_cmd)
} else if self.args.cmd_wallet {
let presale_cmd = ImportWallet {
iterations: self.args.flag_keys_iterations,
path: dirs.keys,
wallet_path: self.args.arg_path.first().unwrap().clone(),
password_file: self.args.flag_password.first().cloned(),
};
Cmd::ImportPresaleWallet(presale_cmd)
} else if self.args.cmd_import {
let import_cmd = ImportBlockchain {
spec: spec,
logger_config: logger_config,
cache_config: cache_config,
dirs: dirs,
file_path: self.args.arg_file.clone(),
format: None,
pruning: pruning,
compaction: compaction,
wal: wal,
mode: mode,
tracing: tracing,
vm_type: vm_type,
};
Cmd::Blockchain(BlockchainCmd::Import(import_cmd))
} else if self.args.cmd_export {
let export_cmd = ExportBlockchain {
spec: spec,
logger_config: logger_config,
cache_config: cache_config,
dirs: dirs,
file_path: self.args.arg_file.clone(),
format: None,
pruning: pruning,
compaction: compaction,
wal: wal,
mode: mode,
tracing: tracing,
from_block: try!(to_block_id(&self.args.flag_from)),
to_block: try!(to_block_id(&self.args.flag_to)),
};
Cmd::Blockchain(BlockchainCmd::Export(export_cmd))
} else {
let daemon = if self.args.cmd_daemon {
Some(self.args.arg_pid_file.clone())
} else {
None
};
let run_cmd = RunCmd {
cache_config: cache_config,
dirs: dirs,
spec: spec,
pruning: pruning,
daemon: daemon,
logger_config: logger_config,
miner_options: miner_options,
http_conf: http_conf,
ipc_conf: ipc_conf,
net_conf: net_conf,
network_id: network_id,
acc_conf: try!(self.accounts_config()),
gas_pricer: try!(self.gas_pricer_config()),
miner_extras: try!(self.miner_extras()),
mode: mode,
tracing: tracing,
compaction: compaction,
wal: wal,
vm_type: vm_type,
enable_network: enable_network,
geth_compatibility: geth_compatibility,
signer_port: signer_port,
net_settings: self.network_settings(),
dapps_conf: dapps_conf,
signer_conf: signer_conf,
ui: self.args.cmd_ui,
name: self.args.flag_identity,
custom_bootnodes: self.args.flag_bootnodes.is_some(),
};
Cmd::Run(run_cmd)
};
Ok(cmd)
}
fn enable_network(&self, mode: &Mode) -> bool {
match *mode {
Mode::Dark(_) => false,
_ => !self.args.flag_no_network,
}
}
fn vm_type(&self) -> Result {
if self.args.flag_jitvm {
VMType::jit().ok_or("Parity is built without the JIT EVM.".into())
} else {
Ok(VMType::Interpreter)
}
}
fn miner_extras(&self) -> Result {
let extras = MinerExtras {
author: try!(self.author()),
extra_data: try!(self.extra_data()),
gas_floor_target: try!(to_u256(&self.args.flag_gas_floor_target)),
gas_ceil_target: try!(to_u256(&self.args.flag_gas_cap)),
transactions_limit: self.args.flag_tx_queue_size,
};
Ok(extras)
}
fn author(&self) -> Result {
to_address(self.args.flag_etherbase.clone().or(self.args.flag_author.clone()))
}
fn cache_config(&self) -> CacheConfig {
match self.args.flag_cache_size.or(self.args.flag_cache) {
Some(size) => CacheConfig::new_with_total_cache_size(size),
None => CacheConfig::new(self.args.flag_cache_size_db, self.args.flag_cache_size_blocks, self.args.flag_cache_size_queue),
}
}
fn logger_config(&self) -> LogConfig {
LogConfig {
mode: self.args.flag_logging.clone(),
color: !self.args.flag_no_color && !cfg!(windows),
file: self.args.flag_log_file.clone(),
}
}
fn chain(&self) -> String {
if self.args.flag_testnet {
"morden".to_owned()
} else {
self.args.flag_chain.clone()
}
}
fn max_peers(&self) -> u32 {
let peers = self.args.flag_max_peers as u32;
max(self.min_peers(), peers)
}
fn min_peers(&self) -> u32 {
self.args.flag_peers.unwrap_or(self.args.flag_min_peers) as u32
}
fn work_notify(&self) -> Vec {
self.args.flag_notify_work.as_ref().map_or_else(Vec::new, |s| s.split(',').map(|s| s.to_owned()).collect())
}
fn accounts_config(&self) -> Result {
let cfg = AccountsConfig {
iterations: self.args.flag_keys_iterations,
import_keys: !self.args.flag_no_import_keys,
testnet: self.args.flag_testnet,
password_files: self.args.flag_password.clone(),
unlocked_accounts: try!(to_addresses(&self.args.flag_unlock)),
};
Ok(cfg)
}
fn miner_options(&self) -> Result {
let reseal = try!(self.args.flag_reseal_on_txs.parse::());
let options = MinerOptions {
new_work_notify: self.work_notify(),
force_sealing: self.args.flag_force_sealing,
reseal_on_external_tx: reseal.external,
reseal_on_own_tx: reseal.own,
tx_gas_limit: match self.args.flag_tx_gas_limit {
Some(ref d) => try!(to_u256(d)),
None => U256::max_value(),
},
tx_queue_size: self.args.flag_tx_queue_size,
pending_set: try!(to_pending_set(&self.args.flag_relay_set)),
reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period),
work_queue_size: self.args.flag_work_queue_size,
enable_resubmission: !self.args.flag_remove_solved,
};
Ok(options)
}
fn signer_config(&self) -> SignerConfiguration {
SignerConfiguration {
enabled: self.signer_enabled(),
port: self.args.flag_signer_port,
signer_path: self.directories().signer,
}
}
fn dapps_config(&self) -> DappsConfiguration {
DappsConfiguration {
enabled: self.dapps_enabled(),
interface: self.dapps_interface(),
port: self.args.flag_dapps_port,
user: self.args.flag_dapps_user.clone(),
pass: self.args.flag_dapps_pass.clone(),
dapps_path: self.directories().dapps,
}
}
fn gas_pricer_config(&self) -> Result {
if let Some(d) = self.args.flag_gasprice.as_ref() {
return Ok(GasPricerConfig::Fixed(try!(to_u256(d))));
}
let usd_per_tx = try!(to_price(&self.args.flag_usd_per_tx));
if "auto" == self.args.flag_usd_per_eth.as_str() {
return Ok(GasPricerConfig::Calibrated {
usd_per_tx: usd_per_tx,
recalibration_period: try!(to_duration(self.args.flag_price_update_period.as_str())),
});
}
let usd_per_eth = try!(to_price(&self.args.flag_usd_per_eth));
let wei_per_usd: f32 = 1.0e18 / usd_per_eth;
let gas_per_tx: f32 = 21000.0;
let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx;
info!(
"Using a fixed conversion rate of Ξ1 = {} ({} wei/gas)",
Colour::White.bold().paint(format!("US${}", usd_per_eth)),
Colour::Yellow.bold().paint(format!("{}", wei_per_gas))
);
Ok(GasPricerConfig::Fixed(U256::from_dec_str(&format!("{:.0}", wei_per_gas)).unwrap()))
}
fn extra_data(&self) -> Result {
match self.args.flag_extradata.as_ref().or(self.args.flag_extra_data.as_ref()) {
Some(ref x) if x.len() <= 32 => Ok(x.as_bytes().to_owned()),
None => Ok(version_data()),
Some(_) => Err("Extra data must be at most 32 characters".into()),
}
}
fn init_reserved_nodes(&self) -> Result, String> {
use std::fs::File;
match self.args.flag_reserved_peers {
Some(ref path) => {
let mut buffer = String::new();
let mut node_file = try!(File::open(path).map_err(|e| format!("Error opening reserved nodes file: {}", e)));
try!(node_file.read_to_string(&mut buffer).map_err(|_| "Error reading reserved node file"));
let lines = buffer.lines().map(|s| s.trim().to_owned()).filter(|s| s.len() > 0).collect::>();
if let Some(invalid) = lines.iter().find(|s| !is_valid_node_url(s)) {
return Err(format!("Invalid node address format given for a boot node: {}", invalid));
}
Ok(lines)
},
None => Ok(Vec::new())
}
}
fn net_addresses(&self) -> Result<(Option, Option), String> {
let port = self.args.flag_port;
let listen_address = Some(SocketAddr::new("0.0.0.0".parse().unwrap(), port));
let public_address = if self.args.flag_nat.starts_with("extip:") {
let host = &self.args.flag_nat[6..];
let host = try!(host.parse().map_err(|_| format!("Invalid host given with `--nat extip:{}`", host)));
Some(SocketAddr::new(host, port))
} else {
None
};
Ok((listen_address, public_address))
}
fn net_config(&self) -> Result {
let mut ret = NetworkConfiguration::new();
ret.nat_enabled = self.args.flag_nat == "any" || self.args.flag_nat == "upnp";
ret.boot_nodes = try!(to_bootnodes(&self.args.flag_bootnodes));
let (listen, public) = try!(self.net_addresses());
ret.listen_address = listen;
ret.public_address = public;
ret.use_secret = self.args.flag_node_key.as_ref().map(|s| s.parse::().unwrap_or_else(|_| s.sha3()));
ret.discovery_enabled = !self.args.flag_no_discovery && !self.args.flag_nodiscover;
ret.max_peers = self.max_peers();
ret.min_peers = self.min_peers();
let mut net_path = PathBuf::from(self.directories().db);
net_path.push("network");
ret.config_path = Some(net_path.to_str().unwrap().to_owned());
ret.reserved_nodes = try!(self.init_reserved_nodes());
if self.args.flag_reserved_only {
ret.non_reserved_mode = ::util::network::NonReservedPeerMode::Deny;
}
Ok(ret)
}
fn network_id(&self) -> Result