This mimics the fucntionality of Geth and the current unratified JSONRPC spec (but not the functionality of eth and the ratified spec).
540 lines
15 KiB
Rust
540 lines
15 KiB
Rust
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
//! Ethcore client application.
|
|
|
|
#![warn(missing_docs)]
|
|
#![cfg_attr(feature="dev", feature(plugin))]
|
|
#![cfg_attr(feature="dev", plugin(clippy))]
|
|
#![cfg_attr(feature="dev", allow(useless_format))]
|
|
|
|
extern crate docopt;
|
|
extern crate num_cpus;
|
|
extern crate rustc_serialize;
|
|
extern crate ethcore_util as util;
|
|
extern crate ethcore;
|
|
extern crate ethsync;
|
|
#[macro_use]
|
|
extern crate log as rlog;
|
|
extern crate env_logger;
|
|
extern crate ctrlc;
|
|
extern crate fdlimit;
|
|
#[cfg(not(windows))]
|
|
extern crate daemonize;
|
|
extern crate time;
|
|
extern crate number_prefix;
|
|
extern crate rpassword;
|
|
extern crate semver;
|
|
extern crate ethcore_ipc as ipc;
|
|
extern crate ethcore_ipc_nano as nanoipc;
|
|
#[macro_use]
|
|
extern crate hyper; // for price_info.rs
|
|
extern crate json_ipc_server as jsonipc;
|
|
|
|
#[cfg(feature = "rpc")]
|
|
extern crate ethcore_rpc;
|
|
|
|
#[cfg(feature = "dapps")]
|
|
extern crate ethcore_dapps;
|
|
|
|
#[cfg(feature = "ethcore-signer")]
|
|
extern crate ethcore_signer;
|
|
|
|
#[macro_use]
|
|
mod die;
|
|
mod price_info;
|
|
mod upgrade;
|
|
mod hypervisor;
|
|
mod setup_log;
|
|
mod rpc;
|
|
mod dapps;
|
|
mod informant;
|
|
mod io_handler;
|
|
mod cli;
|
|
mod configuration;
|
|
mod migration;
|
|
mod signer;
|
|
mod rpc_apis;
|
|
mod url;
|
|
|
|
use std::io::{Write, Read, BufReader, BufRead};
|
|
use std::ops::Deref;
|
|
use std::sync::{Arc, Mutex, Condvar};
|
|
use std::path::Path;
|
|
use std::fs::File;
|
|
use std::str::{FromStr, from_utf8};
|
|
use std::thread::sleep;
|
|
use std::time::Duration;
|
|
use rustc_serialize::hex::FromHex;
|
|
use ctrlc::CtrlC;
|
|
use util::{H256, ToPretty, NetworkConfiguration, PayloadInfo, Bytes, UtilError};
|
|
use util::panics::{MayPanic, ForwardPanic, PanicHandler};
|
|
use ethcore::client::{BlockID, BlockChainClient, ClientConfig, get_db_path};
|
|
use ethcore::error::{Error, ImportError};
|
|
use ethcore::service::ClientService;
|
|
use ethcore::spec::Spec;
|
|
use ethsync::EthSync;
|
|
use ethcore::miner::{Miner, MinerService, ExternalMiner};
|
|
use migration::migrate;
|
|
use informant::Informant;
|
|
|
|
use die::*;
|
|
use cli::print_version;
|
|
use rpc::RpcServer;
|
|
use signer::{SignerServer, new_token};
|
|
use dapps::WebappServer;
|
|
use io_handler::ClientIoHandler;
|
|
use configuration::Configuration;
|
|
|
|
fn main() {
|
|
let conf = Configuration::parse();
|
|
execute(conf);
|
|
}
|
|
|
|
fn execute(conf: Configuration) {
|
|
if conf.args.flag_version {
|
|
print_version();
|
|
return;
|
|
}
|
|
|
|
if conf.args.cmd_signer {
|
|
execute_signer(conf);
|
|
return;
|
|
}
|
|
|
|
let spec = conf.spec();
|
|
let client_config = conf.client_config(&spec);
|
|
|
|
execute_upgrades(&conf, &spec, &client_config);
|
|
|
|
if conf.args.cmd_daemon {
|
|
daemonize(&conf);
|
|
}
|
|
|
|
if conf.args.cmd_account {
|
|
execute_account_cli(conf);
|
|
return;
|
|
}
|
|
|
|
if conf.args.cmd_export {
|
|
execute_export(conf);
|
|
return;
|
|
}
|
|
|
|
if conf.args.cmd_import {
|
|
execute_import(conf);
|
|
return;
|
|
}
|
|
|
|
execute_client(conf, spec, client_config);
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
fn daemonize(conf: &Configuration) {
|
|
use daemonize::Daemonize;
|
|
Daemonize::new()
|
|
.pid_file(conf.args.arg_pid_file.clone())
|
|
.chown_pid_file(true)
|
|
.start()
|
|
.unwrap_or_else(|e| die!("Couldn't daemonize; {}", e));
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn daemonize(_conf: &Configuration) {
|
|
}
|
|
|
|
fn execute_upgrades(conf: &Configuration, spec: &Spec, client_config: &ClientConfig) {
|
|
match ::upgrade::upgrade(Some(&conf.path())) {
|
|
Ok(upgrades_applied) if upgrades_applied > 0 => {
|
|
println!("Executed {} upgrade scripts - ok", upgrades_applied);
|
|
},
|
|
Err(e) => {
|
|
die!("Error upgrading parity data: {:?}", e);
|
|
},
|
|
_ => {},
|
|
}
|
|
|
|
let db_path = get_db_path(Path::new(&conf.path()), client_config.pruning, spec.genesis_header().hash());
|
|
let result = migrate(&db_path);
|
|
if let Err(err) = result {
|
|
die_with_message(&format!("{}", err));
|
|
}
|
|
}
|
|
|
|
fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) {
|
|
// Setup panic handler
|
|
let panic_handler = PanicHandler::new_in_arc();
|
|
|
|
// Setup logging
|
|
let logger = setup_log::setup_log(&conf.args.flag_logging);
|
|
// Raise fdlimit
|
|
unsafe { ::fdlimit::raise_fd_limit(); }
|
|
|
|
let net_settings = conf.net_settings(&spec);
|
|
let sync_config = conf.sync_config(&spec);
|
|
|
|
// Secret Store
|
|
let account_service = Arc::new(conf.account_service());
|
|
|
|
// Miner
|
|
let miner = Miner::with_accounts(conf.args.flag_force_sealing, conf.spec(), account_service.clone());
|
|
miner.set_author(conf.author());
|
|
miner.set_gas_floor_target(conf.gas_floor_target());
|
|
miner.set_extra_data(conf.extra_data());
|
|
miner.set_minimal_gas_price(conf.gas_price());
|
|
miner.set_transactions_limit(conf.args.flag_tx_limit);
|
|
|
|
// Build client
|
|
let mut service = ClientService::start(
|
|
client_config, spec, net_settings, Path::new(&conf.path()), miner.clone(), !conf.args.flag_no_network
|
|
).unwrap_or_else(|e| die_with_error("Client", e));
|
|
|
|
panic_handler.forward_from(&service);
|
|
let client = service.client();
|
|
|
|
let external_miner = Arc::new(ExternalMiner::default());
|
|
let network_settings = Arc::new(conf.network_settings());
|
|
|
|
// Sync
|
|
let sync = EthSync::new(sync_config, client.clone());
|
|
EthSync::register(&*service.network(), sync.clone()).unwrap_or_else(|e| die_with_error("Error registering eth protocol handler", UtilError::from(e).into()));
|
|
|
|
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
|
|
signer_port: conf.signer_port(),
|
|
signer_queue: Arc::new(rpc_apis::ConfirmationsQueue::default()),
|
|
client: client.clone(),
|
|
sync: sync.clone(),
|
|
secret_store: account_service.clone(),
|
|
miner: miner.clone(),
|
|
external_miner: external_miner.clone(),
|
|
logger: logger.clone(),
|
|
settings: network_settings.clone(),
|
|
allow_pending_receipt_query: !conf.args.flag_geth,
|
|
});
|
|
|
|
let dependencies = rpc::Dependencies {
|
|
panic_handler: panic_handler.clone(),
|
|
apis: deps_for_rpc_apis.clone(),
|
|
};
|
|
|
|
// Setup http rpc
|
|
let rpc_server = rpc::new_http(rpc::HttpConfiguration {
|
|
enabled: network_settings.rpc_enabled,
|
|
interface: network_settings.rpc_interface.clone(),
|
|
port: network_settings.rpc_port,
|
|
apis: conf.rpc_apis(),
|
|
cors: conf.rpc_cors(),
|
|
}, &dependencies);
|
|
|
|
// setup ipc rpc
|
|
let _ipc_server = rpc::new_ipc(conf.ipc_settings(), &dependencies);
|
|
debug!("IPC: {}", conf.ipc_settings());
|
|
|
|
if conf.args.flag_webapp { println!("WARNING: Flag -w/--webapp is deprecated. Dapps server is now on by default. Ignoring."); }
|
|
let dapps_server = dapps::new(dapps::Configuration {
|
|
enabled: !conf.args.flag_dapps_off,
|
|
interface: conf.args.flag_dapps_interface.clone(),
|
|
port: conf.args.flag_dapps_port,
|
|
user: conf.args.flag_dapps_user.clone(),
|
|
pass: conf.args.flag_dapps_pass.clone(),
|
|
dapps_path: conf.directories().dapps,
|
|
}, dapps::Dependencies {
|
|
panic_handler: panic_handler.clone(),
|
|
apis: deps_for_rpc_apis.clone(),
|
|
});
|
|
|
|
// Set up a signer
|
|
let signer_server = signer::start(signer::Configuration {
|
|
enabled: deps_for_rpc_apis.signer_port.is_some(),
|
|
port: conf.args.flag_signer_port,
|
|
signer_path: conf.directories().signer,
|
|
}, signer::Dependencies {
|
|
panic_handler: panic_handler.clone(),
|
|
apis: deps_for_rpc_apis.clone(),
|
|
});
|
|
|
|
// Register IO handler
|
|
let io_handler = Arc::new(ClientIoHandler {
|
|
client: service.client(),
|
|
info: Informant::new(conf.have_color()),
|
|
sync: sync.clone(),
|
|
accounts: account_service.clone(),
|
|
network: service.network(),
|
|
});
|
|
service.register_io_handler(io_handler).expect("Error registering IO handler");
|
|
|
|
if conf.args.cmd_ui {
|
|
url::open("http://localhost:8080/")
|
|
}
|
|
|
|
// Handle exit
|
|
wait_for_exit(panic_handler, rpc_server, dapps_server, signer_server);
|
|
}
|
|
|
|
fn flush_stdout() {
|
|
::std::io::stdout().flush().expect("stdout is flushable; qed");
|
|
}
|
|
|
|
enum DataFormat {
|
|
Hex,
|
|
Binary,
|
|
}
|
|
|
|
fn execute_export(conf: Configuration) {
|
|
// Setup panic handler
|
|
let panic_handler = PanicHandler::new_in_arc();
|
|
|
|
// Raise fdlimit
|
|
unsafe { ::fdlimit::raise_fd_limit(); }
|
|
|
|
let spec = conf.spec();
|
|
let net_settings = NetworkConfiguration {
|
|
config_path: None,
|
|
listen_address: None,
|
|
public_address: None,
|
|
udp_port: None,
|
|
nat_enabled: false,
|
|
discovery_enabled: false,
|
|
pin: true,
|
|
boot_nodes: Vec::new(),
|
|
use_secret: None,
|
|
ideal_peers: 0,
|
|
};
|
|
let client_config = conf.client_config(&spec);
|
|
|
|
// Build client
|
|
let service = ClientService::start(
|
|
client_config, spec, net_settings, Path::new(&conf.path()), Arc::new(Miner::default()), false
|
|
).unwrap_or_else(|e| die_with_error("Client", e));
|
|
|
|
panic_handler.forward_from(&service);
|
|
let client = service.client();
|
|
|
|
// we have a client!
|
|
let parse_block_id = |s: &str, arg: &str| -> u64 {
|
|
if s == "latest" {
|
|
client.chain_info().best_block_number
|
|
} else if let Ok(n) = s.parse::<u64>() {
|
|
n
|
|
} else if let Ok(h) = H256::from_str(s) {
|
|
client.block_number(BlockID::Hash(h)).unwrap_or_else(|| {
|
|
die!("Unknown block hash passed to {} parameter: {:?}", arg, s);
|
|
})
|
|
} else {
|
|
die!("Invalid {} parameter given: {:?}", arg, s);
|
|
}
|
|
};
|
|
let from = parse_block_id(&conf.args.flag_from, "--from");
|
|
let to = parse_block_id(&conf.args.flag_to, "--to");
|
|
let format = match conf.args.flag_format {
|
|
Some(x) => match x.deref() {
|
|
"binary" | "bin" => DataFormat::Binary,
|
|
"hex" => DataFormat::Hex,
|
|
x => die!("Invalid --format parameter given: {:?}", x),
|
|
},
|
|
None if conf.args.arg_file.is_none() => DataFormat::Hex,
|
|
None => DataFormat::Binary,
|
|
};
|
|
|
|
let mut out: Box<Write> = if let Some(f) = conf.args.arg_file {
|
|
Box::new(File::create(&f).unwrap_or_else(|_| die!("Cannot write to file given: {}", f)))
|
|
} else {
|
|
Box::new(::std::io::stdout())
|
|
};
|
|
|
|
for i in from..(to + 1) {
|
|
let b = client.deref().block(BlockID::Number(i)).unwrap();
|
|
match format {
|
|
DataFormat::Binary => { out.write(&b).expect("Couldn't write to stream."); }
|
|
DataFormat::Hex => { out.write_fmt(format_args!("{}", b.pretty())).expect("Couldn't write to stream."); }
|
|
}
|
|
}
|
|
}
|
|
|
|
fn execute_import(conf: Configuration) {
|
|
// Setup panic handler
|
|
let panic_handler = PanicHandler::new_in_arc();
|
|
|
|
// Raise fdlimit
|
|
unsafe { ::fdlimit::raise_fd_limit(); }
|
|
|
|
let spec = conf.spec();
|
|
let net_settings = NetworkConfiguration {
|
|
config_path: None,
|
|
listen_address: None,
|
|
public_address: None,
|
|
udp_port: None,
|
|
nat_enabled: false,
|
|
discovery_enabled: false,
|
|
pin: true,
|
|
boot_nodes: Vec::new(),
|
|
use_secret: None,
|
|
ideal_peers: 0,
|
|
};
|
|
let client_config = conf.client_config(&spec);
|
|
|
|
// Build client
|
|
let service = ClientService::start(
|
|
client_config, spec, net_settings, Path::new(&conf.path()), Arc::new(Miner::default()), false
|
|
).unwrap_or_else(|e| die_with_error("Client", e));
|
|
|
|
panic_handler.forward_from(&service);
|
|
let client = service.client();
|
|
|
|
let mut instream: Box<Read> = if let Some(ref f) = conf.args.arg_file {
|
|
let f = File::open(f).unwrap_or_else(|_| die!("Cannot open the file given: {}", f));
|
|
Box::new(f)
|
|
} else {
|
|
Box::new(::std::io::stdin())
|
|
};
|
|
|
|
const READAHEAD_BYTES: usize = 8;
|
|
|
|
let mut first_bytes: Bytes = vec![0; READAHEAD_BYTES];
|
|
let mut first_read = 0;
|
|
|
|
let format = match conf.args.flag_format {
|
|
Some(ref x) => match x.deref() {
|
|
"binary" | "bin" => DataFormat::Binary,
|
|
"hex" => DataFormat::Hex,
|
|
x => die!("Invalid --format parameter given: {:?}", x),
|
|
},
|
|
None => {
|
|
// autodetect...
|
|
first_read = instream.read(&mut(first_bytes[..])).unwrap_or_else(|_| die!("Error reading from the file/stream."));
|
|
match first_bytes[0] {
|
|
0xf9 => {
|
|
println!("Autodetected binary data format.");
|
|
DataFormat::Binary
|
|
}
|
|
_ => {
|
|
println!("Autodetected hex data format.");
|
|
DataFormat::Hex
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
let informant = Informant::new(conf.have_color());
|
|
|
|
let do_import = |bytes| {
|
|
while client.queue_info().is_full() { sleep(Duration::from_secs(1)); }
|
|
match client.import_block(bytes) {
|
|
Ok(_) => {}
|
|
Err(Error::Import(ImportError::AlreadyInChain)) => { trace!("Skipping block already in chain."); }
|
|
Err(e) => die!("Cannot import block: {:?}", e)
|
|
}
|
|
informant.tick(client.deref(), None);
|
|
};
|
|
|
|
match format {
|
|
DataFormat::Binary => {
|
|
loop {
|
|
let mut bytes: Bytes = if first_read > 0 {first_bytes.clone()} else {vec![0; READAHEAD_BYTES]};
|
|
let n = if first_read > 0 {first_read} else {instream.read(&mut(bytes[..])).unwrap_or_else(|_| die!("Error reading from the file/stream."))};
|
|
if n == 0 { break; }
|
|
first_read = 0;
|
|
let s = PayloadInfo::from(&(bytes[..])).unwrap_or_else(|e| die!("Invalid RLP in the file/stream: {:?}", e)).total();
|
|
bytes.resize(s, 0);
|
|
instream.read_exact(&mut(bytes[READAHEAD_BYTES..])).unwrap_or_else(|_| die!("Error reading from the file/stream."));
|
|
do_import(bytes);
|
|
}
|
|
}
|
|
DataFormat::Hex => {
|
|
for line in BufReader::new(instream).lines() {
|
|
let s = line.unwrap_or_else(|_| die!("Error reading from the file/stream."));
|
|
let s = if first_read > 0 {from_utf8(&first_bytes).unwrap().to_owned() + &(s[..])} else {s};
|
|
first_read = 0;
|
|
let bytes = FromHex::from_hex(&(s[..])).unwrap_or_else(|_| die!("Invalid hex in file/stream."));
|
|
do_import(bytes);
|
|
}
|
|
}
|
|
}
|
|
client.flush_queue();
|
|
}
|
|
|
|
fn execute_signer(conf: Configuration) {
|
|
if !conf.args.cmd_new_token {
|
|
die!("Unknown command.");
|
|
}
|
|
|
|
let path = conf.directories().signer;
|
|
new_token(path).unwrap_or_else(|e| {
|
|
die!("Error generating token: {:?}", e)
|
|
});
|
|
}
|
|
|
|
fn execute_account_cli(conf: Configuration) {
|
|
use util::keys::store::SecretStore;
|
|
use rpassword::read_password;
|
|
let mut secret_store = SecretStore::with_security(Path::new(&conf.keys_path()), conf.keys_iterations());
|
|
if conf.args.cmd_new {
|
|
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 {
|
|
println!("Passwords do not match!");
|
|
return;
|
|
}
|
|
println!("New account address:");
|
|
let new_address = secret_store.new_account(&password).unwrap();
|
|
println!("{:?}", new_address);
|
|
return;
|
|
}
|
|
if conf.args.cmd_list {
|
|
println!("Known addresses:");
|
|
for &(addr, _) in &secret_store.accounts().unwrap() {
|
|
println!("{:?}", addr);
|
|
}
|
|
return;
|
|
}
|
|
if conf.args.cmd_import {
|
|
let imported = util::keys::import_keys_paths(&mut secret_store, &conf.args.arg_path).unwrap();
|
|
println!("Imported {} keys", imported);
|
|
}
|
|
}
|
|
|
|
fn wait_for_exit(
|
|
panic_handler: Arc<PanicHandler>,
|
|
_rpc_server: Option<RpcServer>,
|
|
_dapps_server: Option<WebappServer>,
|
|
_signer_server: Option<SignerServer>
|
|
) {
|
|
let exit = Arc::new(Condvar::new());
|
|
|
|
// Handle possible exits
|
|
let e = exit.clone();
|
|
CtrlC::set_handler(move || { e.notify_all(); });
|
|
|
|
// Handle panics
|
|
let e = exit.clone();
|
|
panic_handler.on_panic(move |_reason| { e.notify_all(); });
|
|
|
|
// Wait for signal
|
|
let mutex = Mutex::new(());
|
|
let _ = exit.wait(mutex.lock().unwrap()).unwrap();
|
|
info!("Finishing work, please wait...");
|
|
}
|
|
|
|
/// Parity needs at least 1 test to generate coverage reports correctly.
|
|
#[test]
|
|
fn if_works() {
|
|
}
|