diff --git a/parity/informant.rs b/parity/informant.rs new file mode 100644 index 000000000..866e92e40 --- /dev/null +++ b/parity/informant.rs @@ -0,0 +1,89 @@ +// 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::sync::RwLock; +use std::ops::{Deref, DerefMut}; +use ethsync::{EthSync, SyncProvider}; +use ethcore::client::*; +use number_prefix::{binary_prefix, Standalone, Prefixed}; + +pub struct Informant { + chain_info: RwLock>, + cache_info: RwLock>, + report: RwLock>, +} + +impl Default for Informant { + fn default() -> Self { + Informant { + chain_info: RwLock::new(None), + cache_info: RwLock::new(None), + report: RwLock::new(None), + } + } +} + +impl Informant { + fn format_bytes(b: usize) -> String { + match binary_prefix(b as f64) { + Standalone(bytes) => format!("{} bytes", bytes), + Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix), + } + } + + pub fn tick(&self, client: &Client, sync: &EthSync) { + // 5 seconds betwen calls. TODO: calculate this properly. + let dur = 5usize; + + let chain_info = client.chain_info(); + let queue_info = client.queue_info(); + let cache_info = client.blockchain_cache_info(); + let sync_info = sync.status(); + + let mut write_report = self.report.write().unwrap(); + let report = client.report(); + + if let (_, _, &Some(ref last_report)) = ( + self.chain_info.read().unwrap().deref(), + self.cache_info.read().unwrap().deref(), + write_report.deref() + ) { + println!("[ #{} {} ]---[ {} blk/s | {} tx/s | {} gas/s //··· {}/{} peers, #{}, {}+{} queued ···// mem: {} db, {} chain, {} queue, {} sync ]", + chain_info.best_block_number, + chain_info.best_block_hash, + (report.blocks_imported - last_report.blocks_imported) / dur, + (report.transactions_applied - last_report.transactions_applied) / dur, + (report.gas_processed - last_report.gas_processed) / From::from(dur), + + sync_info.num_active_peers, + sync_info.num_peers, + sync_info.last_imported_block_number.unwrap_or(chain_info.best_block_number), + queue_info.unverified_queue_size, + queue_info.verified_queue_size, + + Informant::format_bytes(report.state_db_mem), + Informant::format_bytes(cache_info.total()), + Informant::format_bytes(queue_info.mem_used), + Informant::format_bytes(sync_info.mem_used), + ); + } + + *self.chain_info.write().unwrap().deref_mut() = Some(chain_info); + *self.cache_info.write().unwrap().deref_mut() = Some(cache_info); + *write_report.deref_mut() = Some(report); + } +} + diff --git a/parity/io_handler.rs b/parity/io_handler.rs new file mode 100644 index 000000000..07cebb5b7 --- /dev/null +++ b/parity/io_handler.rs @@ -0,0 +1,54 @@ +// 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::sync::Arc; +use ethcore::client::Client; +use ethcore::service::NetSyncMessage; +use ethsync::EthSync; +use util::keys::store::AccountService; +use util::{TimerToken, IoHandler, IoContext}; + +use informant::Informant; + +const INFO_TIMER: TimerToken = 0; + +const ACCOUNT_TICK_TIMER: TimerToken = 10; +const ACCOUNT_TICK_MS: u64 = 60000; + +pub struct ClientIoHandler { + pub client: Arc, + pub sync: Arc, + pub accounts: Arc, + pub info: Informant, +} + +impl IoHandler for ClientIoHandler { + fn initialize(&self, io: &IoContext) { + io.register_timer(INFO_TIMER, 5000).expect("Error registering timer"); + io.register_timer(ACCOUNT_TICK_TIMER, ACCOUNT_TICK_MS).expect("Error registering account timer"); + + } + + fn timeout(&self, _io: &IoContext, timer: TimerToken) { + match timer { + INFO_TIMER => { self.info.tick(&self.client, &self.sync); } + ACCOUNT_TICK_TIMER => { self.accounts.tick(); }, + _ => {} + } + } +} + + diff --git a/parity/main.rs b/parity/main.rs index 612719ce2..ecd93d02b 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -42,7 +42,6 @@ extern crate ethcore_ipc as ipc; extern crate ethcore_ipc_nano as nanoipc; extern crate serde; extern crate bincode; - // for price_info.rs #[macro_use] extern crate hyper; @@ -50,7 +49,7 @@ extern crate bincode; extern crate ethcore_rpc; #[cfg(feature = "webapp")] -extern crate ethcore_webapp as webapp; +extern crate ethcore_webapp; use std::io::{BufRead, BufReader}; use std::fs::File; @@ -63,16 +62,12 @@ use util::panics::{MayPanic, ForwardPanic, PanicHandler}; use util::keys::store::*; use ethcore::spec::*; use ethcore::client::*; -use ethcore::service::{ClientService, NetSyncMessage}; use ethcore::ethereum; -use ethsync::{EthSync, SyncConfig, SyncProvider}; +use ethcore::service::ClientService; +use ethsync::{EthSync, SyncConfig}; use ethminer::{Miner, MinerService}; use docopt::Docopt; use daemonize::Daemonize; -use number_prefix::{binary_prefix, Standalone, Prefixed}; - -#[cfg(feature = "webapp")] -use webapp::Server as WebappServer; #[macro_use] mod die; @@ -81,9 +76,14 @@ mod upgrade; mod hypervisor; mod setup_log; mod rpc; +mod webapp; +mod informant; +mod io_handler; use die::*; use rpc::RpcServer; +use webapp::WebappServer; +use io_handler::ClientIoHandler; const USAGE: &'static str = r#" Parity. Ethereum Client. @@ -278,58 +278,6 @@ struct Args { } -#[cfg(feature = "webapp")] -fn setup_webapp_server( - client: Arc, - sync: Arc, - secret_store: Arc, - miner: Arc, - url: &SocketAddr, - auth: Option<(String, String)>, - logger: Arc, -) -> WebappServer { - use ethcore_rpc::v1::*; - - let server = webapp::ServerBuilder::new(); - server.add_delegate(Web3Client::new().to_delegate()); - server.add_delegate(NetClient::new(&sync).to_delegate()); - server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); - server.add_delegate(PersonalClient::new(&secret_store).to_delegate()); - server.add_delegate(EthcoreClient::new(&miner, logger).to_delegate()); - let start_result = match auth { - None => { - server.start_unsecure_http(url) - }, - Some((username, password)) => { - server.start_basic_auth_http(url, &username, &password) - }, - }; - match start_result { - Err(webapp::ServerError::IoError(err)) => die_with_io_error(err), - Err(e) => die!("{:?}", e), - Ok(handle) => handle, - } - -} - - -#[cfg(not(feature = "webapp"))] -struct WebappServer; - -#[cfg(not(feature = "webapp"))] -fn setup_webapp_server( - _client: Arc, - _sync: Arc, - _secret_store: Arc, - _miner: Arc, - _url: &SocketAddr, - _auth: Option<(String, String)>, - _logger: Arc, -) -> ! { - die!("Your Parity version has been compiled without WebApps support.") -} - fn print_version() { println!("\ Parity @@ -644,66 +592,33 @@ impl Configuration { let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone()); // Setup rpc - let rpc_server = if self.args.flag_jsonrpc || self.args.flag_rpc { - let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis); - let url = format!("{}:{}", - match self.args.flag_rpcaddr.as_ref().unwrap_or(&self.args.flag_jsonrpc_interface).as_str() { - "all" => "0.0.0.0", - "local" => "127.0.0.1", - x => x, - }, - self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port) - ); - let addr = SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid JSONRPC listen host/port given.", url)); - let cors_domain = self.args.flag_jsonrpc_cors.clone().or(self.args.flag_rpccorsdomain.clone()); + let rpc_server = rpc::new(rpc::Configuration { + enabled: self.args.flag_jsonrpc || self.args.flag_rpc, + interface: self.args.flag_rpcaddr.clone().unwrap_or(self.args.flag_jsonrpc_interface.clone()), + port: self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port), + apis: self.args.flag_rpcapi.clone().unwrap_or(self.args.flag_jsonrpc_apis.clone()), + cors: self.args.flag_jsonrpc_cors.clone().or(self.args.flag_rpccorsdomain.clone()), + }, rpc::Dependencies { + client: client.clone(), + sync: sync.clone(), + secret_store: account_service.clone(), + miner: miner.clone(), + logger: logger.clone() + }); - Some(rpc::setup_rpc_server( - service.client(), - sync.clone(), - account_service.clone(), - miner.clone(), - &addr, - cors_domain, - apis.split(',').collect(), - logger.clone(), - )) - } else { - None - }; - - let webapp_server = if self.args.flag_webapp { - let url = format!("{}:{}", - match self.args.flag_webapp_interface.as_str() { - "all" => "0.0.0.0", - "local" => "127.0.0.1", - x => x, - }, - self.args.flag_webapp_port - ); - let addr = SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid Webapps listen host/port given.", url)); - let auth = self.args.flag_webapp_user.as_ref().map(|username| { - let password = self.args.flag_webapp_pass.as_ref().map_or_else(|| { - use rpassword::read_password; - println!("Type password for WebApps server (user: {}): ", username); - let pass = read_password().unwrap(); - println!("OK, got it. Starting server..."); - pass - }, |pass| pass.to_owned()); - (username.to_owned(), password) - }); - - Some(setup_webapp_server( - service.client(), - sync.clone(), - account_service.clone(), - miner.clone(), - &addr, - auth, - logger.clone(), - )) - } else { - None - }; + let webapp_server = webapp::new(webapp::Configuration { + enabled: self.args.flag_webapp, + interface: self.args.flag_webapp_interface.clone(), + port: self.args.flag_webapp_port, + user: self.args.flag_webapp_user.clone(), + pass: self.args.flag_webapp_pass.clone(), + }, webapp::Dependencies { + client: client.clone(), + sync: sync.clone(), + secret_store: account_service.clone(), + miner: miner.clone(), + logger: logger.clone() + }); // Register IO handler let io_handler = Arc::new(ClientIoHandler { @@ -740,101 +655,6 @@ fn main() { Configuration::parse().execute(); } -struct Informant { - chain_info: RwLock>, - cache_info: RwLock>, - report: RwLock>, -} - -impl Default for Informant { - fn default() -> Self { - Informant { - chain_info: RwLock::new(None), - cache_info: RwLock::new(None), - report: RwLock::new(None), - } - } -} - -impl Informant { - fn format_bytes(b: usize) -> String { - match binary_prefix(b as f64) { - Standalone(bytes) => format!("{} bytes", bytes), - Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix), - } - } - - pub fn tick(&self, client: &Client, sync: &EthSync) { - // 5 seconds betwen calls. TODO: calculate this properly. - let dur = 5usize; - - let chain_info = client.chain_info(); - let queue_info = client.queue_info(); - let cache_info = client.blockchain_cache_info(); - let sync_info = sync.status(); - - let mut write_report = self.report.write().unwrap(); - let report = client.report(); - - if let (_, _, &Some(ref last_report)) = ( - self.chain_info.read().unwrap().deref(), - self.cache_info.read().unwrap().deref(), - write_report.deref() - ) { - println!("[ #{} {} ]---[ {} blk/s | {} tx/s | {} gas/s //··· {}/{} peers, #{}, {}+{} queued ···// mem: {} db, {} chain, {} queue, {} sync ]", - chain_info.best_block_number, - chain_info.best_block_hash, - (report.blocks_imported - last_report.blocks_imported) / dur, - (report.transactions_applied - last_report.transactions_applied) / dur, - (report.gas_processed - last_report.gas_processed) / From::from(dur), - - sync_info.num_active_peers, - sync_info.num_peers, - sync_info.last_imported_block_number.unwrap_or(chain_info.best_block_number), - queue_info.unverified_queue_size, - queue_info.verified_queue_size, - - Informant::format_bytes(report.state_db_mem), - Informant::format_bytes(cache_info.total()), - Informant::format_bytes(queue_info.mem_used), - Informant::format_bytes(sync_info.mem_used), - ); - } - - *self.chain_info.write().unwrap().deref_mut() = Some(chain_info); - *self.cache_info.write().unwrap().deref_mut() = Some(cache_info); - *write_report.deref_mut() = Some(report); - } -} - -const INFO_TIMER: TimerToken = 0; - -const ACCOUNT_TICK_TIMER: TimerToken = 10; -const ACCOUNT_TICK_MS: u64 = 60000; - -struct ClientIoHandler { - client: Arc, - sync: Arc, - accounts: Arc, - info: Informant, -} - -impl IoHandler for ClientIoHandler { - fn initialize(&self, io: &IoContext) { - io.register_timer(INFO_TIMER, 5000).expect("Error registering timer"); - io.register_timer(ACCOUNT_TICK_TIMER, ACCOUNT_TICK_MS).expect("Error registering account timer"); - - } - - fn timeout(&self, _io: &IoContext, timer: TimerToken) { - match timer { - INFO_TIMER => { self.info.tick(&self.client, &self.sync); } - ACCOUNT_TICK_TIMER => { self.accounts.tick(); }, - _ => {} - } - } -} - /// Parity needs at least 1 test to generate coverage reports correctly. #[test] fn if_works() { diff --git a/parity/rpc.rs b/parity/rpc.rs index b5a2eadb5..b2e6391d8 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . +use std::str::FromStr; use std::sync::Arc; use std::net::SocketAddr; use ethcore::client::Client; @@ -28,34 +29,58 @@ use die::*; pub use ethcore_rpc::Server as RpcServer; #[cfg(feature = "rpc")] use ethcore_rpc::{RpcServerError, RpcServer as Server}; - #[cfg(not(feature = "rpc"))] pub struct RpcServer; +pub struct Configuration { + pub enabled: bool, + pub interface: String, + pub port: u16, + pub apis: String, + pub cors: Option, +} + +pub struct Dependencies { + pub client: Arc, + pub sync: Arc, + pub secret_store: Arc, + pub miner: Arc, + pub logger: Arc, +} + +pub fn new(conf: Configuration, deps: Dependencies) -> Option { + if !conf.enabled { + return None; + } + + let interface = match conf.interface.as_str() { + "all" => "0.0.0.0", + "local" => "127.0.0.1", + x => x, + }; + let apis = conf.apis.split(',').collect(); + let url = format!("{}:{}", interface, conf.port); + let addr = SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid JSONRPC listen host/port given.", url)); + + Some(setup_rpc_server(deps, &addr, conf.cors, apis)) +} + #[cfg(not(feature = "rpc"))] pub fn setup_rpc_server( - _client: Arc, - _sync: Arc, - _secret_store: Arc, - _miner: Arc, + _deps: Dependencies, _url: &SocketAddr, _cors_domain: Option, _apis: Vec<&str>, - _logger: Arc, ) -> ! { die!("Your Parity version has been compiled without JSON-RPC support.") } #[cfg(feature = "rpc")] pub fn setup_rpc_server( - client: Arc, - sync: Arc, - secret_store: Arc, - miner: Arc, + deps: Dependencies, url: &SocketAddr, cors_domain: Option, apis: Vec<&str>, - logger: Arc, ) -> RpcServer { use ethcore_rpc::v1::*; @@ -63,13 +88,13 @@ pub fn setup_rpc_server( for api in apis.into_iter() { match api { "web3" => server.add_delegate(Web3Client::new().to_delegate()), - "net" => server.add_delegate(NetClient::new(&sync).to_delegate()), + "net" => server.add_delegate(NetClient::new(&deps.sync).to_delegate()), "eth" => { - server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); + server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner).to_delegate()); + server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); }, - "personal" => server.add_delegate(PersonalClient::new(&secret_store).to_delegate()), - "ethcore" => server.add_delegate(EthcoreClient::new(&miner, logger.clone()).to_delegate()), + "personal" => server.add_delegate(PersonalClient::new(&deps.secret_store).to_delegate()), + "ethcore" => server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone()).to_delegate()), _ => { die!("{}: Invalid API name to be enabled.", api); }, diff --git a/parity/webapp.rs b/parity/webapp.rs new file mode 100644 index 000000000..74e8c2e51 --- /dev/null +++ b/parity/webapp.rs @@ -0,0 +1,117 @@ +// 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::sync::Arc; +use std::str::FromStr; +use std::net::SocketAddr; +use ethcore::client::Client; +use ethsync::EthSync; +use ethminer::Miner; +use util::RotatingLogger; +use util::keys::store::{AccountService}; +use die::*; + +#[cfg(feature = "webapp")] +pub use ethcore_webapp::Server as WebappServer; +#[cfg(not(feature = "webapp"))] +pub struct WebappServer; + +pub struct Configuration { + pub enabled: bool, + pub interface: String, + pub port: u16, + pub user: Option, + pub pass: Option, +} + +pub struct Dependencies { + pub client: Arc, + pub sync: Arc, + pub secret_store: Arc, + pub miner: Arc, + pub logger: Arc, +} + +pub fn new(configuration: Configuration, deps: Dependencies) -> Option { + if !configuration.enabled { + return None; + } + + let interface = match configuration.interface.as_str() { + "all" => "0.0.0.0", + "local" => "127.0.0.1", + x => x, + }; + let url = format!("{}:{}", interface, configuration.port); + let addr = SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid Webapps listen host/port given.", url)); + + let auth = configuration.user.as_ref().map(|username| { + let password = configuration.pass.as_ref().map_or_else(|| { + use rpassword::read_password; + println!("Type password for WebApps server (user: {}): ", username); + let pass = read_password().unwrap(); + println!("OK, got it. Starting server..."); + pass + }, |pass| pass.to_owned()); + (username.to_owned(), password) + }); + + Some(setup_webapp_server(deps, &addr, auth)) +} + +#[cfg(not(feature = "webapp"))] +pub fn setup_webapp_server( + _deps: Dependencies, + _url: &SocketAddr, + _auth: Option<(String, String)>, +) -> ! { + die!("Your Parity version has been compiled without WebApps support.") +} + +#[cfg(feature = "webapp")] +pub fn setup_webapp_server( + deps: Dependencies, + url: &SocketAddr, + auth: Option<(String, String)> +) -> WebappServer { + use ethcore_rpc::v1::*; + use ethcore_webapp as webapp; + + let server = webapp::ServerBuilder::new(); + server.add_delegate(Web3Client::new().to_delegate()); + server.add_delegate(NetClient::new(&deps.sync).to_delegate()); + server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner).to_delegate()); + server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); + server.add_delegate(PersonalClient::new(&deps.secret_store).to_delegate()); + server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger).to_delegate()); + + let start_result = match auth { + None => { + server.start_unsecure_http(url) + }, + Some((username, password)) => { + server.start_basic_auth_http(url, &username, &password) + }, + }; + + match start_result { + Err(webapp::ServerError::IoError(err)) => die_with_io_error(err), + Err(e) => die!("{:?}", e), + Ok(handle) => handle, + } + +} +