diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 898934965..eb5677cfa 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -322,6 +322,16 @@ impl LightProtocol { .map(|peer| peer.lock().status.clone()) } + /// Get number of (connected, active) peers. + pub fn peer_count(&self) -> (usize, usize) { + let num_pending = self.pending_peers.read().len(); + let peers = self.peers.read(); + ( + num_pending + peers.len(), + peers.values().filter(|p| !p.lock().pending_requests.is_empty()).count(), + ) + } + /// Check the maximum amount of requests of a specific type /// which a peer would be able to serve. Returns zero if the /// peer is unknown or has no buffer flow parameters. diff --git a/ethcore/light/src/net/request_set.rs b/ethcore/light/src/net/request_set.rs index c9f278776..9a26b24b1 100644 --- a/ethcore/light/src/net/request_set.rs +++ b/ethcore/light/src/net/request_set.rs @@ -110,6 +110,14 @@ impl RequestSet { pub fn collect_ids(&self) -> F where F: FromIterator { self.ids.keys().cloned().collect() } + + /// Number of requests in the set. + pub fn len(&self) -> usize { + self.ids.len() + } + + /// Whether the set is empty. + pub fn is_empty(&self) -> bool { self.len() == 0 } } #[cfg(test)] diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index 8ca6a64f6..d17a863f5 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -245,6 +245,31 @@ impl TransactionQueue { .collect() } + /// Get all transactions not ready to be propagated. + /// `best_block_number` and `best_block_timestamp` are used to filter out conditionally + /// propagated transactions. + /// + /// Returned transactions are batched by sender, in order of ascending nonce. + pub fn future_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { + self.by_account.values() + .flat_map(|acct_txs| { + acct_txs.current.iter().skip_while(|tx| match tx.condition { + None => true, + Some(Condition::Number(blk_num)) => blk_num <= best_block_number, + Some(Condition::Timestamp(time)) => time <= best_block_timestamp, + }).chain(acct_txs.future.values()).map(|info| info.hash) + }) + .filter_map(|hash| match self.by_hash.get(&hash) { + Some(tx) => Some(tx.clone()), + None => { + warn!(target: "txqueue", "Inconsistency detected between `by_hash` and `by_account`: {} not stored.", + hash); + None + } + }) + .collect() + } + /// Addresses for which we store transactions. pub fn queued_senders(&self) -> Vec
{ self.by_account.keys().cloned().collect() @@ -471,4 +496,22 @@ mod tests { assert!(txq.transaction(&hash).is_none()); } + + #[test] + fn future_transactions() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + for i in (0..1).chain(3..10) { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.import(tx.into()).unwrap(); + } + + assert_eq!(txq.future_transactions(0, 0).len(), 7); + assert_eq!(txq.next_nonce(&sender).unwrap(), 1.into()); + } } diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 13ae9a0ca..0bea7f9a1 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -28,6 +28,7 @@ use light::TransactionQueue as LightTransactionQueue; use rlp::{self, Stream as StreamRlp}; use util::{Address, H520, H256, U256, Uint, Bytes, Mutex, RwLock}; use util::sha3::Hashable; +use stats::Corpus; use ethkey::Signature; use ethsync::LightSync; @@ -161,11 +162,16 @@ impl Dispatcher for FullDispatcher, - client: Arc, - on_demand: Arc, - cache: Arc>, - transaction_queue: Arc>, + /// Sync service. + pub sync: Arc, + /// Header chain client. + pub client: Arc, + /// On-demand request service. + pub on_demand: Arc, + /// Data cache. + pub cache: Arc>, + /// Transaction queue. + pub transaction_queue: Arc>, } impl LightDispatcher { @@ -187,13 +193,75 @@ impl LightDispatcher { transaction_queue: transaction_queue, } } + + /// Get a recent gas price corpus. + // TODO: this could be `impl Trait`. + pub fn gas_price_corpus(&self) -> BoxFuture, Error> { + const GAS_PRICE_SAMPLE_SIZE: usize = 100; + + if let Some(cached) = self.cache.lock().gas_price_corpus() { + return future::ok(cached).boxed() + } + + let cache = self.cache.clone(); + let eventual_corpus = self.sync.with_context(|ctx| { + // get some recent headers with gas used, + // and request each of the blocks from the network. + let block_futures = self.client.ancestry_iter(BlockId::Latest) + .filter(|hdr| hdr.gas_used() != U256::default()) + .take(GAS_PRICE_SAMPLE_SIZE) + .map(request::Body::new) + .map(|req| self.on_demand.block(ctx, req)); + + // as the blocks come in, collect gas prices into a vector + stream::futures_unordered(block_futures) + .fold(Vec::new(), |mut v, block| { + for t in block.transaction_views().iter() { + v.push(t.gas_price()) + } + + future::ok(v) + }) + .map(move |v| { + // produce a corpus from the vector, cache it, and return + // the median as the intended gas price. + let corpus: ::stats::Corpus<_> = v.into(); + cache.lock().set_gas_price_corpus(corpus.clone()); + corpus + }) + }); + + match eventual_corpus { + Some(corp) => corp.map_err(|_| errors::no_light_peers()).boxed(), + None => future::err(errors::network_disabled()).boxed(), + } + } + + /// Get an account's next nonce. + pub fn next_nonce(&self, addr: Address) -> BoxFuture { + // fast path where we don't go to network; nonce provided or can be gotten from queue. + let maybe_nonce = self.transaction_queue.read().next_nonce(&addr); + if let Some(nonce) = maybe_nonce { + return future::ok(nonce).boxed() + } + + let best_header = self.client.best_block_header(); + let nonce_future = self.sync.with_context(|ctx| self.on_demand.account(ctx, request::Account { + header: best_header, + address: addr, + })); + + match nonce_future { + Some(x) => x.map(|acc| acc.nonce).map_err(|_| errors::no_light_peers()).boxed(), + None => future::err(errors::network_disabled()).boxed() + } + } } impl Dispatcher for LightDispatcher { fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address) -> BoxFuture { - const GAS_PRICE_SAMPLE_SIZE: usize = 100; const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); let gas_limit = self.client.best_block_header().gas_limit(); @@ -214,53 +282,13 @@ impl Dispatcher for LightDispatcher { } }; - // fast path for gas price supplied or cached corpus. - let known_price = request_gas_price.or_else(|| - self.cache.lock().gas_price_corpus().and_then(|corp| corp.median().cloned()) - ); - - match known_price { + // fast path for known gas price. + match request_gas_price { Some(gas_price) => future::ok(with_gas_price(gas_price)).boxed(), - None => { - let cache = self.cache.clone(); - let gas_price_res = self.sync.with_context(|ctx| { - - // get some recent headers with gas used, - // and request each of the blocks from the network. - let block_futures = self.client.ancestry_iter(BlockId::Latest) - .filter(|hdr| hdr.gas_used() != U256::default()) - .take(GAS_PRICE_SAMPLE_SIZE) - .map(request::Body::new) - .map(|req| self.on_demand.block(ctx, req)); - - // as the blocks come in, collect gas prices into a vector - stream::futures_unordered(block_futures) - .fold(Vec::new(), |mut v, block| { - for t in block.transaction_views().iter() { - v.push(t.gas_price()) - } - - future::ok(v) - }) - .map(move |v| { - // produce a corpus from the vector, cache it, and return - // the median as the intended gas price. - let corpus: ::stats::Corpus<_> = v.into(); - cache.lock().set_gas_price_corpus(corpus.clone()); - - - corpus.median().cloned().unwrap_or(DEFAULT_GAS_PRICE) - }) - .map_err(|_| errors::no_light_peers()) - }); - - // attempt to fetch the median, but fall back to a hardcoded - // value in case of weak corpus or disconnected network. - match gas_price_res { - Some(res) => res.map(with_gas_price).boxed(), - None => future::ok(with_gas_price(DEFAULT_GAS_PRICE)).boxed() - } - } + None => self.gas_price_corpus().and_then(|corp| match corp.median() { + Some(median) => future::ok(*median), + None => future::ok(DEFAULT_GAS_PRICE), // fall back to default on error. + }).map(with_gas_price).boxed() } } @@ -269,7 +297,6 @@ impl Dispatcher for LightDispatcher { { let network_id = self.client.signing_network_id(); let address = filled.from; - let best_header = self.client.best_block_header(); let with_nonce = move |filled: FilledTransactionRequest, nonce| { let t = Transaction { @@ -294,25 +321,14 @@ impl Dispatcher for LightDispatcher { })) }; - // fast path where we don't go to network; nonce provided or can be gotten from queue. - let maybe_nonce = filled.nonce.or_else(|| self.transaction_queue.read().next_nonce(&address)); - if let Some(nonce) = maybe_nonce { + // fast path for pre-filled nonce. + if let Some(nonce) = filled.nonce { return future::done(with_nonce(filled, nonce)).boxed() } - let nonce_future = self.sync.with_context(|ctx| self.on_demand.account(ctx, request::Account { - header: best_header, - address: address, - })); - - let nonce_future = match nonce_future { - Some(x) => x, - None => return future::err(errors::no_light_peers()).boxed() - }; - - nonce_future + self.next_nonce(address) .map_err(|_| errors::no_light_peers()) - .and_then(move |acc| with_nonce(filled, acc.nonce)) + .and_then(move |nonce| with_nonce(filled, nonce)) .boxed() } diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index b58999f84..e187c4df6 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -60,6 +60,14 @@ pub fn unimplemented(details: Option) -> Error { } } +pub fn light_unimplemented(details: Option) -> Error { + Error { + code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), + message: "This request is unsupported for light clients.".into(), + data: details.map(Value::String), + } +} + pub fn request_not_found() -> Error { Error { code: ErrorCode::ServerError(codes::REQUEST_NOT_FOUND), diff --git a/rpc/src/v1/impls/light/mod.rs b/rpc/src/v1/impls/light/mod.rs index 1772d5b58..71a3a497d 100644 --- a/rpc/src/v1/impls/light/mod.rs +++ b/rpc/src/v1/impls/light/mod.rs @@ -15,7 +15,12 @@ // along with Parity. If not, see . //! RPC implementations for the light client. +//! +//! This doesn't re-implement all of the RPC APIs, just those which aren't +//! significantly generic to be reused. pub mod eth; +pub mod parity; pub use self::eth::EthClient; +pub use self::parity::ParityClient; diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs new file mode 100644 index 000000000..337324395 --- /dev/null +++ b/rpc/src/v1/impls/light/parity.rs @@ -0,0 +1,335 @@ +// 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 . + +//! Parity-specific rpc implementation. +use std::sync::Arc; +use std::collections::{BTreeMap, HashSet}; +use futures::{self, Future, BoxFuture}; + +use util::RotatingLogger; +use util::misc::version_data; + +use crypto::ecies; +use ethkey::{Brain, Generator}; +use ethstore::random_phrase; +use ethsync::LightSyncProvider; +use ethcore::account_provider::AccountProvider; + +use jsonrpc_core::Error; +use jsonrpc_macros::Trailing; +use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; +use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC}; +use v1::metadata::Metadata; +use v1::traits::Parity; +use v1::types::{ + Bytes, U256, H160, H256, H512, + Peers, Transaction, RpcSettings, Histogram, + TransactionStats, LocalTransactionStatus, + BlockNumber, ConsensusCapability, VersionInfo, + OperationsInfo, DappId, ChainStatus, + AccountInfo, HwAccountInfo +}; + +/// Parity implementation for light client. +pub struct ParityClient { + light_dispatch: Arc, + accounts: Arc, + logger: Arc, + settings: Arc, + signer: Option>, + dapps_interface: Option, + dapps_port: Option, +} + +impl ParityClient { + /// Creates new `ParityClient`. + pub fn new( + light_dispatch: Arc, + accounts: Arc, + logger: Arc, + settings: Arc, + signer: Option>, + dapps_interface: Option, + dapps_port: Option, + ) -> Self { + ParityClient { + light_dispatch: light_dispatch, + accounts: accounts, + logger: logger, + settings: settings, + signer: signer, + dapps_interface: dapps_interface, + dapps_port: dapps_port, + } + } +} + +impl Parity for ParityClient { + type Metadata = Metadata; + + fn accounts_info(&self, dapp: Trailing) -> Result, Error> { + let dapp = dapp.0; + + let store = &self.accounts; + let dapp_accounts = store + .note_dapp_used(dapp.clone().into()) + .and_then(|_| store.dapps_addresses(dapp.into())) + .map_err(|e| errors::internal("Could not fetch accounts.", e))? + .into_iter().collect::>(); + + let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?; + let other = store.addresses_info(); + + Ok(info + .into_iter() + .chain(other.into_iter()) + .filter(|&(ref a, _)| dapp_accounts.contains(a)) + .map(|(a, v)| (H160::from(a), AccountInfo { name: v.name })) + .collect() + ) + } + + fn hardware_accounts_info(&self) -> Result, Error> { + let store = &self.accounts; + let info = store.hardware_accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?; + Ok(info + .into_iter() + .map(|(a, v)| (H160::from(a), HwAccountInfo { name: v.name, manufacturer: v.meta })) + .collect() + ) + } + + fn default_account(&self, meta: Self::Metadata) -> BoxFuture { + let dapp_id = meta.dapp_id(); + let default_account = move || { + Ok(self.accounts + .dapps_addresses(dapp_id.into()) + .ok() + .and_then(|accounts| accounts.get(0).cloned()) + .map(|acc| acc.into()) + .unwrap_or_default()) + }; + + futures::done(default_account()).boxed() + } + + fn transactions_limit(&self) -> Result { + Ok(usize::max_value()) + } + + fn min_gas_price(&self) -> Result { + Ok(U256::default()) + } + + fn extra_data(&self) -> Result { + Ok(Bytes::default()) + } + + fn gas_floor_target(&self) -> Result { + Ok(U256::default()) + } + + fn gas_ceil_target(&self) -> Result { + Ok(U256::default()) + } + + fn dev_logs(&self) -> Result, Error> { + let logs = self.logger.logs(); + Ok(logs.as_slice().to_owned()) + } + + fn dev_logs_levels(&self) -> Result { + Ok(self.logger.levels().to_owned()) + } + + fn net_chain(&self) -> Result { + Ok(self.settings.chain.clone()) + } + + fn net_peers(&self) -> Result { + let peers = self.light_dispatch.sync.peers().into_iter().map(Into::into).collect(); + let peer_numbers = self.light_dispatch.sync.peer_numbers(); + + Ok(Peers { + active: peer_numbers.active, + connected: peer_numbers.connected, + max: peer_numbers.max as u32, + peers: peers, + }) + } + + fn net_port(&self) -> Result { + Ok(self.settings.network_port) + } + + fn node_name(&self) -> Result { + Ok(self.settings.name.clone()) + } + + fn registry_address(&self) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn rpc_settings(&self) -> Result { + Ok(RpcSettings { + enabled: self.settings.rpc_enabled, + interface: self.settings.rpc_interface.clone(), + port: self.settings.rpc_port as u64, + }) + } + + fn default_extra_data(&self) -> Result { + Ok(Bytes::new(version_data())) + } + + fn gas_price_histogram(&self) -> BoxFuture { + self.light_dispatch.gas_price_corpus() + .and_then(|corpus| corpus.histogram(10).ok_or_else(errors::not_enough_data)) + .map(Into::into) + .boxed() + } + + fn unsigned_transactions_count(&self) -> Result { + match self.signer { + None => Err(errors::signer_disabled()), + Some(ref signer) => Ok(signer.len()), + } + } + + fn generate_secret_phrase(&self) -> Result { + Ok(random_phrase(12)) + } + + fn phrase_to_address(&self, phrase: String) -> Result { + Ok(Brain::new(phrase).generate().unwrap().address().into()) + } + + fn list_accounts(&self, _: u64, _: Option, _: Trailing) -> Result>, Error> { + Err(errors::light_unimplemented(None)) + } + + fn list_storage_keys(&self, _: H160, _: u64, _: Option, _: Trailing) -> Result>, Error> { + Err(errors::light_unimplemented(None)) + } + + fn encrypt_message(&self, key: H512, phrase: Bytes) -> Result { + ecies::encrypt(&key.into(), &DEFAULT_MAC, &phrase.0) + .map_err(errors::encryption_error) + .map(Into::into) + } + + fn pending_transactions(&self) -> Result, Error> { + let txq = self.light_dispatch.transaction_queue.read(); + let chain_info = self.light_dispatch.client.chain_info(); + Ok( + txq.ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp) + .into_iter() + .map(Into::into) + .collect::>() + ) + } + + fn future_transactions(&self) -> Result, Error> { + let txq = self.light_dispatch.transaction_queue.read(); + let chain_info = self.light_dispatch.client.chain_info(); + Ok( + txq.future_transactions(chain_info.best_block_number, chain_info.best_block_timestamp) + .into_iter() + .map(Into::into) + .collect::>() + ) + } + + fn pending_transactions_stats(&self) -> Result, Error> { + let stats = self.light_dispatch.sync.transactions_stats(); + Ok(stats.into_iter() + .map(|(hash, stats)| (hash.into(), stats.into())) + .collect() + ) + } + + fn local_transactions(&self) -> Result, Error> { + let mut map = BTreeMap::new(); + let chain_info = self.light_dispatch.client.chain_info(); + let (best_num, best_tm) = (chain_info.best_block_number, chain_info.best_block_timestamp); + let txq = self.light_dispatch.transaction_queue.read(); + + for pending in txq.ready_transactions(best_num, best_tm) { + map.insert(pending.hash().into(), LocalTransactionStatus::Pending); + } + + for future in txq.future_transactions(best_num, best_tm) { + map.insert(future.hash().into(), LocalTransactionStatus::Future); + } + + // TODO: other types? + + Ok(map) + } + + fn signer_port(&self) -> Result { + self.signer + .clone() + .and_then(|signer| signer.address()) + .map(|address| address.1) + .ok_or_else(|| errors::signer_disabled()) + } + + fn dapps_port(&self) -> Result { + self.dapps_port + .ok_or_else(|| errors::dapps_disabled()) + } + + fn dapps_interface(&self) -> Result { + self.dapps_interface.clone() + .ok_or_else(|| errors::dapps_disabled()) + } + + fn next_nonce(&self, address: H160) -> BoxFuture { + self.light_dispatch.next_nonce(address.into()).map(Into::into).boxed() + } + + fn mode(&self) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn enode(&self) -> Result { + self.light_dispatch.sync.enode().ok_or_else(errors::network_disabled) + } + + fn consensus_capability(&self) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn version_info(&self) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn releases_info(&self) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn chain_status(&self) -> Result { + let chain_info = self.light_dispatch.client.chain_info(); + + let gap = chain_info.ancient_block_number.map(|x| U256::from(x + 1)) + .and_then(|first| chain_info.first_block_number.map(|last| (first, U256::from(last)))); + + Ok(ChainStatus { + block_gap: gap.map(|(x, y)| (x.into(), y.into())), + }) + } +} diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 8dbb4578f..dea155168 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Weak}; use std::str::FromStr; use std::collections::{BTreeMap, HashSet}; -use futures::{self, Future, BoxFuture}; +use futures::{self, future, Future, BoxFuture}; use util::{RotatingLogger, Address}; use util::misc::version_data; @@ -235,8 +235,13 @@ impl Parity for ParityClient where Ok(Bytes::new(version_data())) } - fn gas_price_histogram(&self) -> Result { - take_weak!(self.client).gas_price_corpus(100).histogram(10).ok_or_else(errors::not_enough_data).map(Into::into) + fn gas_price_histogram(&self) -> BoxFuture { + future::done(take_weakf!(self.client) + .gas_price_corpus(100) + .histogram(10) + .ok_or_else(errors::not_enough_data) + .map(Into::into) + ).boxed() } fn unsigned_transactions_count(&self) -> Result { @@ -315,16 +320,16 @@ impl Parity for ParityClient where .ok_or_else(|| errors::dapps_disabled()) } - fn next_nonce(&self, address: H160) -> Result { + fn next_nonce(&self, address: H160) -> BoxFuture { let address: Address = address.into(); - let miner = take_weak!(self.miner); - let client = take_weak!(self.client); + let miner = take_weakf!(self.miner); + let client = take_weakf!(self.client); - Ok(miner.last_nonce(&address) + future::ok(miner.last_nonce(&address) .map(|n| n + 1.into()) .unwrap_or_else(|| client.latest_nonce(&address)) .into() - ) + ).boxed() } fn mode(&self) -> Result { diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index d5ecbd5e6..10e3b54bd 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -101,8 +101,8 @@ build_rpc_trait! { fn default_extra_data(&self) -> Result; /// Returns distribution of gas price in latest blocks. - #[rpc(name = "parity_gasPriceHistogram")] - fn gas_price_histogram(&self) -> Result; + #[rpc(async, name = "parity_gasPriceHistogram")] + fn gas_price_histogram(&self) -> BoxFuture; /// Returns number of unsigned transactions waiting in the signer queue (if signer enabled) /// Returns error when signer is disabled @@ -164,8 +164,8 @@ build_rpc_trait! { fn dapps_interface(&self) -> Result; /// Returns next nonce for particular sender. Should include all transactions in the queue. - #[rpc(name = "parity_nextNonce")] - fn next_nonce(&self, H160) -> Result; + #[rpc(async, name = "parity_nextNonce")] + fn next_nonce(&self, H160) -> BoxFuture; /// Get the mode. Results one of: "active", "passive", "dark", "offline". #[rpc(name = "parity_mode")] diff --git a/sync/src/api.rs b/sync/src/api.rs index 9b1ace73b..4cdc9d37a 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -28,7 +28,7 @@ use ethcore::client::{BlockChainClient, ChainNotify}; use ethcore::snapshot::SnapshotService; use ethcore::header::BlockNumber; use sync_io::NetSyncIo; -use chain::{ChainSync, SyncStatus}; +use chain::{ChainSync, SyncStatus as EthSyncStatus}; use std::net::{SocketAddr, AddrParseError}; use ipc::{BinaryConvertable, BinaryConvertError, IpcConfig}; use std::str::FromStr; @@ -82,12 +82,12 @@ impl Default for SyncConfig { } binary_fixed_size!(SyncConfig); -binary_fixed_size!(SyncStatus); +binary_fixed_size!(EthSyncStatus); /// Current sync status pub trait SyncProvider: Send + Sync { /// Get sync status - fn status(&self) -> SyncStatus; + fn status(&self) -> EthSyncStatus; /// Get peers information fn peers(&self) -> Vec; @@ -240,7 +240,7 @@ impl EthSync { #[cfg_attr(feature = "ipc", ipc(client_ident="SyncClient"))] impl SyncProvider for EthSync { /// Get sync status - fn status(&self) -> SyncStatus { + fn status(&self) -> EthSyncStatus { self.eth_handler.sync.write().status() } @@ -620,6 +620,35 @@ pub struct ServiceConfiguration { pub io_path: String, } +/// Numbers of peers (max, min, active). +#[derive(Debug, Clone)] +#[cfg_attr(feature = "ipc", binary)] +pub struct PeerNumbers { + /// Number of connected peers. + pub connected: usize, + /// Number of active peers. + pub active: usize, + /// Max peers. + pub max: usize, + /// Min peers. + pub min: usize, +} + +/// Light synchronization. +pub trait LightSyncProvider { + /// Get peer numbers. + fn peer_numbers(&self) -> PeerNumbers; + + /// Get peers information + fn peers(&self) -> Vec; + + /// Get the enode if available. + fn enode(&self) -> Option; + + /// Returns propagation count for pending transactions. + fn transactions_stats(&self) -> BTreeMap; +} + /// Configuration for the light sync. pub struct LightSyncParams { /// Network configuration. @@ -728,3 +757,46 @@ impl ManageNetwork for LightSync { } } +impl LightSyncProvider for LightSync { + fn peer_numbers(&self) -> PeerNumbers { + let (connected, active) = self.proto.peer_count(); + let config = self.network_config(); + PeerNumbers { + connected: connected, + active: active, + max: config.max_peers as usize, + min: config.min_peers as usize, + } + } + + fn peers(&self) -> Vec { + self.network.with_context_eval(self.subprotocol_name, |ctx| { + let peer_ids = self.network.connected_peers(); + + peer_ids.into_iter().filter_map(|peer_id| { + let session_info = match ctx.session_info(peer_id) { + None => return None, + Some(info) => info, + }; + + Some(PeerInfo { + id: session_info.id.map(|id| id.hex()), + client_version: session_info.client_version, + capabilities: session_info.peer_capabilities.into_iter().map(|c| c.to_string()).collect(), + remote_address: session_info.remote_address, + local_address: session_info.local_address, + eth_info: None, + les_info: self.proto.peer_status(&peer_id).map(Into::into), + }) + }).collect() + }).unwrap_or_else(Vec::new) + } + + fn enode(&self) -> Option { + self.network.external_url() + } + + fn transactions_stats(&self) -> BTreeMap { + Default::default() // TODO + } +} diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 8ea6705f2..6cd4fade5 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -72,11 +72,7 @@ mod api { #[cfg(not(feature = "ipc"))] mod api; -pub use api::{ - EthSync, Params, SyncProvider, ManageNetwork, SyncConfig, - ServiceConfiguration, NetworkConfiguration, PeerInfo, AllowIP, TransactionStats, - LightSync, LightSyncParams, LesProtocolInfo, EthProtocolInfo, -}; +pub use api::*; pub use chain::{SyncStatus, SyncState}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError};