diff --git a/Cargo.lock b/Cargo.lock index c5ee9ad8b..913ac0e44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1613,6 +1613,21 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "node-health" +version = "0.1.0" +dependencies = [ + "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ntp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-reactor 0.1.0", + "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.9" @@ -1821,6 +1836,7 @@ dependencies = [ "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "node-health 0.1.0", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "panic_hook 0.1.0", @@ -1862,7 +1878,6 @@ dependencies = [ "ethcore-util 1.8.0", "fetch 0.1.0", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", @@ -1870,7 +1885,7 @@ dependencies = [ "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ntp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "node-health 0.1.0", "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.8.0", "parity-reactor 0.1.0", @@ -1985,6 +2000,7 @@ dependencies = [ "jsonrpc-ws-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "multihash 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "node-health 0.1.0", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", "parity-updater 1.8.0", diff --git a/Cargo.toml b/Cargo.toml index 3adaf62d4..ebd60ca64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ ethcore-logger = { path = "logger" } ethcore-stratum = { path = "stratum" } ethcore-network = { path = "util/network" } ethkey = { path = "ethkey" } +node-health = { path = "dapps/node-health" } rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-hash-fetch = { path = "hash-fetch" } @@ -110,4 +111,4 @@ lto = false panic = "abort" [workspace] -members = ["ethstore/cli", "ethkey/cli", "evmbin", "whisper", "chainspec"] +members = ["ethstore/cli", "ethkey/cli", "evmbin", "whisper", "chainspec", "dapps/node-health"] diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 143bbe30f..b1751c616 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -9,15 +9,12 @@ authors = ["Parity Technologies "] [dependencies] base32 = "0.3" -env_logger = "0.4" futures = "0.1" -futures-cpupool = "0.1" linked-hash-map = "0.3" log = "0.3" parity-dapps-glue = "1.7" mime = "0.2" mime_guess = "1.6.1" -ntp = "0.2.0" rand = "0.3" rustc-hex = "1.0" serde = "1.0" @@ -32,15 +29,19 @@ itertools = "0.5" jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } -ethcore-devtools = { path = "../devtools" } ethcore-util = { path = "../util" } fetch = { path = "../util/fetch" } +node-health = { path = "./node-health" } parity-hash-fetch = { path = "../hash-fetch" } parity-reactor = { path = "../util/reactor" } parity-ui = { path = "./ui" } clippy = { version = "0.0.103", optional = true} +[dev-dependencies] +env_logger = "0.4" +ethcore-devtools = { path = "../devtools" } + [features] dev = ["clippy", "ethcore-util/dev"] diff --git a/dapps/node-health/Cargo.toml b/dapps/node-health/Cargo.toml new file mode 100644 index 000000000..ff13e8593 --- /dev/null +++ b/dapps/node-health/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "node-health" +description = "Node's health status" +version = "0.1.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[dependencies] +futures = "0.1" +futures-cpupool = "0.1" +log = "0.3" +ntp = "0.2.0" +parking_lot = "0.4" +serde = "1.0" +serde_derive = "1.0" +time = "0.1.35" + +parity-reactor = { path = "../../util/reactor" } diff --git a/dapps/node-health/src/health.rs b/dapps/node-health/src/health.rs new file mode 100644 index 000000000..3b3563d6b --- /dev/null +++ b/dapps/node-health/src/health.rs @@ -0,0 +1,122 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Reporting node's health. + +use std::sync::Arc; +use std::time; +use futures::{Future, BoxFuture}; +use futures::sync::oneshot; +use types::{HealthInfo, HealthStatus, Health}; +use time::{TimeChecker, MAX_DRIFT}; +use parity_reactor::Remote; +use parking_lot::Mutex; +use {SyncStatus}; + +const TIMEOUT_SECS: u64 = 5; +const PROOF: &str = "Only one closure is invoked."; + +/// A struct enabling you to query for node's health. +#[derive(Debug, Clone)] +pub struct NodeHealth { + sync_status: Arc, + time: TimeChecker, + remote: Remote, +} + +impl NodeHealth { + /// Creates new `NodeHealth`. + pub fn new(sync_status: Arc, time: TimeChecker, remote: Remote) -> Self { + NodeHealth { sync_status, time, remote, } + } + + /// Query latest health report. + pub fn health(&self) -> BoxFuture { + trace!(target: "dapps", "Checking node health."); + // Check timediff + let sync_status = self.sync_status.clone(); + let time = self.time.time_drift(); + let (tx, rx) = oneshot::channel(); + let tx = Arc::new(Mutex::new(Some(tx))); + let tx2 = tx.clone(); + self.remote.spawn_with_timeout( + move || time.then(move |result| { + let _ = tx.lock().take().expect(PROOF).send(Ok(result)); + Ok(()) + }), + time::Duration::from_secs(TIMEOUT_SECS), + move || { + let _ = tx2.lock().take().expect(PROOF).send(Err(())); + }, + ); + + rx.map_err(|err| { + warn!(target: "dapps", "Health request cancelled: {:?}", err); + }).and_then(move |time| { + // Check peers + let peers = { + let (connected, max) = sync_status.peers(); + let (status, message) = match connected { + 0 => { + (HealthStatus::Bad, "You are not connected to any peers. There is most likely some network issue. Fix connectivity.".into()) + }, + 1 => (HealthStatus::NeedsAttention, "You are connected to only one peer. Your node might not be reliable. Check your network connection.".into()), + _ => (HealthStatus::Ok, "".into()), + }; + HealthInfo { status, message, details: (connected, max) } + }; + + // Check sync + let sync = { + let is_syncing = sync_status.is_major_importing(); + let (status, message) = if is_syncing { + (HealthStatus::NeedsAttention, "Your node is still syncing, the values you see might be outdated. Wait until it's fully synced.".into()) + } else { + (HealthStatus::Ok, "".into()) + }; + HealthInfo { status, message, details: is_syncing } + }; + + // Check time + let time = { + let (status, message, details) = match time { + Ok(Ok(diff)) if diff < MAX_DRIFT && diff > -MAX_DRIFT => { + (HealthStatus::Ok, "".into(), diff) + }, + Ok(Ok(diff)) => { + (HealthStatus::Bad, format!( + "Your clock is not in sync. Detected difference is too big for the protocol to work: {}ms. Synchronize your clock.", + diff, + ), diff) + }, + Ok(Err(err)) => { + (HealthStatus::NeedsAttention, format!( + "Unable to reach time API: {}. Make sure that your clock is synchronized.", + err, + ), 0) + }, + Err(_) => { + (HealthStatus::NeedsAttention, "Time API request timed out. Make sure that the clock is synchronized.".into(), 0) + }, + }; + + HealthInfo { status, message, details, } + }; + + Ok(Health { peers, sync, time}) + }).boxed() + } +} diff --git a/dapps/node-health/src/lib.rs b/dapps/node-health/src/lib.rs new file mode 100644 index 000000000..b0eb133ee --- /dev/null +++ b/dapps/node-health/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Node Health status reporting. + +#![warn(missing_docs)] + +extern crate futures; +extern crate futures_cpupool; +extern crate ntp; +extern crate time as time_crate; +extern crate parity_reactor; +extern crate parking_lot; + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; + +mod health; +mod time; +mod types; + +pub use futures_cpupool::CpuPool; +pub use health::NodeHealth; +pub use types::{Health, HealthInfo, HealthStatus}; +pub use time::{TimeChecker, Error}; + +/// Indicates sync status +pub trait SyncStatus: ::std::fmt::Debug + Send + Sync { + /// Returns true if there is a major sync happening. + fn is_major_importing(&self) -> bool; + + /// Returns number of connected and ideal peers. + fn peers(&self) -> (usize, usize); +} diff --git a/dapps/src/api/time.rs b/dapps/node-health/src/time.rs similarity index 98% rename from dapps/src/api/time.rs rename to dapps/node-health/src/time.rs index 06b9cee7f..05c48ce47 100644 --- a/dapps/src/api/time.rs +++ b/dapps/node-health/src/time.rs @@ -41,8 +41,8 @@ use futures::{self, Future, BoxFuture}; use futures::future::{self, IntoFuture}; use futures_cpupool::{CpuPool, CpuFuture}; use ntp; -use time::{Duration, Timespec}; -use util::RwLock; +use parking_lot::RwLock; +use time_crate::{Duration, Timespec}; /// Time checker error. #[derive(Debug, Clone, PartialEq)] @@ -164,7 +164,7 @@ impl Ntp for SimpleNtp { match ntp::request(&server.address) { Ok(packet) => { - let dest_time = ::time::now_utc().to_timespec(); + let dest_time = ::time_crate::now_utc().to_timespec(); let orig_time = Timespec::from(packet.orig_time); let recv_time = Timespec::from(packet.recv_time); let transmit_time = Timespec::from(packet.transmit_time); @@ -293,7 +293,7 @@ mod tests { use time::Duration; use futures::{future, Future}; use super::{Ntp, TimeChecker, Error}; - use util::RwLock; + use parking_lot::RwLock; #[derive(Clone)] struct FakeNtp(RefCell>, Cell); diff --git a/dapps/node-health/src/types.rs b/dapps/node-health/src/types.rs new file mode 100644 index 000000000..ae883a626 --- /dev/null +++ b/dapps/node-health/src/types.rs @@ -0,0 +1,57 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Base health types. + +/// Health API endpoint status. +#[derive(Debug, PartialEq, Serialize)] +pub enum HealthStatus { + /// Everything's OK. + #[serde(rename = "ok")] + Ok, + /// Node health need attention + /// (the issue is not critical, but may need investigation) + #[serde(rename = "needsAttention")] + NeedsAttention, + /// There is something bad detected with the node. + #[serde(rename = "bad")] + Bad, +} + +/// Represents a single check in node health. +/// Cointains the status of that check and apropriate message and details. +#[derive(Debug, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct HealthInfo { + /// Check status. + pub status: HealthStatus, + /// Human-readable message. + pub message: String, + /// Technical details of the check. + pub details: T, +} + +/// Node Health status. +#[derive(Debug, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Health { + /// Status of peers. + pub peers: HealthInfo<(usize, usize)>, + /// Sync status. + pub sync: HealthInfo, + /// Time diff info. + pub time: HealthInfo, +} diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 7bd7fa049..03ba859f8 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -21,32 +21,28 @@ use hyper::method::Method; use hyper::status::StatusCode; use api::{response, types}; -use api::time::{TimeChecker, MAX_DRIFT}; use apps::fetcher::Fetcher; use handlers::{self, extract_url}; use endpoint::{Endpoint, Handler, EndpointPath}; +use node_health::{NodeHealth, HealthStatus, Health}; use parity_reactor::Remote; -use {SyncStatus}; #[derive(Clone)] pub struct RestApi { fetcher: Arc, - sync_status: Arc, - time: TimeChecker, + health: NodeHealth, remote: Remote, } impl RestApi { pub fn new( fetcher: Arc, - sync_status: Arc, - time: TimeChecker, + health: NodeHealth, remote: Remote, ) -> Box { Box::new(RestApi { fetcher, - sync_status, - time, + health, remote, }) } @@ -90,68 +86,23 @@ impl RestApiRouter { } fn health(&self, control: Control) -> Box { - use self::types::{HealthInfo, HealthStatus, Health}; - - trace!(target: "dapps", "Checking node health."); - // Check timediff - let sync_status = self.api.sync_status.clone(); - let map = move |time| { - // Check peers - let peers = { - let (connected, max) = sync_status.peers(); - let (status, message) = match connected { - 0 => { - (HealthStatus::Bad, "You are not connected to any peers. There is most likely some network issue. Fix connectivity.".into()) - }, - 1 => (HealthStatus::NeedsAttention, "You are connected to only one peer. Your node might not be reliable. Check your network connection.".into()), - _ => (HealthStatus::Ok, "".into()), - }; - HealthInfo { status, message, details: (connected, max) } + let map = move |health: Result, ()>| { + let status = match health { + Ok(Ok(ref health)) => { + if [&health.peers.status, &health.sync.status].iter().any(|x| *x != &HealthStatus::Ok) { + StatusCode::PreconditionFailed // HTTP 412 + } else { + StatusCode::Ok // HTTP 200 + } + }, + _ => StatusCode::ServiceUnavailable, // HTTP 503 }; - // Check sync - let sync = { - let is_syncing = sync_status.is_major_importing(); - let (status, message) = if is_syncing { - (HealthStatus::NeedsAttention, "Your node is still syncing, the values you see might be outdated. Wait until it's fully synced.".into()) - } else { - (HealthStatus::Ok, "".into()) - }; - HealthInfo { status, message, details: is_syncing } - }; - - // Check time - let time = { - let (status, message, details) = match time { - Ok(Ok(diff)) if diff < MAX_DRIFT && diff > -MAX_DRIFT => { - (HealthStatus::Ok, "".into(), diff) - }, - Ok(Ok(diff)) => { - (HealthStatus::Bad, format!( - "Your clock is not in sync. Detected difference is too big for the protocol to work: {}ms. Synchronize your clock.", - diff, - ), diff) - }, - Ok(Err(err)) => { - (HealthStatus::NeedsAttention, format!( - "Unable to reach time API: {}. Make sure that your clock is synchronized.", - err, - ), 0) - }, - Err(_) => { - (HealthStatus::NeedsAttention, "Time API request timed out. Make sure that the clock is synchronized.".into(), 0) - }, - }; - - HealthInfo { status, message, details, } - }; - - response::as_json(StatusCode::Ok, &Health { peers, sync, time }) + response::as_json(status, &health) }; - - let time = self.api.time.time_drift(); + let health = self.api.health.health(); let remote = self.api.remote.clone(); - Box::new(handlers::AsyncHandler::new(time, map, remote, control)) + Box::new(handlers::AsyncHandler::new(health, map, remote, control)) } } diff --git a/dapps/src/api/mod.rs b/dapps/src/api/mod.rs index 59c634399..4ffb9f791 100644 --- a/dapps/src/api/mod.rs +++ b/dapps/src/api/mod.rs @@ -18,8 +18,6 @@ mod api; mod response; -mod time; mod types; pub use self::api::RestApi; -pub use self::time::TimeChecker; diff --git a/dapps/src/api/types.rs b/dapps/src/api/types.rs index a61964143..6beca3b58 100644 --- a/dapps/src/api/types.rs +++ b/dapps/src/api/types.rs @@ -25,43 +25,3 @@ pub struct ApiError { /// More technical error details. pub detail: String, } - -/// Health API endpoint status. -#[derive(Debug, PartialEq, Serialize)] -pub enum HealthStatus { - /// Everything's OK. - #[serde(rename = "ok")] - Ok, - /// Node health need attention - /// (the issue is not critical, but may need investigation) - #[serde(rename = "needsAttention")] - NeedsAttention, - /// There is something bad detected with the node. - #[serde(rename = "bad")] - Bad -} - -/// Represents a single check in node health. -/// Cointains the status of that check and apropriate message and details. -#[derive(Debug, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct HealthInfo { - /// Check status. - pub status: HealthStatus, - /// Human-readable message. - pub message: String, - /// Technical details of the check. - pub details: T, -} - -/// Node Health status. -#[derive(Debug, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Health { - /// Status of peers. - pub peers: HealthInfo<(usize, usize)>, - /// Sync status. - pub sync: HealthInfo, - /// Time diff info. - pub time: HealthInfo, -} diff --git a/dapps/src/apps/fetcher/mod.rs b/dapps/src/apps/fetcher/mod.rs index 1fdf2f697..5b91da1a3 100644 --- a/dapps/src/apps/fetcher/mod.rs +++ b/dapps/src/apps/fetcher/mod.rs @@ -281,6 +281,7 @@ mod tests { } } + #[derive(Debug)] struct FakeSync(bool); impl SyncStatus for FakeSync { fn is_major_importing(&self) -> bool { self.0 } diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 135f0bb36..073db5121 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -21,11 +21,9 @@ extern crate base32; extern crate futures; -extern crate futures_cpupool; extern crate itertools; extern crate linked_hash_map; extern crate mime_guess; -extern crate ntp; extern crate rand; extern crate rustc_hex; extern crate serde; @@ -40,6 +38,7 @@ extern crate jsonrpc_http_server; extern crate ethcore_util as util; extern crate fetch; +extern crate node_health; extern crate parity_dapps_glue as parity_dapps; extern crate parity_hash_fetch as hash_fetch; extern crate parity_reactor; @@ -57,7 +56,6 @@ extern crate ethcore_devtools as devtools; #[cfg(test)] extern crate env_logger; - mod endpoint; mod apps; mod page; @@ -79,19 +77,12 @@ use util::RwLock; use jsonrpc_http_server::{self as http, hyper, Origin}; use fetch::Fetch; -use futures_cpupool::CpuPool; +use node_health::NodeHealth; use parity_reactor::Remote; pub use hash_fetch::urlhint::ContractClient; +pub use node_health::SyncStatus; -/// Indicates sync status -pub trait SyncStatus: Send + Sync { - /// Returns true if there is a major sync happening. - fn is_major_importing(&self) -> bool; - - /// Returns number of connected and ideal peers. - fn peers(&self) -> (usize, usize); -} /// Validates Web Proxy tokens pub trait WebProxyTokens: Send + Sync { @@ -156,8 +147,7 @@ impl Middleware { /// Creates new middleware for UI server. pub fn ui( - ntp_servers: &[String], - pool: CpuPool, + health: NodeHealth, remote: Remote, dapps_domain: &str, registrar: Arc, @@ -172,11 +162,9 @@ impl Middleware { ).embeddable_on(None).allow_dapps(false)); let special = { let mut special = special_endpoints( - ntp_servers, - pool, + health, content_fetcher.clone(), remote.clone(), - sync_status.clone(), ); special.insert(router::SpecialEndpoint::Home, Some(apps::ui())); special @@ -197,8 +185,7 @@ impl Middleware { /// Creates new Dapps server middleware. pub fn dapps( - ntp_servers: &[String], - pool: CpuPool, + health: NodeHealth, remote: Remote, ui_address: Option<(String, u16)>, extra_embed_on: Vec<(String, u16)>, @@ -236,11 +223,9 @@ impl Middleware { let special = { let mut special = special_endpoints( - ntp_servers, - pool, + health, content_fetcher.clone(), remote.clone(), - sync_status, ); special.insert( router::SpecialEndpoint::Home, @@ -270,20 +255,17 @@ impl http::RequestMiddleware for Middleware { } } -fn special_endpoints>( - ntp_servers: &[T], - pool: CpuPool, +fn special_endpoints( + health: NodeHealth, content_fetcher: Arc, remote: Remote, - sync_status: Arc, ) -> HashMap>> { let mut special = HashMap::new(); special.insert(router::SpecialEndpoint::Rpc, None); special.insert(router::SpecialEndpoint::Utils, Some(apps::utils())); special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new( content_fetcher, - sync_status, - api::TimeChecker::new(ntp_servers, pool), + health, remote, ))); special diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 30d3ba8f9..6f4652351 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -26,7 +26,7 @@ use jsonrpc_http_server::{self as http, Host, DomainsValidation}; use devtools::http_client; use hash_fetch::urlhint::ContractClient; use fetch::{Fetch, Client as FetchClient}; -use futures_cpupool::CpuPool; +use node_health::{NodeHealth, TimeChecker, CpuPool}; use parity_reactor::Remote; use {Middleware, SyncStatus, WebProxyTokens}; @@ -39,6 +39,7 @@ use self::fetch::FakeFetch; const SIGNER_PORT: u16 = 18180; +#[derive(Debug)] struct FakeSync(bool); impl SyncStatus for FakeSync { fn is_major_importing(&self) -> bool { self.0 } @@ -254,9 +255,13 @@ impl Server { remote: Remote, fetch: F, ) -> Result { + let health = NodeHealth::new( + sync_status.clone(), + TimeChecker::new::(&[], CpuPool::new(1)), + remote.clone(), + ); let middleware = Middleware::dapps( - &["0.pool.ntp.org:123".into(), "1.pool.ntp.org:123".into()], - CpuPool::new(4), + health, remote, signer_address, vec![], diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index c2681b3fb..59ccb5884 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -385,6 +385,11 @@ export default class Parity { .then(outNumber); } + nodeHealth () { + return this._transport + .execute('parity_nodeHealth'); + } + nodeName () { return this._transport .execute('parity_nodeName'); diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index e58fcf6c1..39ba74d01 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -200,7 +200,7 @@ export default class Status { const statusPromises = [ this._api.eth.syncing(), this._api.parity.netPeers(), - this._fetchHealth() + this._api.parity.nodeHealth() ]; return Promise @@ -247,13 +247,6 @@ export default class Status { }; } - _fetchHealth = () => { - // Support Parity-Extension. - const uiUrl = this._api.transport.uiUrlWithProtocol || ''; - - return fetch(`${uiUrl}/api/health`).then(res => res.json()); - } - /** * The data fetched here should not change * unless Parity is restarted. They are thus diff --git a/parity/configuration.rs b/parity/configuration.rs index 35a2a7f47..681804346 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -346,6 +346,7 @@ impl Configuration { daemon: daemon, logger_config: logger_config.clone(), miner_options: self.miner_options(self.args.flag_reseal_min_period)?, + ntp_servers: self.ntp_servers(), ws_conf: ws_conf, http_conf: http_conf, ipc_conf: ipc_conf, @@ -563,7 +564,6 @@ impl Configuration { fn ui_config(&self) -> UiConfiguration { UiConfiguration { enabled: self.ui_enabled(), - ntp_servers: self.ntp_servers(), interface: self.ui_interface(), port: self.ui_port(), hosts: self.ui_hosts(), @@ -576,7 +576,6 @@ impl Configuration { DappsConfiguration { enabled: self.dapps_enabled(), - ntp_servers: self.ntp_servers(), dapps_path: PathBuf::from(self.directories().dapps), extra_dapps: if self.args.cmd_dapp { self.args.arg_path.iter().map(|path| PathBuf::from(path)).collect() @@ -1318,12 +1317,6 @@ mod tests { support_token_api: true }, UiConfiguration { enabled: true, - ntp_servers: vec![ - "0.parity.pool.ntp.org:123".into(), - "1.parity.pool.ntp.org:123".into(), - "2.parity.pool.ntp.org:123".into(), - "3.parity.pool.ntp.org:123".into(), - ], interface: "127.0.0.1".into(), port: 8180, hosts: Some(vec![]), @@ -1348,6 +1341,12 @@ mod tests { daemon: None, logger_config: Default::default(), miner_options: Default::default(), + ntp_servers: vec![ + "0.parity.pool.ntp.org:123".into(), + "1.parity.pool.ntp.org:123".into(), + "2.parity.pool.ntp.org:123".into(), + "3.parity.pool.ntp.org:123".into(), + ], ws_conf: Default::default(), http_conf: Default::default(), ipc_conf: Default::default(), @@ -1567,16 +1566,9 @@ mod tests { let conf3 = parse(&["parity", "--ui-path", "signer", "--ui-interface", "test"]); // then - let ntp_servers = vec![ - "0.parity.pool.ntp.org:123".into(), - "1.parity.pool.ntp.org:123".into(), - "2.parity.pool.ntp.org:123".into(), - "3.parity.pool.ntp.org:123".into(), - ]; assert_eq!(conf0.directories().signer, "signer".to_owned()); assert_eq!(conf0.ui_config(), UiConfiguration { enabled: true, - ntp_servers: ntp_servers.clone(), interface: "127.0.0.1".into(), port: 8180, hosts: Some(vec![]), @@ -1585,7 +1577,6 @@ mod tests { assert_eq!(conf1.directories().signer, "signer".to_owned()); assert_eq!(conf1.ui_config(), UiConfiguration { enabled: true, - ntp_servers: ntp_servers.clone(), interface: "127.0.0.1".into(), port: 8180, hosts: Some(vec![]), @@ -1595,7 +1586,6 @@ mod tests { assert_eq!(conf2.directories().signer, "signer".to_owned()); assert_eq!(conf2.ui_config(), UiConfiguration { enabled: true, - ntp_servers: ntp_servers.clone(), interface: "127.0.0.1".into(), port: 3123, hosts: Some(vec![]), @@ -1604,7 +1594,6 @@ mod tests { assert_eq!(conf3.directories().signer, "signer".to_owned()); assert_eq!(conf3.ui_config(), UiConfiguration { enabled: true, - ntp_servers: ntp_servers.clone(), interface: "test".into(), port: 8180, hosts: Some(vec![]), diff --git a/parity/dapps.rs b/parity/dapps.rs index fddd050f5..4177644d3 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -22,12 +22,12 @@ use ethcore::client::{Client, BlockChainClient, BlockId}; use ethcore::transaction::{Transaction, Action}; use ethsync::LightSync; use futures::{future, IntoFuture, Future, BoxFuture}; -use futures_cpupool::CpuPool; use hash_fetch::fetch::Client as FetchClient; use hash_fetch::urlhint::ContractClient; use helpers::replace_home; use light::client::Client as LightClient; use light::on_demand::{self, OnDemand}; +use node_health::{SyncStatus, NodeHealth}; use rpc; use rpc_apis::SignerService; use parity_reactor; @@ -36,7 +36,6 @@ use util::{Bytes, Address}; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { pub enabled: bool, - pub ntp_servers: Vec, pub dapps_path: PathBuf, pub extra_dapps: Vec, pub extra_embed_on: Vec<(String, u16)>, @@ -48,12 +47,6 @@ impl Default for Configuration { let data_dir = default_data_path(); Configuration { enabled: true, - ntp_servers: vec![ - "0.parity.pool.ntp.org:123".into(), - "1.parity.pool.ntp.org:123".into(), - "2.parity.pool.ntp.org:123".into(), - "3.parity.pool.ntp.org:123".into(), - ], dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), extra_dapps: vec![], extra_embed_on: vec![], @@ -149,10 +142,10 @@ impl ContractClient for LightRegistrar { // to resolve. #[derive(Clone)] pub struct Dependencies { + pub node_health: NodeHealth, pub sync_status: Arc, pub contract_client: Arc, pub remote: parity_reactor::TokioRemote, - pub pool: CpuPool, pub fetch: FetchClient, pub signer: Arc, pub ui_address: Option<(String, u16)>, @@ -165,7 +158,6 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result Result Result, String> { +pub fn new_ui(enabled: bool, deps: Dependencies) -> Result, String> { if !enabled { return Ok(None); } server::ui_middleware( deps, - ntp_servers, rpc::DAPPS_DOMAIN, ).map(Some) } -pub use self::server::{SyncStatus, Middleware, service}; +pub use self::server::{Middleware, service}; #[cfg(not(feature = "dapps"))] mod server { @@ -196,11 +187,6 @@ mod server { use parity_rpc::{hyper, RequestMiddleware, RequestMiddlewareAction}; use rpc_apis; - pub trait SyncStatus { - fn is_major_importing(&self) -> bool; - fn peers(&self) -> (usize, usize); - } - pub struct Middleware; impl RequestMiddleware for Middleware { fn on_request( @@ -212,7 +198,6 @@ mod server { pub fn dapps_middleware( _deps: Dependencies, - _ntp_servers: &[String], _dapps_path: PathBuf, _extra_dapps: Vec, _dapps_domain: &str, @@ -224,7 +209,6 @@ mod server { pub fn ui_middleware( _deps: Dependencies, - _ntp_servers: &[String], _dapps_domain: &str, ) -> Result { Err("Your Parity version has been compiled without UI support.".into()) @@ -246,11 +230,9 @@ mod server { use parity_reactor; pub use parity_dapps::Middleware; - pub use parity_dapps::SyncStatus; pub fn dapps_middleware( deps: Dependencies, - ntp_servers: &[String], dapps_path: PathBuf, extra_dapps: Vec, dapps_domain: &str, @@ -262,8 +244,7 @@ mod server { let web_proxy_tokens = Arc::new(move |token| signer.web_proxy_access_token_domain(&token)); Ok(parity_dapps::Middleware::dapps( - ntp_servers, - deps.pool, + deps.node_health, parity_remote, deps.ui_address, extra_embed_on, @@ -280,13 +261,11 @@ mod server { pub fn ui_middleware( deps: Dependencies, - ntp_servers: &[String], dapps_domain: &str, ) -> Result { let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); Ok(parity_dapps::Middleware::ui( - ntp_servers, - deps.pool, + deps.node_health, parity_remote, dapps_domain, deps.contract_client, diff --git a/parity/main.rs b/parity/main.rs index 48e752bf2..46e698998 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -58,6 +58,7 @@ extern crate ethcore_util as util; extern crate ethcore_network as network; extern crate ethkey; extern crate ethsync; +extern crate node_health; extern crate panic_hook; extern crate parity_hash_fetch as hash_fetch; extern crate parity_ipfs_api; diff --git a/parity/rpc.rs b/parity/rpc.rs index 1e148dad2..f11d4c3ae 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -73,7 +73,6 @@ impl Default for HttpConfiguration { #[derive(Debug, PartialEq, Clone)] pub struct UiConfiguration { pub enabled: bool, - pub ntp_servers: Vec, pub interface: String, pub port: u16, pub hosts: Option>, @@ -107,12 +106,6 @@ impl Default for UiConfiguration { fn default() -> Self { UiConfiguration { enabled: true && cfg!(feature = "ui-enabled"), - ntp_servers: vec![ - "0.parity.pool.ntp.org:123".into(), - "1.parity.pool.ntp.org:123".into(), - "2.parity.pool.ntp.org:123".into(), - "3.parity.pool.ntp.org:123".into(), - ], port: 8180, interface: "127.0.0.1".into(), hosts: Some(vec![]), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index dba69ae7e..7948d9b20 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -27,17 +27,18 @@ use ethcore::account_provider::AccountProvider; use ethcore::client::Client; use ethcore::miner::{Miner, ExternalMiner}; use ethcore::snapshot::SnapshotService; -use parity_rpc::{Metadata, NetworkSettings}; -use parity_rpc::informant::{ActivityNotifier, ClientNotifier}; -use parity_rpc::dispatch::{FullDispatcher, LightDispatcher}; +use ethcore_logger::RotatingLogger; use ethsync::{ManageNetwork, SyncProvider, LightSync}; use hash_fetch::fetch::Client as FetchClient; use jsonrpc_core::{self as core, MetaIoHandler}; use light::{TransactionQueue as LightTransactionQueue, Cache as LightDataCache}; +use node_health::NodeHealth; +use parity_reactor; +use parity_rpc::dispatch::{FullDispatcher, LightDispatcher}; +use parity_rpc::informant::{ActivityNotifier, ClientNotifier}; +use parity_rpc::{Metadata, NetworkSettings}; use updater::Updater; use util::{Mutex, RwLock}; -use ethcore_logger::RotatingLogger; -use parity_reactor; #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum Api { @@ -217,6 +218,7 @@ pub struct FullDependencies { pub settings: Arc, pub net_service: Arc, pub updater: Arc, + pub health: NodeHealth, pub geth_compatibility: bool, pub dapps_service: Option>, pub dapps_address: Option<(String, u16)>, @@ -300,12 +302,13 @@ impl FullDependencies { false => None, }; handler.extend_with(ParityClient::new( - &self.client, - &self.miner, - &self.sync, - &self.updater, - &self.net_service, - &self.secret_store, + self.client.clone(), + self.miner.clone(), + self.sync.clone(), + self.updater.clone(), + self.net_service.clone(), + self.health.clone(), + self.secret_store.clone(), self.logger.clone(), self.settings.clone(), signer, @@ -403,6 +406,7 @@ pub struct LightDependencies { pub secret_store: Arc, pub logger: Arc, pub settings: Arc, + pub health: NodeHealth, pub on_demand: Arc<::light::on_demand::OnDemand>, pub cache: Arc>, pub transaction_queue: Arc>, @@ -507,6 +511,7 @@ impl LightDependencies { self.secret_store.clone(), self.logger.clone(), self.settings.clone(), + self.health.clone(), signer, self.dapps_address.clone(), self.ws_address.clone(), diff --git a/parity/run.rs b/parity/run.rs index 49624480c..a0270d0a3 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::fmt; use std::sync::Arc; use std::net::{TcpListener}; @@ -32,6 +33,7 @@ use fdlimit::raise_fd_limit; use hash_fetch::fetch::{Fetch, Client as FetchClient}; use informant::{Informant, LightNodeInformantData, FullNodeInformantData}; use light::Cache as LightDataCache; +use node_health; use parity_reactor::EventLoop; use parity_rpc::{NetworkSettings, informant, is_major_importing}; use updater::{UpdatePolicy, Updater}; @@ -80,6 +82,7 @@ pub struct RunCmd { pub daemon: Option, pub logger_config: LogConfig, pub miner_options: MinerOptions, + pub ntp_servers: Vec, pub ws_conf: rpc::WsConfiguration, pub http_conf: rpc::HttpConfiguration, pub ipc_conf: rpc::IpcConfiguration, @@ -283,7 +286,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> // the dapps server let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf, &cmd.logger_config)); - let dapps_deps = { + let (node_health, dapps_deps) = { let contract_client = Arc::new(::dapps::LightRegistrar { client: service.client().clone(), sync: light_sync.clone(), @@ -291,7 +294,12 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> }); struct LightSyncStatus(Arc); - impl dapps::SyncStatus for LightSyncStatus { + impl fmt::Debug for LightSyncStatus { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "Light Sync Status") + } + } + impl node_health::SyncStatus for LightSyncStatus { fn is_major_importing(&self) -> bool { self.0.is_major_importing() } fn peers(&self) -> (usize, usize) { let peers = ethsync::LightSyncProvider::peer_numbers(&*self.0); @@ -299,19 +307,26 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> } } - dapps::Dependencies { - sync_status: Arc::new(LightSyncStatus(light_sync.clone())), - pool: fetch.pool(), + let sync_status = Arc::new(LightSyncStatus(light_sync.clone())); + let node_health = node_health::NodeHealth::new( + sync_status.clone(), + node_health::TimeChecker::new(&cmd.ntp_servers, fetch.pool()), + event_loop.remote(), + ); + + (node_health.clone(), dapps::Dependencies { + sync_status, + node_health, contract_client: contract_client, remote: event_loop.raw_remote(), fetch: fetch.clone(), signer: signer_service.clone(), ui_address: cmd.ui_conf.address(), - } + }) }; let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; - let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, &cmd.ui_conf.ntp_servers, dapps_deps)?; + let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?; // start RPCs let dapps_service = dapps::service(&dapps_middleware); @@ -320,6 +335,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> client: service.client().clone(), sync: light_sync.clone(), net: light_sync.clone(), + health: node_health, secret_store: account_provider, logger: logger, settings: Arc::new(cmd.net_settings), @@ -661,12 +677,17 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf, &cmd.logger_config)); // the dapps server - let dapps_deps = { + let (node_health, dapps_deps) = { let (sync, client) = (sync_provider.clone(), client.clone()); let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() }); struct SyncStatus(Arc, Arc, ethsync::NetworkConfiguration); - impl dapps::SyncStatus for SyncStatus { + impl fmt::Debug for SyncStatus { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "Dapps Sync Status") + } + } + impl node_health::SyncStatus for SyncStatus { fn is_major_importing(&self) -> bool { is_major_importing(Some(self.0.status().state), self.1.queue_info()) } @@ -676,18 +697,24 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R } } - dapps::Dependencies { - sync_status: Arc::new(SyncStatus(sync, client, net_conf)), - pool: fetch.pool(), + let sync_status = Arc::new(SyncStatus(sync, client, net_conf)); + let node_health = node_health::NodeHealth::new( + sync_status.clone(), + node_health::TimeChecker::new(&cmd.ntp_servers, fetch.pool()), + event_loop.remote(), + ); + (node_health.clone(), dapps::Dependencies { + sync_status, + node_health, contract_client: contract_client, remote: event_loop.raw_remote(), fetch: fetch.clone(), signer: signer_service.clone(), ui_address: cmd.ui_conf.address(), - } + }) }; let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; - let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, &cmd.ui_conf.ntp_servers, dapps_deps)?; + let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?; let dapps_service = dapps::service(&dapps_middleware); let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies { @@ -695,6 +722,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R snapshot: snapshot_service.clone(), client: client.clone(), sync: sync_provider.clone(), + health: node_health, net: manage_network.clone(), secret_store: secret_store, miner: miner.clone(), diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 73230c24e..a5a371cdf 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -47,12 +47,13 @@ ethjson = { path = "../json" } ethcore-devtools = { path = "../devtools" } ethcore-light = { path = "../ethcore/light" } ethcore-logger = { path = "../logger" } -vm = { path = "../ethcore/vm" } -parity-updater = { path = "../updater" } -parity-reactor = { path = "../util/reactor" } -rlp = { path = "../util/rlp" } fetch = { path = "../util/fetch" } +node-health = { path = "../dapps/node-health" } +parity-reactor = { path = "../util/reactor" } +parity-updater = { path = "../updater" } +rlp = { path = "../util/rlp" } stats = { path = "../util/stats" } +vm = { path = "../ethcore/vm" } clippy = { version = "0.0.103", optional = true} pretty_assertions = "0.1" diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index e75d187de..c429471f3 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -55,6 +55,7 @@ extern crate ethsync; extern crate ethcore_logger; extern crate vm; extern crate fetch; +extern crate node_health; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rlp; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index b2602d6a1..ab8dd655a 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -584,8 +584,9 @@ fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: S } /// Extract the default gas price from a client and miner. -pub fn default_gas_price(client: &C, miner: &M) -> U256 - where C: MiningBlockChainClient, M: MinerService +pub fn default_gas_price(client: &C, miner: &M) -> U256 where + C: MiningBlockChainClient, + M: MinerService, { client.gas_price_corpus(100).median().cloned().unwrap_or_else(|| miner.sensible_gas_price()) } diff --git a/rpc/src/v1/helpers/fake_sign.rs b/rpc/src/v1/helpers/fake_sign.rs index 551bf5d35..2bbaef0ee 100644 --- a/rpc/src/v1/helpers/fake_sign.rs +++ b/rpc/src/v1/helpers/fake_sign.rs @@ -24,8 +24,8 @@ use jsonrpc_core::Error; use v1::helpers::CallRequest; use v1::helpers::dispatch::default_gas_price; -pub fn sign_call( - client: &Arc, +pub fn sign_call ( + client: &Arc, miner: &Arc, request: CallRequest, gas_cap: bool, diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index 676e39b11..7cd540937 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -19,15 +19,15 @@ use std::sync::Arc; use std::collections::{BTreeMap, HashSet}; use futures::{future, Future, BoxFuture}; -use ethcore_logger::RotatingLogger; use util::misc::version_data; -use crypto::ecies; +use crypto::{ecies, DEFAULT_MAC}; use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::LightSyncProvider; use ethcore::account_provider::AccountProvider; -use crypto::DEFAULT_MAC; +use ethcore_logger::RotatingLogger; +use node_health::{NodeHealth, Health}; use light::client::LightChainClient; @@ -53,6 +53,7 @@ pub struct ParityClient { accounts: Arc, logger: Arc, settings: Arc, + health: NodeHealth, signer: Option>, dapps_address: Option<(String, u16)>, ws_address: Option<(String, u16)>, @@ -67,18 +68,20 @@ impl ParityClient { accounts: Arc, logger: Arc, settings: Arc, + health: NodeHealth, signer: Option>, dapps_address: Option<(String, u16)>, ws_address: Option<(String, u16)>, ) -> Self { ParityClient { - light_dispatch: light_dispatch, - accounts: accounts, - logger: logger, - settings: settings, - signer: signer, - dapps_address: dapps_address, - ws_address: ws_address, + light_dispatch, + accounts, + logger, + settings, + health, + signer, + dapps_address, + ws_address, eip86_transition: client.eip86_transition(), } } @@ -393,4 +396,10 @@ impl Parity for ParityClient { fn call(&self, _meta: Self::Metadata, _requests: Vec, _block: Trailing) -> BoxFuture, Error> { future::err(errors::light_unimplemented(None)).boxed() } + + fn node_health(&self) -> BoxFuture { + self.health.health() + .map_err(|err| errors::internal("Health API failure.", err)) + .boxed() + } } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index fb82c5205..e66d3ac05 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -20,11 +20,10 @@ use std::str::FromStr; use std::collections::{BTreeMap, HashSet}; use futures::{future, Future, BoxFuture}; -use ethcore_logger::RotatingLogger; use util::Address; use util::misc::version_data; -use crypto::ecies; +use crypto::{DEFAULT_MAC, ecies}; use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::{SyncProvider, ManageNetwork}; @@ -34,8 +33,9 @@ use ethcore::ids::BlockId; use ethcore::miner::MinerService; use ethcore::mode::Mode; use ethcore::transaction::SignedTransaction; +use ethcore_logger::RotatingLogger; +use node_health::{NodeHealth, Health}; use updater::{Service as UpdateService}; -use crypto::DEFAULT_MAC; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; @@ -53,17 +53,13 @@ use v1::types::{ }; /// Parity implementation. -pub struct ParityClient where - C: MiningBlockChainClient, - M: MinerService, - S: SyncProvider, - U: UpdateService, -{ +pub struct ParityClient { client: Arc, miner: Arc, - sync: Arc, updater: Arc, + sync: Arc, net: Arc, + health: NodeHealth, accounts: Option>, logger: Arc, settings: Arc, @@ -73,39 +69,39 @@ pub struct ParityClient where eip86_transition: u64, } -impl ParityClient where +impl ParityClient where C: MiningBlockChainClient, - M: MinerService, - S: SyncProvider, - U: UpdateService, { /// Creates new `ParityClient`. pub fn new( - client: &Arc, - miner: &Arc, - sync: &Arc, - updater: &Arc, - net: &Arc, - store: &Option>, + client: Arc, + miner: Arc, + sync: Arc, + updater: Arc, + net: Arc, + health: NodeHealth, + accounts: Option>, logger: Arc, settings: Arc, signer: Option>, dapps_address: Option<(String, u16)>, ws_address: Option<(String, u16)>, ) -> Self { + let eip86_transition = client.eip86_transition(); ParityClient { - client: client.clone(), - miner: miner.clone(), - sync: sync.clone(), - updater: updater.clone(), - net: net.clone(), - accounts: store.clone(), - logger: logger, - settings: settings, - signer: signer, - dapps_address: dapps_address, - ws_address: ws_address, - eip86_transition: client.eip86_transition(), + client, + miner, + sync, + updater, + net, + health, + accounts, + logger, + settings, + signer, + dapps_address, + ws_address, + eip86_transition, } } @@ -116,10 +112,9 @@ impl ParityClient where } } -impl Parity for ParityClient where - M: MinerService + 'static, +impl Parity for ParityClient where C: MiningBlockChainClient + 'static, - S: SyncProvider + 'static, + M: MinerService + 'static, U: UpdateService + 'static, { type Metadata = Metadata; @@ -429,4 +424,10 @@ impl Parity for ParityClient where future::done(result).boxed() } + + fn node_health(&self) -> BoxFuture { + self.health.health() + .map_err(|err| errors::internal("Health API failure.", err)) + .boxed() + } } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index b5cdada6f..3c98ea8ef 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -15,13 +15,15 @@ // along with Parity. If not, see . use std::sync::Arc; -use ethcore_logger::RotatingLogger; -use util::{Address, U256}; -use ethsync::ManageNetwork; use ethcore::account_provider::AccountProvider; use ethcore::client::{TestBlockChainClient, Executed}; use ethcore::miner::LocalTransactionStatus; +use ethcore_logger::RotatingLogger; use ethstore::ethkey::{Generator, Random}; +use ethsync::ManageNetwork; +use node_health::{self, NodeHealth}; +use parity_reactor; +use util::Address; use jsonrpc_core::IoHandler; use v1::{Parity, ParityClient}; @@ -30,13 +32,14 @@ use v1::helpers::{SignerService, NetworkSettings}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestUpdater}; use super::manage_network::TestManageNetwork; -pub type TestParityClient = ParityClient; +pub type TestParityClient = ParityClient; pub struct Dependencies { pub miner: Arc, pub client: Arc, pub sync: Arc, pub updater: Arc, + pub health: NodeHealth, pub logger: Arc, pub settings: Arc, pub network: Arc, @@ -54,6 +57,11 @@ impl Dependencies { network_id: 3, num_peers: 120, })), + health: NodeHealth::new( + Arc::new(FakeSync), + node_health::TimeChecker::new::(&[], node_health::CpuPool::new(1)), + parity_reactor::Remote::new_sync(), + ), updater: Arc::new(TestUpdater::default()), logger: Arc::new(RotatingLogger::new("rpc=trace".to_owned())), settings: Arc::new(NetworkSettings { @@ -75,12 +83,13 @@ impl Dependencies { let opt_accounts = Some(self.accounts.clone()); ParityClient::new( - &self.client, - &self.miner, - &self.sync, - &self.updater, - &self.network, - &opt_accounts, + self.client.clone(), + self.miner.clone(), + self.sync.clone(), + self.updater.clone(), + self.network.clone(), + self.health.clone(), + opt_accounts.clone(), self.logger.clone(), self.settings.clone(), signer, @@ -102,6 +111,13 @@ impl Dependencies { } } +#[derive(Debug)] +struct FakeSync; +impl node_health::SyncStatus for FakeSync { + fn is_major_importing(&self) -> bool { false } + fn peers(&self) -> (usize, usize) { (4, 25) } +} + #[test] fn rpc_parity_accounts_info() { let deps = Dependencies::new(); @@ -507,6 +523,8 @@ fn rpc_parity_cid() { #[test] fn rpc_parity_call() { + use util::U256; + let deps = Dependencies::new(); deps.client.set_execution_result(Ok(Executed { exception: None, @@ -541,3 +559,14 @@ fn rpc_parity_call() { assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } + +#[test] +fn rpc_parity_node_health() { + let deps = Dependencies::new(); + let io = deps.default_client(); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_nodeHealth", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"peers":{"details":[4,25],"message":"","status":"ok"},"sync":{"details":false,"message":"","status":"ok"},"time":{"details":0,"message":"","status":"ok"}},"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index c9ca522c1..f1512b8ce 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -22,6 +22,7 @@ use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use futures::BoxFuture; +use node_health::Health; use v1::types::{ H160, H256, H512, U256, Bytes, CallRequest, Peers, Transaction, RpcSettings, Histogram, @@ -207,5 +208,9 @@ build_rpc_trait! { /// Call contract, returning the output data. #[rpc(meta, name = "parity_call")] fn call(&self, Self::Metadata, Vec, Trailing) -> BoxFuture, Error>; + + /// Returns node's health report. + #[rpc(async, name = "parity_nodeHealth")] + fn node_health(&self) -> BoxFuture; } } diff --git a/util/reactor/src/lib.rs b/util/reactor/src/lib.rs index e5f04d652..f5a37fb0f 100644 --- a/util/reactor/src/lib.rs +++ b/util/reactor/src/lib.rs @@ -20,7 +20,7 @@ extern crate futures; extern crate tokio_core; -use std::thread; +use std::{fmt, thread}; use std::sync::mpsc; use std::time::Duration; use futures::{Future, IntoFuture}; @@ -81,7 +81,19 @@ enum Mode { ThreadPerFuture, } -#[derive(Clone)] +impl fmt::Debug for Mode { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + use self::Mode::*; + + match *self { + Tokio(_) => write!(fmt, "tokio"), + Sync => write!(fmt, "synchronous"), + ThreadPerFuture => write!(fmt, "thread per future"), + } + } +} + +#[derive(Debug, Clone)] pub struct Remote { inner: Mode, }