From 2b91c922c14d21d6704d606397a5a2369c86d0bf Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 16 Feb 2017 16:08:58 +0100 Subject: [PATCH 01/51] get signing network ID for light client --- ethcore/light/src/client/mod.rs | 41 ++++++++++++++++++++++++++++++++- rpc/src/v1/helpers/dispatch.rs | 2 +- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index a113b4367..bebe89e0e 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use ethcore::block_import_error::BlockImportError; use ethcore::block_status::BlockStatus; -use ethcore::client::ClientReport; +use ethcore::client::{ClientReport, EnvInfo}; use ethcore::engines::Engine; use ethcore::ids::BlockId; use ethcore::header::Header; @@ -62,6 +62,9 @@ pub trait LightChainClient: Send + Sync { /// Get the best block header. fn best_block_header(&self) -> encoded::Header; + /// Get the signing network ID. + fn signing_network_id(&self) -> Option; + /// Query whether a block is known. fn is_known(&self, hash: &H256) -> bool; @@ -164,6 +167,11 @@ impl Client { self.chain.best_header() } + /// Get the signing network id. + pub fn signing_network_id(&self) -> Option { + self.engine.signing_network_id(&self.latest_env_info()) + } + /// Flush the header queue. pub fn flush_queue(&self) { self.queue.flush() @@ -217,6 +225,33 @@ impl Client { pub fn engine(&self) -> &Engine { &*self.engine } + + fn latest_env_info(&self) -> EnvInfo { + let header = self.best_block_header(); + + EnvInfo { + number: header.number(), + author: header.author(), + timestamp: header.timestamp(), + difficulty: header.difficulty(), + last_hashes: self.build_last_hashes(header.hash()), + gas_used: Default::default(), + gas_limit: header.gas_limit(), + } + } + + fn build_last_hashes(&self, mut parent_hash: H256) -> Arc> { + let mut v = Vec::with_capacity(256); + for _ in 0..255 { + v.push(parent_hash); + match self.block_header(BlockId::Hash(parent_hash)) { + Some(header) => parent_hash = header.hash(), + None => break, + } + } + + Arc::new(v) + } } impl LightChainClient for Client { @@ -234,6 +269,10 @@ impl LightChainClient for Client { Client::best_block_header(self) } + fn signing_network_id(&self) -> Option { + Client::signing_network_id(self) + } + fn is_known(&self, hash: &H256) -> bool { self.status(hash) == BlockStatus::InChain } diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index de0207d79..e8fbf9b76 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -207,7 +207,7 @@ impl Dispatcher for LightDispatcher { fn sign(&self, accounts: Arc, filled: FilledTransactionRequest, password: SignWith) -> BoxFuture, Error> { - let network_id = None; // TODO: fetch from client. + let network_id = self.client.signing_network_id(); let address = filled.from; let best_header = self.client.best_block_header(); From 3b9741e9d83b787495ae8c1450b656b8f416952a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 16 Feb 2017 18:07:28 +0100 Subject: [PATCH 02/51] Implement the basic data cache --- ethcore/light/src/cache.rs | 174 +++++++++++++++++++++++++++++++++++++ ethcore/light/src/lib.rs | 1 + 2 files changed, 175 insertions(+) create mode 100644 ethcore/light/src/cache.rs diff --git a/ethcore/light/src/cache.rs b/ethcore/light/src/cache.rs new file mode 100644 index 000000000..a64c9076e --- /dev/null +++ b/ethcore/light/src/cache.rs @@ -0,0 +1,174 @@ +// 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 . + +//! Cache for data fetched from the network. +//! +//! Stores ancient block headers, bodies, receipts, and total difficulties. +//! Furthermore, stores a "gas price corpus" of relative recency, which is a sorted +//! vector of all gas prices from a recent range of blocks. + +use ethcore::encoded; +use ethcore::header::BlockNumber; +use ethcore::receipt::Receipt; + +use time::{SteadyTime, Duration}; +use util::{U256, H256}; +use util::cache::MemoryLruCache; + +/// Configuration for how much data to cache. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CacheSizes { + /// Maximum size, in bytes, of cached headers. + pub headers: usize, + /// Maximum size, in bytes, of cached canonical hashes. + pub canon_hashes: usize, + /// Maximum size, in bytes, of cached block bodies. + pub bodies: usize, + /// Maximum size, in bytes, of cached block receipts. + pub receipts: usize, + /// Maximum size, in bytes, of cached chain score for the block. + pub chain_score: usize, +} + +impl Default for CacheSizes { + fn default() -> Self { + const MB: usize = 1024 * 1024; + CacheSizes { + headers: 10 * MB, + canon_hashes: 3 * MB, + bodies: 20 * MB, + receipts: 10 * MB, + chain_score: 7 * MB, + } + } +} + +/// The light client data cache. +/// +/// Note that almost all getter methods take `&mut self` due to the necessity to update +/// the underlying LRU-caches on read. +pub struct Cache { + headers: MemoryLruCache, + canon_hashes: MemoryLruCache, + bodies: MemoryLruCache, + receipts: MemoryLruCache>, + chain_score: MemoryLruCache, + corpus: Option<(Vec, SteadyTime)>, + corpus_expiration: Duration, +} + +impl Cache { + /// Create a new data cache with the given sizes and gas price corpus expiration time. + pub fn new(sizes: CacheSizes, corpus_expiration: Duration) -> Self { + Cache { + headers: MemoryLruCache::new(sizes.headers), + canon_hashes: MemoryLruCache::new(sizes.canon_hashes), + bodies: MemoryLruCache::new(sizes.bodies), + receipts: MemoryLruCache::new(sizes.receipts), + chain_score: MemoryLruCache::new(sizes.chain_score), + corpus: None, + corpus_expiration: corpus_expiration, + } + } + + /// Query header by hash. + pub fn block_header(&mut self, hash: &H256) -> Option { + self.headers.get_mut(hash).map(|x| x.clone()) + } + + /// Query hash by number. + pub fn block_hash(&mut self, num: &BlockNumber) -> Option { + self.canon_hashes.get_mut(num).map(|x| x.clone()) + } + + /// Query block body by block hash. + pub fn block_body(&mut self, hash: &H256) -> Option { + self.bodies.get_mut(hash).map(|x| x.clone()) + } + + /// Query block receipts by block hash. + pub fn block_receipts(&mut self, hash: &H256) -> Option> { + self.receipts.get_mut(hash).map(|x| x.clone()) + } + + /// Query chain score by block hash. + pub fn chain_score(&mut self, hash: &H256) -> Option { + self.chain_score.get_mut(hash).map(|x| x.clone()) + } + + /// Cache the given header. + pub fn insert_block_header(&mut self, hash: H256, hdr: encoded::Header) { + self.headers.insert(hash, hdr); + } + + /// Cache the given canonical block hash. + pub fn insert_block_hash(&mut self, num: BlockNumber, hash: H256) { + self.canon_hashes.insert(num, hash); + } + + /// Cache the given block body. + pub fn insert_block_body(&mut self, hash: H256, body: encoded::Body) { + self.bodies.insert(hash, body); + } + + /// Cache the given block receipts. + pub fn insert_block_receipts(&mut self, hash: H256, receipts: Vec) { + self.receipts.insert(hash, receipts); + } + + /// Cache the given chain scoring. + pub fn insert_chain_score(&mut self, hash: H256, score: U256) { + self.chain_score.insert(hash, score); + } + + /// Get gas price corpus, if recent enough. + pub fn gas_price_corpus(&self) -> Option> { + let now = SteadyTime::now(); + + self.corpus.as_ref().and_then(|&(ref corpus, ref tm)| { + if *tm + self.corpus_expiration >= now { + Some(corpus.clone()) + } else { + None + } + }) + } + + /// Set the cached gas price corpus. + pub fn set_gas_price_corpus(&mut self, corpus: Vec) { + self.corpus = Some((corpus, SteadyTime::now())) + } +} + +#[cfg(test)] +mod tests { + use super::Cache; + use time::{Duration, SteadyTime}; + + #[test] + fn corpus_inaccessible() { + let mut cache = Cache::new(Default::default(), Duration::hours(5)); + + cache.set_gas_price_corpus(vec![]); + assert_eq!(cache.gas_price_corpus(), Some(vec![])); + + { + let corpus_time = &mut cache.corpus.as_mut().unwrap().1; + *corpus_time = *corpus_time - Duration::hours(6); + } + assert!(cache.gas_price_corpus().is_none()); + } +} diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 94d267c7a..53d32f44e 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -37,6 +37,7 @@ pub mod cht; pub mod net; pub mod on_demand; pub mod transaction_queue; +pub mod cache; #[cfg(not(feature = "ipc"))] pub mod provider; From 48cf591e6694eae2911819f552772f6b1d6052b0 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 16 Feb 2017 20:46:59 +0100 Subject: [PATCH 03/51] integrate cache in on-demand --- ethcore/light/src/cache.rs | 2 +- ethcore/light/src/on_demand/mod.rs | 143 ++++++++++++++++++++++++----- rpc/src/v1/impls/light/eth.rs | 2 +- 3 files changed, 124 insertions(+), 23 deletions(-) diff --git a/ethcore/light/src/cache.rs b/ethcore/light/src/cache.rs index a64c9076e..defa247ec 100644 --- a/ethcore/light/src/cache.rs +++ b/ethcore/light/src/cache.rs @@ -156,7 +156,7 @@ impl Cache { #[cfg(test)] mod tests { use super::Cache; - use time::{Duration, SteadyTime}; + use time::Duration; #[test] fn corpus_inaccessible() { diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index c34e2d922..ec3b758ce 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -19,6 +19,7 @@ //! will take the raw data received here and extract meaningful results from it. use std::collections::HashMap; +use std::sync::Arc; use ethcore::basic_account::BasicAccount; use ethcore::encoded; @@ -28,10 +29,11 @@ use futures::{Async, Poll, Future}; use futures::sync::oneshot::{self, Sender, Receiver}; use network::PeerId; use rlp::{RlpStream, Stream}; -use util::{Bytes, RwLock, U256}; +use util::{Bytes, RwLock, Mutex, U256}; use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP}; use net::{Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId}; +use cache::Cache; use types::les_request::{self as les_request, Request as LesRequest}; pub mod request; @@ -42,9 +44,16 @@ struct Peer { capabilities: Capabilities, } +// Which portions of a CHT proof should be sent. +enum ChtProofSender { + Both(Sender<(encoded::Header, U256)>), + Header(Sender), + ChainScore(Sender), +} + // Attempted request info and sender to put received value. enum Pending { - HeaderByNumber(request::HeaderByNumber, Sender<(encoded::Header, U256)>), // num + CHT root + HeaderByNumber(request::HeaderByNumber, ChtProofSender), HeaderByHash(request::HeaderByHash, Sender), Block(request::Body, Sender), BlockReceipts(request::BlockReceipts, Sender>), @@ -58,30 +67,77 @@ enum Pending { pub struct OnDemand { peers: RwLock>, pending_requests: RwLock>, + cache: Arc>, orphaned_requests: RwLock>, } -impl Default for OnDemand { - fn default() -> Self { +impl OnDemand { + /// Create a new `OnDemand` service with the given cache. + pub fn new(cache: Arc>) -> Self { OnDemand { peers: RwLock::new(HashMap::new()), pending_requests: RwLock::new(HashMap::new()), + cache: cache, orphaned_requests: RwLock::new(Vec::new()), } } -} -impl OnDemand { /// Request a header by block number and CHT root hash. - /// Returns the header and the total difficulty. - pub fn header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver<(encoded::Header, U256)> { + /// Returns the header. + pub fn header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver { let (sender, receiver) = oneshot::channel(); - self.dispatch_header_by_number(ctx, req, sender); + let cached = { + let mut cache = self.cache.lock(); + cache.block_hash(&req.num()).and_then(|hash| cache.block_header(&hash)) + }; + + match cached { + Some(hdr) => sender.complete(hdr), + None => self.dispatch_header_by_number(ctx, req, ChtProofSender::Header(sender)), + } + receiver + } + + /// Request a canonical block's chain score. + /// Returns the chain score. + pub fn chain_score_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver { + let (sender, receiver) = oneshot::channel(); + let cached = { + let mut cache = self.cache.lock(); + cache.block_hash(&req.num()).and_then(|hash| cache.chain_score(&hash)) + }; + + match cached { + Some(score) => sender.complete(score), + None => self.dispatch_header_by_number(ctx, req, ChtProofSender::ChainScore(sender)), + } + + receiver + } + + /// Request a canonical block's chain score. + /// Returns the header and chain score. + pub fn header_and_score_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver<(encoded::Header, U256)> { + let (sender, receiver) = oneshot::channel(); + let cached = { + let mut cache = self.cache.lock(); + let hash = cache.block_hash(&req.num()); + ( + hash.clone().and_then(|hash| cache.block_header(&hash)), + hash.and_then(|hash| cache.chain_score(&hash)), + ) + }; + + match cached { + (Some(hdr), Some(score)) => sender.complete((hdr, score)), + _ => self.dispatch_header_by_number(ctx, req, ChtProofSender::Both(sender)), + } + receiver } // dispatch the request, completing the request if no peers available. - fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: Sender<(encoded::Header, U256)>) { + fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: ChtProofSender) { let num = req.num(); let cht_num = req.cht_num(); @@ -123,7 +179,10 @@ impl OnDemand { /// it as easily. pub fn header_by_hash(&self, ctx: &BasicContext, req: request::HeaderByHash) -> Receiver { let (sender, receiver) = oneshot::channel(); - self.dispatch_header_by_hash(ctx, req, sender); + match self.cache.lock().block_header(&req.0) { + Some(hdr) => sender.complete(hdr), + None => self.dispatch_header_by_hash(ctx, req, sender), + } receiver } @@ -181,7 +240,16 @@ impl OnDemand { sender.complete(encoded::Block::new(stream.out())) } else { - self.dispatch_block(ctx, req, sender); + match self.cache.lock().block_body(&req.hash) { + Some(body) => { + let mut stream = RlpStream::new_list(3); + stream.append_raw(&req.header.into_inner(), 1); + stream.append_raw(&body.into_inner(), 2); + + sender.complete(encoded::Block::new(stream.out())); + } + None => self.dispatch_block(ctx, req, sender), + } } receiver } @@ -224,7 +292,10 @@ impl OnDemand { if req.0.receipts_root() == SHA3_NULL_RLP { sender.complete(Vec::new()) } else { - self.dispatch_block_receipts(ctx, req, sender); + match self.cache.lock().block_receipts(&req.0.hash()) { + Some(receipts) => sender.complete(receipts), + None => self.dispatch_block_receipts(ctx, req, sender), + } } receiver @@ -378,8 +449,15 @@ impl OnDemand { for orphaned in to_dispatch { match orphaned { - Pending::HeaderByNumber(req, mut sender) => - if !check_hangup(&mut sender) { self.dispatch_header_by_number(ctx, req, sender) }, + Pending::HeaderByNumber(req, mut sender) => { + let hangup = match sender { + ChtProofSender::Both(ref mut s) => check_hangup(s), + ChtProofSender::Header(ref mut s) => check_hangup(s), + ChtProofSender::ChainScore(ref mut s) => check_hangup(s), + }; + + if !hangup { self.dispatch_header_by_number(ctx, req, sender) } + } Pending::HeaderByHash(req, mut sender) => if !check_hangup(&mut sender) { self.dispatch_header_by_hash(ctx, req, sender) }, Pending::Block(req, mut sender) => @@ -439,8 +517,19 @@ impl Handler for OnDemand { Pending::HeaderByNumber(req, sender) => { if let Some(&(ref header, ref proof)) = proofs.get(0) { match req.check_response(header, proof) { - Ok(header) => { - sender.complete(header); + Ok((header, score)) => { + let mut cache = self.cache.lock(); + let hash = header.hash(); + cache.insert_block_header(hash, header.clone()); + cache.insert_block_hash(header.number(), hash); + cache.insert_chain_score(hash, score); + + match sender { + ChtProofSender::Both(sender) => sender.complete((header, score)), + ChtProofSender::Header(sender) => sender.complete(header), + ChtProofSender::ChainScore(sender) => sender.complete(score), + } + return } Err(e) => { @@ -468,6 +557,7 @@ impl Handler for OnDemand { if let Some(ref header) = headers.get(0) { match req.check_response(header) { Ok(header) => { + self.cache.lock().insert_block_header(req.0, header.clone()); sender.complete(header); return } @@ -493,9 +583,11 @@ impl Handler for OnDemand { match req { Pending::Block(req, sender) => { - if let Some(ref block) = bodies.get(0) { - match req.check_response(block) { + if let Some(ref body) = bodies.get(0) { + match req.check_response(body) { Ok(block) => { + let body = encoded::Body::new(body.to_vec()); + self.cache.lock().insert_block_body(req.hash, body); sender.complete(block); return } @@ -524,6 +616,8 @@ impl Handler for OnDemand { if let Some(ref receipts) = receipts.get(0) { match req.check_response(receipts) { Ok(receipts) => { + let hash = req.0.hash(); + self.cache.lock().insert_block_receipts(hash, receipts.clone()); sender.complete(receipts); return } @@ -604,10 +698,16 @@ impl Handler for OnDemand { #[cfg(test)] mod tests { use super::*; + + use std::sync::Arc; + + use cache::Cache; use net::{Announcement, BasicContext, ReqId, Error as LesError}; use request::{Request as LesRequest, Kind as LesRequestKind}; + use network::{PeerId, NodeId}; - use util::H256; + use time::Duration; + use util::{H256, Mutex}; struct FakeContext; @@ -624,7 +724,8 @@ mod tests { #[test] fn detects_hangup() { - let on_demand = OnDemand::default(); + let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); + let on_demand = OnDemand::new(cache); let result = on_demand.header_by_hash(&FakeContext, request::HeaderByHash(H256::default())); assert!(on_demand.orphaned_requests.read().len() == 1); diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 944b419f7..b739959c5 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -108,7 +108,7 @@ impl EthClient { self.sync.with_context(|ctx| self.on_demand.header_by_number(ctx, req) - .map(|(h, _)| Some(h)) + .map(Some) .map_err(err_premature_cancel) .boxed() ) From 59315b0cb7c290d9bd3bbb1145a4967cd713d55a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 15:16:28 +0100 Subject: [PATCH 04/51] stats utility in its own crate --- util/src/lib.rs | 1 - util/stats/Cargo.toml | 7 ++ util/{src/stats.rs => stats/src/lib.rs} | 90 ++++++++++++++++++++----- 3 files changed, 79 insertions(+), 19 deletions(-) create mode 100644 util/stats/Cargo.toml rename util/{src/stats.rs => stats/src/lib.rs} (53%) diff --git a/util/src/lib.rs b/util/src/lib.rs index 720b80869..b67154f7b 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -142,7 +142,6 @@ pub mod semantic_version; pub mod log; pub mod path; pub mod snappy; -pub mod stats; pub mod cache; mod timer; diff --git a/util/stats/Cargo.toml b/util/stats/Cargo.toml new file mode 100644 index 000000000..99e81c9e7 --- /dev/null +++ b/util/stats/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "stats" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +log = "0.3" diff --git a/util/src/stats.rs b/util/stats/src/lib.rs similarity index 53% rename from util/src/stats.rs rename to util/stats/src/lib.rs index c4c08ddc8..ccfca525b 100644 --- a/util/src/stats.rs +++ b/util/stats/src/lib.rs @@ -14,22 +14,77 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Statistical functions. +//! Statistical functions and helpers. -use bigint::prelude::*; +use std::iter::FromIterator; +use std::ops::{Add, Sub, Div}; + +#[macro_use] +extern crate log; + +/// Sorted corpus of data. +#[derive(Debug, Clone, PartialEq)] +pub struct Corpus(Vec); + +impl From> for Corpus { + fn from(mut data: Vec) -> Self { + data.sort(); + Corpus(data) + } +} + +impl FromIterator for Corpus { + fn from_iter>(iterable: I) -> Self { + iterable.into_iter().collect::>().into() + } +} + +impl Corpus { + /// Get the median element, if it exists. + pub fn median(&self) -> Option<&T> { + self.0.get(self.0.len() / 2) + } + + /// Whether the corpus is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Number of elements in the corpus. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Split the corpus at a given point. + pub fn split_at(self, idx: usize) -> (Self, Self) { + let (left, right) = self.0.split_at(idx); + (Corpus(left), Corpus(right)) + } +} + +impl Corpus + where T: Add + Sub + Div + From +{ + /// Create a histogram of this corpus if it at least spans the buckets. Bounds are left closed. + pub fn histogram(&self, bucket_number: usize) -> Option> { + Histogram::create(&self.0, bucket_number) + } +} /// Discretised histogram. #[derive(Debug, PartialEq)] -pub struct Histogram { +pub struct Histogram { /// Bounds of each bucket. - pub bucket_bounds: Vec, + pub bucket_bounds: Vec, /// Count within each bucket. - pub counts: Vec + pub counts: Vec, } -impl Histogram { - /// Histogram of a sorted corpus if it at least spans the buckets. Bounds are left closed. - pub fn new(corpus: &[U256], bucket_number: usize) -> Option { +impl Histogram + where T: Add + Sub + Div + From +{ + // Histogram of a sorted corpus if it at least spans the buckets. Bounds are left closed. + fn create(corpus: &[T], bucket_number: usize) -> Option> { if corpus.len() < 1 { return None; } let corpus_end = corpus.last().expect("there is at least 1 element; qed").clone(); let corpus_start = corpus.first().expect("there is at least 1 element; qed").clone(); @@ -63,42 +118,41 @@ impl Histogram { #[cfg(test)] mod tests { - use bigint::prelude::U256; use super::Histogram; #[test] fn check_histogram() { - let hist = Histogram::new(slice_into![643,689,1408,2000,2296,2512,4250,4320,4842,4958,5804,6065,6098,6354,7002,7145,7845,8589,8593,8895], 5).unwrap(); - let correct_bounds: Vec = vec_into![643, 2294, 3945, 5596, 7247, 8898]; + let hist = Histogram::create(&[643,689,1408,2000,2296,2512,4250,4320,4842,4958,5804,6065,6098,6354,7002,7145,7845,8589,8593,8895], 5).unwrap(); + let correct_bounds: Vec = vec![643, 2294, 3945, 5596, 7247, 8898]; assert_eq!(Histogram { bucket_bounds: correct_bounds, counts: vec![4,2,4,6,4] }, hist); } #[test] fn smaller_data_range_than_bucket_range() { assert_eq!( - Histogram::new(slice_into![1, 2, 2], 3), - Some(Histogram { bucket_bounds: vec_into![1, 2, 3, 4], counts: vec![1, 2, 0] }) + Histogram::create(&[1, 2, 2], 3), + Some(Histogram { bucket_bounds: vec![1, 2, 3, 4], counts: vec![1, 2, 0] }) ); } #[test] fn data_range_is_not_multiple_of_bucket_range() { assert_eq!( - Histogram::new(slice_into![1, 2, 5], 2), - Some(Histogram { bucket_bounds: vec_into![1, 4, 7], counts: vec![2, 1] }) + Histogram::create(&[1, 2, 5], 2), + Some(Histogram { bucket_bounds: vec![1, 4, 7], counts: vec![2, 1] }) ); } #[test] fn data_range_is_multiple_of_bucket_range() { assert_eq!( - Histogram::new(slice_into![1, 2, 6], 2), - Some(Histogram { bucket_bounds: vec_into![1, 4, 7], counts: vec![2, 1] }) + Histogram::create(&[1, 2, 6], 2), + Some(Histogram { bucket_bounds: vec![1, 4, 7], counts: vec![2, 1] }) ); } #[test] fn none_when_too_few_data() { - assert!(Histogram::new(slice_into![], 1).is_none()); + assert!(Histogram::::create(&[], 1).is_none()); } } From 7a857a24aed9ee55f8ddbabf37899924120d53e9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 16:18:31 +0100 Subject: [PATCH 05/51] use new histogram/corpus --- Cargo.lock | 9 +++++++++ ethcore/Cargo.toml | 1 + ethcore/src/client/traits.rs | 35 ++++++++++------------------------ ethcore/src/lib.rs | 1 + ethcore/src/tests/client.rs | 11 +++++------ rpc/Cargo.toml | 1 + rpc/src/lib.rs | 1 + rpc/src/v1/helpers/dispatch.rs | 8 ++++++-- rpc/src/v1/impls/parity.rs | 2 +- rpc/src/v1/types/histogram.rs | 7 +++---- util/stats/src/lib.rs | 21 +++++++++++--------- 11 files changed, 50 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c51c85ee0..8119910b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,6 +393,7 @@ dependencies = [ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -615,6 +616,7 @@ dependencies = [ "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2129,6 +2131,13 @@ name = "stable-heap" version = "0.1.0" source = "git+https://github.com/carllerche/stable-heap?rev=3c5cd1ca47#3c5cd1ca4706f167a1de85658b5af0d6d3e65165" +[[package]] +name = "stats" +version = "0.1.0" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.3.0" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 442f8b785..c8a1c7fb5 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -43,6 +43,7 @@ rlp = { path = "../util/rlp" } ethcore-stratum = { path = "../stratum" } ethcore-bloom-journal = { path = "../util/bloom" } hardware-wallet = { path = "../hw" } +stats = { path = "../util/stats" } [dependencies.hyper] git = "https://github.com/ethcore/hyper" diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index dce708b3a..6e1ea9d31 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -16,7 +16,6 @@ use std::collections::BTreeMap; use util::{U256, Address, H256, H2048, Bytes, Itertools}; -use util::stats::Histogram; use blockchain::TreeRoute; use verification::queue::QueueInfo as BlockQueueInfo; use block::{OpenBlock, SealedBlock}; @@ -212,38 +211,24 @@ pub trait BlockChainClient : Sync + Send { fn ready_transactions(&self) -> Vec; /// Sorted list of transaction gas prices from at least last sample_size blocks. - fn gas_price_corpus(&self, sample_size: usize) -> Vec { + fn gas_price_corpus(&self, sample_size: usize) -> ::stats::Corpus { let mut h = self.chain_info().best_block_hash; let mut corpus = Vec::new(); while corpus.is_empty() { for _ in 0..sample_size { - let block = self.block(BlockId::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); - let header = block.header_view(); - if header.number() == 0 { - corpus.sort(); - return corpus; + let block = match self.block(BlockId::Hash(h)) { + Some(block) => block, + None => return corpus.into(), + }; + + if block.number() == 0 { + return corpus.into(); } block.transaction_views().iter().foreach(|t| corpus.push(t.gas_price())); - h = header.parent_hash().clone(); + h = block.parent_hash().clone(); } } - corpus.sort(); - corpus - } - - /// Calculate median gas price from recent blocks if they have any transactions. - fn gas_price_median(&self, sample_size: usize) -> Option { - let corpus = self.gas_price_corpus(sample_size); - corpus.get(corpus.len() / 2).cloned() - } - - /// Get the gas price distribution based on recent blocks if they have any transactions. - fn gas_price_histogram(&self, sample_size: usize, bucket_number: usize) -> Option { - let raw_corpus = self.gas_price_corpus(sample_size); - let raw_len = raw_corpus.len(); - // Throw out outliers. - let (corpus, _) = raw_corpus.split_at(raw_len - raw_len / 40); - Histogram::new(corpus, bucket_number) + corpus.into() } /// Get the preferred network ID to sign on diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index be5247340..3a56db51b 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -106,6 +106,7 @@ extern crate lru_cache; extern crate ethcore_stratum; extern crate ethabi; extern crate hardware_wallet; +extern crate stats; #[macro_use] extern crate log; diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 6c2c02c2d..e239eec5b 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -27,7 +27,6 @@ use miner::Miner; use rlp::View; use spec::Spec; use views::BlockView; -use util::stats::Histogram; use ethkey::{KeyPair, Secret}; use transaction::{PendingTransaction, Transaction, Action, Condition}; use miner::MinerService; @@ -208,11 +207,11 @@ fn can_collect_garbage() { fn can_generate_gas_price_median() { let client_result = generate_dummy_client_with_data(3, 1, slice_into![1, 2, 3]); let client = client_result.reference(); - assert_eq!(Some(U256::from(2)), client.gas_price_median(3)); + assert_eq!(Some(&U256::from(2)), client.gas_price_corpus(3).median()); let client_result = generate_dummy_client_with_data(4, 1, slice_into![1, 4, 3, 2]); let client = client_result.reference(); - assert_eq!(Some(U256::from(3)), client.gas_price_median(4)); + assert_eq!(Some(&U256::from(3)), client.gas_price_corpus(3).median()); } #[test] @@ -220,8 +219,8 @@ fn can_generate_gas_price_histogram() { let client_result = generate_dummy_client_with_data(20, 1, slice_into![6354,8593,6065,4842,7845,7002,689,4958,4250,6098,5804,4320,643,8895,2296,8589,7145,2000,2512,1408]); let client = client_result.reference(); - let hist = client.gas_price_histogram(20, 5).unwrap(); - let correct_hist = Histogram { bucket_bounds: vec_into![643, 2294, 3945, 5596, 7247, 8898], counts: vec![4,2,4,6,4] }; + let hist = client.gas_price_corpus(20).histogram(5).unwrap(); + let correct_hist = ::stats::Histogram { bucket_bounds: vec_into![643, 2294, 3945, 5596, 7247, 8898], counts: vec![4,2,4,6,4] }; assert_eq!(hist, correct_hist); } @@ -230,7 +229,7 @@ fn empty_gas_price_histogram() { let client_result = generate_dummy_client_with_data(20, 0, slice_into![]); let client = client_result.reference(); - assert!(client.gas_price_histogram(20, 5).is_none()); + assert!(client.gas_price_corpus(20).histogram(5).is_none()); } #[test] diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 8b8f9ecd7..91058b990 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -39,6 +39,7 @@ rlp = { path = "../util/rlp" } fetch = { path = "../util/fetch" } parity-reactor = { path = "../util/reactor" } clippy = { version = "0.0.103", optional = true} +stats = { path = "../util/stats" } [features] dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev"] diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 030a03702..201f41c22 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -44,6 +44,7 @@ extern crate futures; extern crate order_stat; extern crate parity_updater as updater; extern crate parity_reactor; +extern crate stats; #[macro_use] extern crate log; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index e8fbf9b76..64f5b69af 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -21,11 +21,12 @@ use std::ops::Deref; use std::sync::{Arc, Weak}; use futures::{future, Future, BoxFuture}; +use light::cache::Cache as LightDataCache; use light::client::LightChainClient; use light::on_demand::{request, OnDemand}; use light::TransactionQueue as LightTransactionQueue; use rlp::{self, Stream}; -use util::{Address, H520, H256, U256, Uint, Bytes, RwLock}; +use util::{Address, H520, H256, U256, Uint, Bytes, Mutex, RwLock}; use util::sha3::Hashable; use ethkey::Signature; @@ -162,6 +163,7 @@ pub struct LightDispatcher { sync: Arc, client: Arc, on_demand: Arc, + cache: Arc>, transaction_queue: Arc>, } @@ -173,12 +175,14 @@ impl LightDispatcher { sync: Arc, client: Arc, on_demand: Arc, + cache: Arc>, transaction_queue: Arc>, ) -> Self { LightDispatcher { sync: sync, client: client, on_demand: on_demand, + cache: cache, transaction_queue: transaction_queue, } } @@ -453,7 +457,7 @@ fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: S pub fn default_gas_price(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService { - client.gas_price_median(100).unwrap_or_else(|| miner.sensible_gas_price()) + client.gas_price_corpus(100).median().cloned().unwrap_or_else(|| miner.sensible_gas_price()) } /// Convert RPC confirmation payload to signer confirmation payload. diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 431c34e84..8dbb4578f 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -236,7 +236,7 @@ impl Parity for ParityClient where } fn gas_price_histogram(&self) -> Result { - take_weak!(self.client).gas_price_histogram(100, 10).ok_or_else(errors::not_enough_data).map(Into::into) + take_weak!(self.client).gas_price_corpus(100).histogram(10).ok_or_else(errors::not_enough_data).map(Into::into) } fn unsigned_transactions_count(&self) -> Result { diff --git a/rpc/src/v1/types/histogram.rs b/rpc/src/v1/types/histogram.rs index 6cec96469..55d8ae835 100644 --- a/rpc/src/v1/types/histogram.rs +++ b/rpc/src/v1/types/histogram.rs @@ -17,7 +17,6 @@ //! Gas prices histogram. use v1::types::U256; -use util::stats; /// Values of RPC settings. #[derive(Serialize, Deserialize)] @@ -27,11 +26,11 @@ pub struct Histogram { #[serde(rename="bucketBounds")] pub bucket_bounds: Vec, /// Transacion counts for each bucket. - pub counts: Vec, + pub counts: Vec, } -impl From for Histogram { - fn from(h: stats::Histogram) -> Self { +impl From<::stats::Histogram<::util::U256>> for Histogram { + fn from(h: ::stats::Histogram<::util::U256>) -> Self { Histogram { bucket_bounds: h.bucket_bounds.into_iter().map(Into::into).collect(), counts: h.counts diff --git a/util/stats/src/lib.rs b/util/stats/src/lib.rs index ccfca525b..01c988232 100644 --- a/util/stats/src/lib.rs +++ b/util/stats/src/lib.rs @@ -17,14 +17,14 @@ //! Statistical functions and helpers. use std::iter::FromIterator; -use std::ops::{Add, Sub, Div}; +use std::ops::{Add, Sub, Deref, Div}; #[macro_use] extern crate log; /// Sorted corpus of data. #[derive(Debug, Clone, PartialEq)] -pub struct Corpus(Vec); +pub struct Corpus(Vec); impl From> for Corpus { fn from(mut data: Vec) -> Self { @@ -39,6 +39,12 @@ impl FromIterator for Corpus { } } +impl Deref for Corpus { + type Target = [T]; + + fn deref(&self) -> &[T] { &self.0[..] } +} + impl Corpus { /// Get the median element, if it exists. pub fn median(&self) -> Option<&T> { @@ -54,20 +60,17 @@ impl Corpus { pub fn len(&self) -> usize { self.0.len() } - - /// Split the corpus at a given point. - pub fn split_at(self, idx: usize) -> (Self, Self) { - let (left, right) = self.0.split_at(idx); - (Corpus(left), Corpus(right)) - } } impl Corpus where T: Add + Sub + Div + From { /// Create a histogram of this corpus if it at least spans the buckets. Bounds are left closed. + /// Excludes outliers. pub fn histogram(&self, bucket_number: usize) -> Option> { - Histogram::create(&self.0, bucket_number) + // TODO: get outliers properly. + let upto = self.len() - self.len() / 40; + Histogram::create(&self.0[..upto], bucket_number) } } From 3b023c82b75d858bd4e27f67f8598b24d7c704cf Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 17:08:46 +0100 Subject: [PATCH 06/51] fetch gas price corpus from network when needed --- Cargo.lock | 33 ++++----- ethcore/light/Cargo.toml | 1 + ethcore/light/src/cache.rs | 11 +-- ethcore/light/src/client/header_chain.rs | 27 ++++++++ ethcore/light/src/client/mod.rs | 14 +++- ethcore/light/src/lib.rs | 1 + rpc/src/v1/helpers/dispatch.rs | 86 +++++++++++++++++++----- 7 files changed, 136 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8119910b6..6269a85f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -427,7 +427,7 @@ dependencies = [ "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", "fetch 0.1.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -536,12 +536,13 @@ dependencies = [ "ethcore-ipc-codegen 1.6.0", "ethcore-network 1.6.0", "ethcore-util 1.6.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "smallvec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -601,7 +602,7 @@ dependencies = [ "ethstore 0.1.0", "ethsync 1.6.0", "fetch 0.1.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-ipc-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -650,7 +651,7 @@ dependencies = [ "ethcore-ipc-codegen 1.6.0", "ethcore-ipc-nano 1.6.0", "ethcore-util 1.6.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-tcp-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -813,7 +814,7 @@ dependencies = [ name = "fetch" version = "0.1.0" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -832,7 +833,7 @@ dependencies = [ [[package]] name = "futures" -version = "0.1.6" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -844,7 +845,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1045,7 +1046,7 @@ name = "jsonrpc-core" version = "6.0.0" source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1565,7 +1566,7 @@ dependencies = [ "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-util 1.6.0", "fetch 0.1.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (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)", @@ -1578,7 +1579,7 @@ dependencies = [ name = "parity-reactor" version = "0.1.0" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1589,7 +1590,7 @@ dependencies = [ "ethcore-rpc 1.6.0", "ethcore-signer 1.6.0", "ethcore-util 1.6.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1898,7 +1899,7 @@ dependencies = [ "ethcore-bigint 0.1.2", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "parity-rpc-client 1.4.0", "rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2249,7 +2250,7 @@ name = "tokio-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2261,7 +2262,7 @@ name = "tokio-proto" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2277,7 +2278,7 @@ name = "tokio-service" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2503,7 +2504,7 @@ dependencies = [ "checksum ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d8f6cc4c1acd005f48e1d17b06a461adac8fb6eeeb331fbf19a0e656fba91cd" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" -"checksum futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bad0a2ac64b227fdc10c254051ae5af542cf19c9328704fd4092f7914196897" +"checksum futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c1913eb7083840b1bbcbf9631b7fda55eaf35fe7ead13cca034e8946f9e2bc41" "checksum futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bb982bb25cd8fa5da6a8eb3a460354c984ff1113da82bcb4f0b0862b5795db82" "checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index d2444dd59..9e10449fb 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -23,6 +23,7 @@ smallvec = "0.3.1" futures = "0.1" rand = "0.3" itertools = "0.5" +stats = { path = "../../util/stats" } [features] default = [] diff --git a/ethcore/light/src/cache.rs b/ethcore/light/src/cache.rs index defa247ec..185007a1b 100644 --- a/ethcore/light/src/cache.rs +++ b/ethcore/light/src/cache.rs @@ -24,6 +24,7 @@ use ethcore::encoded; use ethcore::header::BlockNumber; use ethcore::receipt::Receipt; +use stats::Corpus; use time::{SteadyTime, Duration}; use util::{U256, H256}; use util::cache::MemoryLruCache; @@ -66,7 +67,7 @@ pub struct Cache { bodies: MemoryLruCache, receipts: MemoryLruCache>, chain_score: MemoryLruCache, - corpus: Option<(Vec, SteadyTime)>, + corpus: Option<(Corpus, SteadyTime)>, corpus_expiration: Duration, } @@ -135,7 +136,7 @@ impl Cache { } /// Get gas price corpus, if recent enough. - pub fn gas_price_corpus(&self) -> Option> { + pub fn gas_price_corpus(&self) -> Option> { let now = SteadyTime::now(); self.corpus.as_ref().and_then(|&(ref corpus, ref tm)| { @@ -148,7 +149,7 @@ impl Cache { } /// Set the cached gas price corpus. - pub fn set_gas_price_corpus(&mut self, corpus: Vec) { + pub fn set_gas_price_corpus(&mut self, corpus: Corpus) { self.corpus = Some((corpus, SteadyTime::now())) } } @@ -162,8 +163,8 @@ mod tests { fn corpus_inaccessible() { let mut cache = Cache::new(Default::default(), Duration::hours(5)); - cache.set_gas_price_corpus(vec![]); - assert_eq!(cache.gas_price_corpus(), Some(vec![])); + cache.set_gas_price_corpus(vec![].into()); + assert_eq!(cache.gas_price_corpus(), Some(vec![].into())); { let corpus_time = &mut cache.corpus.as_mut().unwrap().1; diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index 403d3555d..575938cd5 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -241,6 +241,14 @@ impl HeaderChain { self.block_header(BlockId::Latest).expect("Header for best block always stored; qed") } + /// Get an iterator over a block and its ancestry. + pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter { + AncestryIter { + next: self.block_header(start), + chain: self, + } + } + /// Get the nth CHT root, if it's been computed. /// /// CHT root 0 is from block `1..2048`. @@ -295,6 +303,25 @@ impl HeapSizeOf for HeaderChain { } } +/// Iterator over a block's ancestry. +pub struct AncestryIter<'a> { + next: Option, + chain: &'a HeaderChain, +} + +impl<'a> Iterator for AncestryIter<'a> { + type Item = encoded::Header; + + fn next(&mut self) -> Option { + let next = self.next.take(); + if let Some(p_hash) = next.as_ref().map(|hdr| hdr.parent_hash()) { + self.next = self.chain.block_header(BlockId::Hash(p_hash)); + } + + next + } +} + #[cfg(test)] mod tests { use super::HeaderChain; diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index bebe89e0e..5701fc606 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -33,7 +33,7 @@ use io::IoChannel; use util::{Bytes, H256, Mutex, RwLock}; -use self::header_chain::HeaderChain; +use self::header_chain::{AncestryIter, HeaderChain}; pub use self::service::Service; @@ -62,6 +62,9 @@ pub trait LightChainClient: Send + Sync { /// Get the best block header. fn best_block_header(&self) -> encoded::Header; + /// Get an iterator over a block and its ancestry. + fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box + 'a>; + /// Get the signing network ID. fn signing_network_id(&self) -> Option; @@ -167,6 +170,11 @@ impl Client { self.chain.best_header() } + /// Get an iterator over a block and its ancestry. + pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter { + self.chain.ancestry_iter(start) + } + /// Get the signing network id. pub fn signing_network_id(&self) -> Option { self.engine.signing_network_id(&self.latest_env_info()) @@ -269,6 +277,10 @@ impl LightChainClient for Client { Client::best_block_header(self) } + fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box + 'a> { + Box::new(Client::ancestry_iter(self, start)) + } + fn signing_network_id(&self) -> Option { Client::signing_network_id(self) } diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 53d32f44e..b6e06a02b 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -72,6 +72,7 @@ extern crate time; extern crate futures; extern crate rand; extern crate itertools; +extern crate stats; #[cfg(feature = "ipc")] extern crate ethcore_ipc as ipc; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 64f5b69af..13ae9a0ca 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -20,17 +20,18 @@ use std::fmt::Debug; use std::ops::Deref; use std::sync::{Arc, Weak}; -use futures::{future, Future, BoxFuture}; +use futures::{future, stream, Future, Stream, BoxFuture}; use light::cache::Cache as LightDataCache; use light::client::LightChainClient; use light::on_demand::{request, OnDemand}; use light::TransactionQueue as LightTransactionQueue; -use rlp::{self, Stream}; +use rlp::{self, Stream as StreamRlp}; use util::{Address, H520, H256, U256, Uint, Bytes, Mutex, RwLock}; use util::sha3::Hashable; use ethkey::Signature; use ethsync::LightSync; +use ethcore::ids::BlockId; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; use ethcore::transaction::{Action, SignedTransaction, PendingTransaction, Transaction}; @@ -192,20 +193,75 @@ impl Dispatcher for LightDispatcher { fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address) -> BoxFuture { - let request = request; - let gas_limit = self.client.best_block_header().gas_limit(); + const GAS_PRICE_SAMPLE_SIZE: usize = 100; + const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); - future::ok(FilledTransactionRequest { - from: request.from.unwrap_or(default_sender), - used_default_from: request.from.is_none(), - to: request.to, - nonce: request.nonce, - gas_price: request.gas_price.unwrap_or_else(|| 21_000_000.into()), // TODO: fetch corpus from network. - gas: request.gas.unwrap_or_else(|| gas_limit / 3.into()), - value: request.value.unwrap_or_else(|| 0.into()), - data: request.data.unwrap_or_else(Vec::new), - condition: request.condition, - }).boxed() + let gas_limit = self.client.best_block_header().gas_limit(); + let request_gas_price = request.gas_price.clone(); + + let with_gas_price = move |gas_price| { + let request = request; + FilledTransactionRequest { + from: request.from.unwrap_or(default_sender), + used_default_from: request.from.is_none(), + to: request.to, + nonce: request.nonce, + gas_price: gas_price, + gas: request.gas.unwrap_or_else(|| gas_limit / 3.into()), + value: request.value.unwrap_or_else(|| 0.into()), + data: request.data.unwrap_or_else(Vec::new), + condition: request.condition, + } + }; + + // 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 { + 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() + } + } + } } fn sign(&self, accounts: Arc, filled: FilledTransactionRequest, password: SignWith) From 9316eb4ad34417de1fc3567a8e8a1000ef1f4fc3 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 21:38:43 +0100 Subject: [PATCH 07/51] (most of) parity RPC for light client --- ethcore/light/src/net/mod.rs | 10 + ethcore/light/src/net/request_set.rs | 8 + ethcore/light/src/transaction_queue.rs | 43 ++++ rpc/src/v1/helpers/dispatch.rs | 152 ++++++----- rpc/src/v1/helpers/errors.rs | 8 + rpc/src/v1/impls/light/mod.rs | 5 + rpc/src/v1/impls/light/parity.rs | 335 +++++++++++++++++++++++++ rpc/src/v1/impls/parity.rs | 21 +- rpc/src/v1/traits/parity.rs | 8 +- sync/src/api.rs | 80 +++++- sync/src/lib.rs | 6 +- 11 files changed, 587 insertions(+), 89 deletions(-) create mode 100644 rpc/src/v1/impls/light/parity.rs 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}; From 9e761ba2ea870693d9f61c7e9b9a4c75d1bb19b1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 21:43:15 +0100 Subject: [PATCH 08/51] ParitySet stubs --- rpc/src/v1/impls/light/mod.rs | 2 + rpc/src/v1/impls/light/parity_set.rs | 143 +++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 rpc/src/v1/impls/light/parity_set.rs diff --git a/rpc/src/v1/impls/light/mod.rs b/rpc/src/v1/impls/light/mod.rs index 71a3a497d..3a09076a9 100644 --- a/rpc/src/v1/impls/light/mod.rs +++ b/rpc/src/v1/impls/light/mod.rs @@ -21,6 +21,8 @@ pub mod eth; pub mod parity; +pub mod parity_set; pub use self::eth::EthClient; pub use self::parity::ParityClient; +pub use self::parity_set::ParitySetClient; diff --git a/rpc/src/v1/impls/light/parity_set.rs b/rpc/src/v1/impls/light/parity_set.rs new file mode 100644 index 000000000..4741f2bc0 --- /dev/null +++ b/rpc/src/v1/impls/light/parity_set.rs @@ -0,0 +1,143 @@ +// 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 interface for operations altering the settings. +//! Implementation for light client. + +use std::io; +use std::sync::{Arc, Weak}; + +use ethcore::miner::MinerService; +use ethcore::client::MiningBlockChainClient; +use ethcore::mode::Mode; +use ethsync::ManageNetwork; +use fetch::{self, Fetch}; +use futures::{BoxFuture, Future}; +use util::sha3; +use updater::{Service as UpdateService}; + +use jsonrpc_core::Error; +use v1::helpers::errors; +use v1::traits::ParitySet; +use v1::types::{Bytes, H160, H256, U256, ReleaseInfo}; + +/// Parity-specific rpc interface for operations altering the settings. +pub struct ParitySetClient { + net: Arc, + fetch: F, +} + +impl ParitySetClient { + /// Creates new `ParitySetClient` with given `Fetch`. + pub fn new(net: Arc, fetch: F) -> Self { + ParitySetClient { + net: net, + fetch: fetch, + } + } +} + +impl ParitySet for ParitySetClient { + + fn set_min_gas_price(&self, _gas_price: U256) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn set_gas_floor_target(&self, _target: U256) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn set_gas_ceil_target(&self, _target: U256) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn set_extra_data(&self, _extra_data: Bytes) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn set_author(&self, _author: H160) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn set_engine_signer(&self, _address: H160, _password: String) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn set_transactions_limit(&self, _limit: usize) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn set_tx_gas_limit(&self, _limit: U256) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn add_reserved_peer(&self, peer: String) -> Result { + match self.net.add_reserved_peer(peer) { + Ok(()) => Ok(true), + Err(e) => Err(errors::invalid_params("Peer address", e)), + } + } + + fn remove_reserved_peer(&self, peer: String) -> Result { + match self.net.remove_reserved_peer(peer) { + Ok(()) => Ok(true), + Err(e) => Err(errors::invalid_params("Peer address", e)), + } + } + + fn drop_non_reserved_peers(&self) -> Result { + self.net.deny_unreserved_peers(); + Ok(true) + } + + fn accept_non_reserved_peers(&self) -> Result { + self.net.accept_unreserved_peers(); + Ok(true) + } + + fn start_network(&self) -> Result { + self.net.start_network(); + Ok(true) + } + + fn stop_network(&self) -> Result { + self.net.stop_network(); + Ok(true) + } + + fn set_mode(&self, mode: String) -> Result { + Err(errors::light_unimplemented(None)) + } + + fn hash_content(&self, url: String) -> BoxFuture { + self.fetch.process(self.fetch.fetch(&url).then(move |result| { + result + .map_err(errors::from_fetch_error) + .and_then(|response| { + sha3(&mut io::BufReader::new(response)).map_err(errors::from_fetch_error) + }) + .map(Into::into) + })) + } + + fn upgrade_ready(&self) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn execute_upgrade(&self) -> Result { + Err(errors::light_unimplemented(None)) + } +} From 4de208786da0d7e4ca53728938fde36837f1e369 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 17 Feb 2017 22:21:43 +0100 Subject: [PATCH 09/51] trace API stubs --- rpc/src/v1/impls/light/eth.rs | 18 +++------ rpc/src/v1/impls/light/mod.rs | 1 + rpc/src/v1/impls/light/parity_set.rs | 11 ++---- rpc/src/v1/impls/light/trace.rs | 57 ++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 rpc/src/v1/impls/light/trace.rs diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index b739959c5..029766a31 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -62,11 +62,6 @@ pub struct EthClient { accounts: Arc, } -// helper for internal error: no network context. -fn err_no_context() -> Error { - errors::internal("network service detached", "") -} - // helper for internal error: on demand sender cancelled. fn err_premature_cancel(_cancel: oneshot::Canceled) -> Error { errors::internal("on-demand sender prematurely cancelled", "") @@ -128,10 +123,9 @@ impl EthClient { _ => None, // latest, earliest, and pending will have all already returned. }; - // todo: cache returned values (header, TD) match maybe_future { Some(recv) => recv, - None => future::err(err_no_context()).boxed() + None => future::err(errors::network_disabled()).boxed() } } @@ -150,7 +144,7 @@ impl EthClient { address: address, }).map(Some)) .map(|x| x.map_err(err_premature_cancel).boxed()) - .unwrap_or_else(|| future::err(err_no_context()).boxed()) + .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) }).boxed() } } @@ -235,7 +229,7 @@ impl Eth for EthClient { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into()))) .map(|x| x.map_err(err_premature_cancel).boxed()) - .unwrap_or_else(|| future::err(err_no_context()).boxed()) + .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() } @@ -255,7 +249,7 @@ impl Eth for EthClient { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into()))) .map(|x| x.map_err(err_premature_cancel).boxed()) - .unwrap_or_else(|| future::err(err_no_context()).boxed()) + .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() } @@ -275,7 +269,7 @@ impl Eth for EthClient { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into()))) .map(|x| x.map_err(err_premature_cancel).boxed()) - .unwrap_or_else(|| future::err(err_no_context()).boxed()) + .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() } @@ -295,7 +289,7 @@ impl Eth for EthClient { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into()))) .map(|x| x.map_err(err_premature_cancel).boxed()) - .unwrap_or_else(|| future::err(err_no_context()).boxed()) + .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() } diff --git a/rpc/src/v1/impls/light/mod.rs b/rpc/src/v1/impls/light/mod.rs index 3a09076a9..8c2e6d240 100644 --- a/rpc/src/v1/impls/light/mod.rs +++ b/rpc/src/v1/impls/light/mod.rs @@ -22,6 +22,7 @@ pub mod eth; pub mod parity; pub mod parity_set; +pub mod trace; pub use self::eth::EthClient; pub use self::parity::ParityClient; diff --git a/rpc/src/v1/impls/light/parity_set.rs b/rpc/src/v1/impls/light/parity_set.rs index 4741f2bc0..720af0dd9 100644 --- a/rpc/src/v1/impls/light/parity_set.rs +++ b/rpc/src/v1/impls/light/parity_set.rs @@ -18,16 +18,12 @@ //! Implementation for light client. use std::io; -use std::sync::{Arc, Weak}; +use std::sync::Arc; -use ethcore::miner::MinerService; -use ethcore::client::MiningBlockChainClient; -use ethcore::mode::Mode; use ethsync::ManageNetwork; -use fetch::{self, Fetch}; +use fetch::Fetch; use futures::{BoxFuture, Future}; use util::sha3; -use updater::{Service as UpdateService}; use jsonrpc_core::Error; use v1::helpers::errors; @@ -51,7 +47,6 @@ impl ParitySetClient { } impl ParitySet for ParitySetClient { - fn set_min_gas_price(&self, _gas_price: U256) -> Result { Err(errors::light_unimplemented(None)) } @@ -118,7 +113,7 @@ impl ParitySet for ParitySetClient { Ok(true) } - fn set_mode(&self, mode: String) -> Result { + fn set_mode(&self, _mode: String) -> Result { Err(errors::light_unimplemented(None)) } diff --git a/rpc/src/v1/impls/light/trace.rs b/rpc/src/v1/impls/light/trace.rs new file mode 100644 index 000000000..5f785ed1b --- /dev/null +++ b/rpc/src/v1/impls/light/trace.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 . + +//! Traces api implementation. + +use jsonrpc_core::Error; +use jsonrpc_macros::Trailing; +use v1::traits::Traces; +use v1::helpers::errors; +use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; + +/// Traces api implementation. +// TODO: all calling APIs should be possible w. proved remote TX execution. +pub struct TracesClient; + +impl Traces for TracesClient { + fn filter(&self, _filter: TraceFilter) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn block_traces(&self, _block_number: BlockNumber) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn transaction_traces(&self, _transaction_hash: H256) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn trace(&self, _transaction_hash: H256, _address: Vec) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn call(&self, _request: CallRequest, _flags: Vec, _block: Trailing) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn raw_transaction(&self, _raw_transaction: Bytes, _flags: Vec, _block: Trailing) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } + + fn replay_transaction(&self, _transaction_hash: H256, _flags: Vec) -> Result, Error> { + Err(errors::light_unimplemented(None)) + } +} From 61e3d036d98362eea78e9c4e93b05cfd8cb0131c Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 21 Feb 2017 12:59:36 +0100 Subject: [PATCH 10/51] Extract new available i18n strings (#4623) --- js/src/i18n/_default/accounts.js | 10 ++++ js/src/i18n/_default/createAccount.js | 7 --- js/src/i18n/_default/errors.js | 24 +++++++++ js/src/i18n/_default/index.js | 3 ++ js/src/i18n/_default/tabBar.js | 21 ++++++++ js/src/i18n/_default/ui.js | 7 +++ js/src/i18n/_default/vaults.js | 75 +++++++++++++++++++++++++++ 7 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 js/src/i18n/_default/errors.js create mode 100644 js/src/i18n/_default/tabBar.js create mode 100644 js/src/i18n/_default/vaults.js diff --git a/js/src/i18n/_default/accounts.js b/js/src/i18n/_default/accounts.js index 5cafa1054..d22c5a504 100644 --- a/js/src/i18n/_default/accounts.js +++ b/js/src/i18n/_default/accounts.js @@ -15,7 +15,17 @@ // along with Parity. If not, see . export default { + button: { + newAccount: `new account`, + newWallet: `new wallet`, + vaults: `vaults` + }, summary: { minedBlock: `Mined at block #{blockNumber}` + }, + title: `Accounts Overview`, + tooltip: { + actions: `actions relating to the current view are available on the toolbar for quick access, be it for performing actions or creating a new item`, + overview: `your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account` } }; diff --git a/js/src/i18n/_default/createAccount.js b/js/src/i18n/_default/createAccount.js index 562f1ff0c..485a877f8 100644 --- a/js/src/i18n/_default/createAccount.js +++ b/js/src/i18n/_default/createAccount.js @@ -61,13 +61,6 @@ export default { label: `Import raw private key` } }, - error: { - invalidKey: `the raw key needs to be hex, 64 characters in length and contain the prefix "0x"`, - noFile: `select a valid wallet file to import`, - noKey: `you need to provide the raw private key`, - noMatchPassword: `the supplied passwords does not match`, - noName: `you need to specify a valid name for the account` - }, newAccount: { hint: { hint: `(optional) a hint to help with remembering the password`, diff --git a/js/src/i18n/_default/errors.js b/js/src/i18n/_default/errors.js new file mode 100644 index 000000000..76fed24cd --- /dev/null +++ b/js/src/i18n/_default/errors.js @@ -0,0 +1,24 @@ +// 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 . + +export default { + duplicateName: `the name already exists`, + invalidKey: `the raw key needs to be hex, 64 characters in length and contain the prefix "0x"`, + noFile: `select a valid wallet file to import`, + noKey: `you need to provide the raw private key`, + noMatchPassword: `the supplied passwords does not match`, + noName: `you need to specify a valid name` +}; diff --git a/js/src/i18n/_default/index.js b/js/src/i18n/_default/index.js index 59fea4b05..7e8bac8ef 100644 --- a/js/src/i18n/_default/index.js +++ b/js/src/i18n/_default/index.js @@ -29,6 +29,7 @@ export dapps from './dapps'; export deleteAccount from './deleteAccount'; export deployContract from './deployContract'; export editMeta from './editMeta'; +export errors from './errors'; export executeContract from './executeContract'; export extension from './extension'; export firstRun from './firstRun'; @@ -38,9 +39,11 @@ export parityBar from './parityBar'; export passwordChange from './passwordChange'; export settings from './settings'; export shapeshift from './shapeshift'; +export tabBar from './tabBar'; export transfer from './transfer'; export txEditor from './txEditor'; export ui from './ui'; export upgradeParity from './upgradeParity'; +export vaults from './vaults'; export walletSettings from './walletSettings'; export web from './web'; diff --git a/js/src/i18n/_default/tabBar.js b/js/src/i18n/_default/tabBar.js new file mode 100644 index 000000000..1692a1997 --- /dev/null +++ b/js/src/i18n/_default/tabBar.js @@ -0,0 +1,21 @@ +// 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 . + +export default { + tooltip: { + overview: `navigate between the different parts and views of the application, switching between an account view, token view and distributed application view` + } +}; diff --git a/js/src/i18n/_default/ui.js b/js/src/i18n/_default/ui.js index 9bed497f5..242f14b9b 100644 --- a/js/src/i18n/_default/ui.js +++ b/js/src/i18n/_default/ui.js @@ -35,6 +35,13 @@ export default { passwordStrength: { label: `password strength` }, + tooltips: { + button: { + done: `Done`, + next: `Next`, + skip: `Skip` + } + }, txHash: { confirmations: `{count} {value, plural, one {confirmation} other {confirmations}}`, oog: `The transaction might have gone out of gas. Try again with more gas.`, diff --git a/js/src/i18n/_default/vaults.js b/js/src/i18n/_default/vaults.js new file mode 100644 index 000000000..3024beed3 --- /dev/null +++ b/js/src/i18n/_default/vaults.js @@ -0,0 +1,75 @@ +// 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 . + +export default { + accounts: { + button: { + cancel: `Cancel`, + execute: `Set` + }, + empty: `There are no accounts in this vault`, + title: `Manage Vault Accounts` + }, + button: { + accounts: `accounts`, + add: `create vault`, + close: `close vault`, + open: `open vault` + }, + confirmClose: { + info: `You are about to close a vault. Any accounts associated with the vault won't be visible after this operation concludes. To view the associated accounts, open the vault again.`, + title: `Close Vault` + }, + confirmOpen: { + info: `You are about to open a vault. After confirming your password, all accounts associated with this vault will be visible. Closing the vault will remove the accounts from view until the vault is opened again.`, + password: { + hint: `the password specified when creating the vault`, + label: `vault password` + }, + title: `Open Vault` + }, + create: { + button: { + close: `close`, + vault: `create vault` + }, + description: { + hint: `an extended description for the vault` + }, + descriptions: { + label: `(optional) description` + }, + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a name for the vault`, + label: `vault name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + }, + title: `Create a new vault` + }, + empty: `There are currently no vaults to display.`, + title: `Vault Management` +}; From 80155ef93ab8c5dee36a701858e362dde05b0dd4 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Tue, 21 Feb 2017 12:12:14 +0000 Subject: [PATCH 11/51] [ci skip] js-precompiled 20170221-120741 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 349284e6c..2f3e770af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1702,7 +1702,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9fb4ab9d8ffaca9cd9f07270bf69681c2081050f" +source = "git+https://github.com/ethcore/js-precompiled.git#a7ca030c48918cef04e236cc37a9fcbe234a6443" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 5bceb8032..1c424142b 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.94", + "version": "0.3.95", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 502af0e29b86d65f9532c1385b847f3a982359cc Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Tue, 21 Feb 2017 16:42:18 +0400 Subject: [PATCH 12/51] [ci skip] update gitlab-ci fix path to `tools` add `tools` in `linux-stable-debian` --- .gitlab-ci.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b0e4ad009..913256c7c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,9 +33,9 @@ linux-stable: - md5sum target/release/parity > parity.md5 - sh scripts/deb-build.sh amd64 - cp target/release/parity deb/usr/bin/parity - - cp target/release/parity/evm deb/usr/bin/evm - - cp target/release/parity/ethstore deb/usr/bin/ethstore - - cp target/release/parity/ethkey deb/usr/bin/ethkey + - cp target/release/evm deb/usr/bin/evm + - cp target/release/ethstore deb/usr/bin/ethstore + - cp target/release/ethkey deb/usr/bin/ethkey - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_amd64.deb" - md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5" @@ -69,11 +69,20 @@ linux-stable-debian: - triggers script: - cargo build -j $(nproc) --release --features final $CARGOFLAGS + - cargo build -j $(nproc) --release -p evmbin + - cargo build -j $(nproc) --release -p ethstore + - cargo build -j $(nproc) --release -p ethkey - strip target/release/parity + - strip target/release/evm + - strip target/release/ethstore + - strip target/release/ethkey - export SHA3=$(target/release/parity tools hash target/release/parity) - md5sum target/release/parity > parity.md5 - sh scripts/deb-build.sh amd64 - cp target/release/parity deb/usr/bin/parity + - cp target/release/evm deb/usr/bin/evm + - cp target/release/ethstore deb/usr/bin/ethstore + - cp target/release/ethkey deb/usr/bin/ethkey - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") - dpkg-deb -b deb "parity_"$VER"_amd64.deb" - md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5" From df414c8186d507c9544d6e11827691f876b3a9a7 Mon Sep 17 00:00:00 2001 From: Rain Gloom Date: Tue, 21 Feb 2017 18:08:47 +0100 Subject: [PATCH 13/51] fixed minor grammar mistake in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dfd0f533c..260566c5e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Be sure to check out [our wiki][wiki-url] for more information. Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs. -Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) this simply go to http://127.0.0.1:8080/. It +Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) simply go to http://127.0.0.1:8080/. It includes various functionality allowing you to: - create and manage your Ethereum accounts; - manage your Ether and any Ethereum tokens; From 0a85fc7a3e520534a8d1d0f839ca9c7d69efad7d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 22 Feb 2017 10:42:42 +0100 Subject: [PATCH 14/51] Display ETH balance in overlay account selector (#4588) * Add account balance display from ParityBar * Ellipsis with title * Balance display in Dapp permissions * Add balance to vault account selector * Add key prop to accounts in Vault * Fix failing test (missing redux prop) --- .../modals/DappPermissions/dappPermissions.js | 35 ++++++++++++++----- .../DappPermissions/dappPermissions.spec.js | 35 ++++++++++++++++--- js/src/modals/VaultAccounts/vaultAccounts.js | 10 +++++- .../VaultAccounts/vaultAccounts.spec.js | 3 ++ js/src/ui/AccountCard/accountCard.css | 3 ++ js/src/ui/VaultCard/vaultCard.js | 5 ++- js/src/views/Dapps/dapps.js | 2 +- js/src/views/ParityBar/parityBar.js | 6 ++++ js/src/views/ParityBar/parityBar.spec.js | 9 ++++- 9 files changed, 91 insertions(+), 17 deletions(-) diff --git a/js/src/modals/DappPermissions/dappPermissions.js b/js/src/modals/DappPermissions/dappPermissions.js index 4cd7cc837..410dcf4b7 100644 --- a/js/src/modals/DappPermissions/dappPermissions.js +++ b/js/src/modals/DappPermissions/dappPermissions.js @@ -17,6 +17,7 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; import { AccountCard, Portal, SectionList } from '~/ui'; import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons'; @@ -24,15 +25,16 @@ import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons'; import styles from './dappPermissions.css'; @observer -export default class DappPermissions extends Component { +class DappPermissions extends Component { static propTypes = { - store: PropTypes.object.isRequired + balances: PropTypes.object, + permissionStore: PropTypes.object.isRequired }; render () { - const { store } = this.props; + const { permissionStore } = this.props; - if (!store.modalOpen) { + if (!permissionStore.modalOpen) { return null; } @@ -50,7 +52,7 @@ export default class DappPermissions extends Component { /> } - onClose={ store.closeModal } + onClose={ permissionStore.closeModal } open title={
@@ -71,14 +73,15 @@ export default class DappPermissions extends Component { } renderAccount = (account) => { - const { store } = this.props; + const { balances, permissionStore } = this.props; + const balance = balances[account.address]; const onMakeDefault = () => { - store.setDefaultAccount(account.address); + permissionStore.setDefaultAccount(account.address); }; const onSelect = () => { - store.selectAccount(account.address); + permissionStore.selectAccount(account.address); }; let className; @@ -95,6 +98,7 @@ export default class DappPermissions extends Component {
@@ -114,3 +118,16 @@ export default class DappPermissions extends Component { ); } } + +function mapStateToProps (state) { + const { balances } = state.balances; + + return { + balances + }; +} + +export default connect( + mapStateToProps, + null +)(DappPermissions); diff --git a/js/src/modals/DappPermissions/dappPermissions.spec.js b/js/src/modals/DappPermissions/dappPermissions.spec.js index ea562a962..717ec3b20 100644 --- a/js/src/modals/DappPermissions/dappPermissions.spec.js +++ b/js/src/modals/DappPermissions/dappPermissions.spec.js @@ -16,13 +16,40 @@ import { shallow } from 'enzyme'; import React from 'react'; +import sinon from 'sinon'; import DappPermissions from './'; -function renderShallow (store = {}) { - return shallow( - - ); +let component; +let store; + +function createRedux () { + store = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return { + balances: { + balances: {} + } + }; + } + }; + + return store; +} + +function renderShallow (permissionStore = {}) { + component = shallow( + , + { + context: { + store: createRedux() + } + } + ).find('DappPermissions').shallow(); + + return component; } describe('modals/DappPermissions', () => { diff --git a/js/src/modals/VaultAccounts/vaultAccounts.js b/js/src/modals/VaultAccounts/vaultAccounts.js index 96f170f79..833b5805e 100644 --- a/js/src/modals/VaultAccounts/vaultAccounts.js +++ b/js/src/modals/VaultAccounts/vaultAccounts.js @@ -35,6 +35,7 @@ class VaultAccounts extends Component { static propTypes = { accounts: PropTypes.object.isRequired, + balances: PropTypes.object.isRequired, newError: PropTypes.func.isRequired, personalAccountsInfo: PropTypes.func.isRequired, vaultStore: PropTypes.object.isRequired @@ -105,7 +106,9 @@ class VaultAccounts extends Component { // (although that has defaults) and this one. A genrerix multi-select component // would be applicable going forward. (Originals passed in, new selections back) renderAccount = (account) => { + const { balances } = this.props; const { vaultName, selectedAccounts } = this.props.vaultStore; + const balance = balances[account.address]; const isInVault = account.meta.vault === vaultName; const isSelected = isInVault ? !selectedAccounts[account.address] @@ -119,6 +122,7 @@ class VaultAccounts extends Component {
{ return { + balances: { + balances: {} + }, personal: { accounts: ACCOUNTS } diff --git a/js/src/ui/AccountCard/accountCard.css b/js/src/ui/AccountCard/accountCard.css index 4799b1310..ae382374c 100644 --- a/js/src/ui/AccountCard/accountCard.css +++ b/js/src/ui/AccountCard/accountCard.css @@ -120,6 +120,9 @@ .accountName { font-weight: 700 !important; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } } diff --git a/js/src/ui/VaultCard/vaultCard.js b/js/src/ui/VaultCard/vaultCard.js index 5d04b9880..e105173e7 100644 --- a/js/src/ui/VaultCard/vaultCard.js +++ b/js/src/ui/VaultCard/vaultCard.js @@ -85,7 +85,10 @@ export default class VaultCard extends Component { { accounts.map((address) => { return ( - + - + { + const { balances } = this.props; + const balance = balances[account.address]; const makeDefaultAccount = () => { this.toggleAccountsDisplay(); return this.accountStore @@ -358,6 +361,7 @@ class ParityBar extends Component { > Object.assign({ signer: { pending: [] } }, state) + getState: () => Object.assign({ + balances: { + balances: {} + }, + signer: { + pending: [] + } + }, state) }; return store; From 5337bf6413d09aa11af27072f026e3e101611833 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 22 Feb 2017 10:43:02 +0100 Subject: [PATCH 15/51] Default account selection update (#4609) * Default accounts setting - account provider * RPC support for default accounts * Updating JS code * Rename whitelist to addresses * Set the defaults using default, allowing for null/full lists * Update failing tests (after merge) * Fix merge with wrong rpc call names * One account needs to be selected --- js/src/modals/DappPermissions/store.js | 51 ++++++++++++--------- js/src/modals/DappPermissions/store.spec.js | 20 ++++++-- js/src/views/ParityBar/accountStore.js | 13 ++---- js/src/views/ParityBar/accountStore.spec.js | 8 ++-- js/src/views/ParityBar/parityBar.test.js | 3 +- 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/js/src/modals/DappPermissions/store.js b/js/src/modals/DappPermissions/store.js index 3239343c5..0476879a5 100644 --- a/js/src/modals/DappPermissions/store.js +++ b/js/src/modals/DappPermissions/store.js @@ -20,6 +20,7 @@ export default class Store { @observable accounts = []; @observable modalOpen = false; @observable whitelist = []; + @observable whitelistDefault = null; constructor (api) { this._api = api; @@ -29,17 +30,14 @@ export default class Store { @action closeModal = () => { transaction(() => { - let addresses = null; const checkedAccounts = this.accounts.filter((account) => account.checked); - - if (checkedAccounts.length) { - addresses = checkedAccounts.filter((account) => account.default) - .concat(checkedAccounts.filter((account) => !account.default)) - .map((account) => account.address); - } + const defaultAddress = (this.accounts.find((account) => account.default) || {}).address; + const addresses = checkedAccounts.length === this.accounts.length + ? null + : checkedAccounts.map((account) => account.address); this.modalOpen = false; - this.updateWhitelist(addresses); + this.updateWhitelist(addresses, defaultAddress); }); } @@ -53,8 +51,8 @@ export default class Store { checked: this.whitelist ? this.whitelist.includes(account.address) : true, - default: this.whitelist - ? this.whitelist[0] === account.address + default: this.whitelistDefault + ? this.whitelistDefault === account.address : index === 0, description: account.meta.description, name: account.name @@ -66,8 +64,10 @@ export default class Store { @action selectAccount = (address) => { transaction(() => { + const isSingleAccount = this.accounts.filter((account) => account.checked).length === 1; + this.accounts = this.accounts.map((account) => { - if (account.address === address) { + if (account.address === address && (!isSingleAccount || !account.checked)) { account.checked = !account.checked; account.default = false; } @@ -96,26 +96,35 @@ export default class Store { }); } - @action setWhitelist = (whitelist) => { - this.whitelist = whitelist; + @action setWhitelist = (whitelist, whitelistDefault) => { + transaction(() => { + this.whitelist = whitelist; + this.whitelistDefault = whitelistDefault; + }); } loadWhitelist () { - return this._api.parity - .getNewDappsAddresses() - .then((whitelist) => { - this.setWhitelist(whitelist); + return Promise + .all([ + this._api.parity.getNewDappsAddresses(), + this._api.parity.getNewDappsDefaultAddress() + ]) + .then(([whitelist, whitelistDefault]) => { + this.setWhitelist(whitelist, whitelistDefault); }) .catch((error) => { console.warn('loadWhitelist', error); }); } - updateWhitelist (whitelist) { - return this._api.parity - .setNewDappsAddresses(whitelist) + updateWhitelist (whitelist, whitelistDefault = null) { + return Promise + .all([ + this._api.parity.setNewDappsAddresses(whitelist), + this._api.parity.setNewDappsDefaultAddress(whitelistDefault) + ]) .then(() => { - this.setWhitelist(whitelist); + this.setWhitelist(whitelist, whitelistDefault); }) .catch((error) => { console.warn('updateWhitelist', error); diff --git a/js/src/modals/DappPermissions/store.spec.js b/js/src/modals/DappPermissions/store.spec.js index 20d927484..800c8a315 100644 --- a/js/src/modals/DappPermissions/store.spec.js +++ b/js/src/modals/DappPermissions/store.spec.js @@ -32,14 +32,16 @@ function create () { api = { parity: { getNewDappsAddresses: sinon.stub().resolves(WHITELIST), - setNewDappsAddresses: sinon.stub().resolves(true) + getNewDappsDefaultAddress: sinon.stub().resolves(WHITELIST[0]), + setNewDappsAddresses: sinon.stub().resolves(true), + setNewDappsDefaultAddress: sinon.stub().resolves(true) } }; store = new Store(api); } -describe('modals/DappPermissions/store', () => { +describe.only('modals/DappPermissions/store', () => { beforeEach(() => { create(); }); @@ -80,11 +82,11 @@ describe('modals/DappPermissions/store', () => { }); it('calls setNewDappsAddresses', () => { - expect(api.parity.setNewDappsAddresses).to.have.been.calledOnce; + expect(api.parity.setNewDappsAddresses).to.have.been.calledWith(['456', '789']); }); - it('has the default account in first position', () => { - expect(api.parity.setNewDappsAddresses).to.have.been.calledWith(['789', '456']); + it('calls into setNewDappsDefaultAddress', () => { + expect(api.parity.setNewDappsDefaultAddress).to.have.been.calledWith('789'); }); }); @@ -107,6 +109,14 @@ describe('modals/DappPermissions/store', () => { expect(store.accounts.find((account) => account.address === '456').default).to.be.false; expect(store.accounts.find((account) => account.address === '123').default).to.be.true; }); + + it('does not deselect the last account', () => { + store.selectAccount('123'); + store.selectAccount('456'); + console.log(store.accounts.map((account) => ({ address: account.address, checked: account.checked }))); + expect(store.accounts.find((account) => account.address === '456').default).to.be.true; + expect(store.accounts.find((account) => account.address === '456').checked).to.be.true; + }); }); describe('setDefaultAccount', () => { diff --git a/js/src/views/ParityBar/accountStore.js b/js/src/views/ParityBar/accountStore.js index fef00a142..4c2736864 100644 --- a/js/src/views/ParityBar/accountStore.js +++ b/js/src/views/ParityBar/accountStore.js @@ -50,18 +50,11 @@ export default class AccountStore { this.isLoading = isLoading; } - makeDefaultAccount = (address) => { - const accounts = [address].concat( - this.accounts - .filter((account) => account.address !== address) - .map((account) => account.address) - ); - - // Have optimistic UI: https://www.smashingmagazine.com/2016/11/true-lies-of-optimistic-user-interfaces/?utm_source=codropscollective - this.setDefaultAccount(address); + makeDefaultAccount = (defaultAddress) => { + this.setDefaultAccount(defaultAddress); return this._api.parity - .setNewDappsAddresses(accounts) + .setNewDappsDefaultAddress(defaultAddress) .catch((error) => { console.warn('makeDefaultAccount', error); }); diff --git a/js/src/views/ParityBar/accountStore.spec.js b/js/src/views/ParityBar/accountStore.spec.js index 8b2a9a41a..16cebf9e1 100644 --- a/js/src/views/ParityBar/accountStore.spec.js +++ b/js/src/views/ParityBar/accountStore.spec.js @@ -18,7 +18,7 @@ import sinon from 'sinon'; import AccountStore from './accountStore'; -import { ACCOUNT_DEFAULT, ACCOUNT_FIRST, ACCOUNT_NEW, createApi } from './parityBar.test.js'; +import { ACCOUNT_DEFAULT, ACCOUNT_NEW, createApi } from './parityBar.test.js'; let api; let store; @@ -104,10 +104,8 @@ describe('views/ParityBar/AccountStore', () => { return store.makeDefaultAccount(ACCOUNT_NEW); }); - it('calls into parity_setNewDappsAddresses (with ordering)', () => { - expect(api.parity.setNewDappsAddresses).to.have.been.calledWith([ - ACCOUNT_NEW, ACCOUNT_FIRST, ACCOUNT_DEFAULT - ]); + it('calls into parity_setNewDappsDefaultAddress', () => { + expect(api.parity.setNewDappsDefaultAddress).to.have.been.calledWith(ACCOUNT_NEW); }); }); }); diff --git a/js/src/views/ParityBar/parityBar.test.js b/js/src/views/ParityBar/parityBar.test.js index 97c6e6251..f390424e1 100644 --- a/js/src/views/ParityBar/parityBar.test.js +++ b/js/src/views/ParityBar/parityBar.test.js @@ -37,7 +37,8 @@ function createApi () { defaultAccount: sinon.stub().resolves(ACCOUNT_DEFAULT), allAccountsInfo: sinon.stub().resolves(ACCOUNTS), getNewDappsAddresses: sinon.stub().resolves(null), - setNewDappsAddresses: sinon.stub().resolves(true) + setNewDappsAddresses: sinon.stub().resolves(true), + setNewDappsDefaultAddress: sinon.stub().resolves(true) } }; From 49675483c3fa42155eb021e04488f3e47c94737a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 22 Feb 2017 09:56:05 +0000 Subject: [PATCH 16/51] [ci skip] js-precompiled 20170222-095134 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f3e770af..ac7fa364a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1702,7 +1702,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#a7ca030c48918cef04e236cc37a9fcbe234a6443" +source = "git+https://github.com/ethcore/js-precompiled.git#c48973cfe4a2762078929b05c4b6f1103ebdeef9" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 1c424142b..df7786abd 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.95", + "version": "0.3.96", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 6938a7a2024955f61a72ac963536077d3137ac9f Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 22 Feb 2017 15:26:58 +0100 Subject: [PATCH 17/51] Convert all remaining Modals to use Portal (UI consistency) (#4625) * FirstRun dialog -> Portal * CreateAccount Modal -> Portal * CreateWallet dialog -> Portal * Transfer dialog -> Portal * Fix failing tests * ShapeShift dialog -> Portal * Verification dialog -> Portal * EditMeta dialog -> Portal * PasswordManager dialog -> Portal * WalletSettings dialog -> Portal * AddAddress dialog -> Portal * s/delete address/forget address/ * AddContract dialog -> Portal * DeployContract dialog -> Portal * ExceuteContract dialog -> Portal * LoadContract dialog -> Portal * SaveContract dialog -> Portal * UpgradeParity dialog -> Portal * Convert inline modals (tsk, tsk) * Remove ui/Modal * Import dialog i18n * Button array returns (thanks @derhuerst) * Unneeded debug * Typo * Readability formatting --- js/src/modals/AddAddress/addAddress.js | 19 +- js/src/modals/AddContract/addContract.js | 13 +- .../AccountDetails/accountDetails.js | 84 +++--- .../CreationType/creationType.js | 158 ++++++----- .../CreationType/creationType.spec.js | 4 +- .../NewAccount/newAccount.spec.js | 2 +- js/src/modals/CreateAccount/createAccount.css | 23 +- js/src/modals/CreateAccount/createAccount.js | 13 +- js/src/modals/CreateAccount/store.js | 2 + js/src/modals/CreateAccount/store.spec.js | 4 +- js/src/modals/CreateWallet/createWallet.js | 31 ++- js/src/modals/DappPermissions/store.spec.js | 2 +- .../modals/DeployContract/deployContract.js | 20 +- js/src/modals/EditMeta/editMeta.js | 21 +- .../modals/ExecuteContract/executeContract.js | 29 +- js/src/modals/FirstRun/Completed/completed.js | 18 +- js/src/modals/FirstRun/TnC/tnc.js | 12 +- js/src/modals/FirstRun/Welcome/welcome.js | 52 +++- .../FirstRun/{TnC/tnc.css => firstRun.css} | 34 ++- js/src/modals/FirstRun/firstRun.js | 13 +- js/src/modals/LoadContract/loadContract.js | 11 +- .../PasswordManager/passwordManager.css | 2 +- .../modals/PasswordManager/passwordManager.js | 22 +- js/src/modals/SaveContract/saveContract.js | 15 +- js/src/modals/Shapeshift/shapeshift.css | 26 +- js/src/modals/Shapeshift/shapeshift.js | 31 ++- js/src/modals/Transfer/transfer.js | 22 +- js/src/modals/UpgradeParity/upgradeParity.css | 20 +- js/src/modals/UpgradeParity/upgradeParity.js | 249 +++++++++++------- .../Verification/QueryCode/queryCode.js | 29 +- js/src/modals/Verification/verification.js | 220 ++++++++++++---- .../modals/WalletSettings/walletSettings.js | 143 +++++++--- js/src/ui/Actionbar/Import/import.js | 103 ++++++-- js/src/ui/Icons/index.js | 4 + js/src/ui/Modal/index.js | 2 - js/src/ui/Modal/modal.css | 55 ---- js/src/ui/Modal/modal.js | 121 --------- js/src/ui/Portal/portal.example.js | 9 - js/src/ui/Portal/portal.js | 7 +- js/src/ui/Theme/theme.js | 2 + js/src/ui/VaultCard/vaultCard.js | 1 - js/src/ui/index.js | 2 +- js/src/views/Accounts/accounts.js | 12 +- js/src/views/Address/address.js | 2 +- js/src/views/Contract/contract.js | 11 +- 45 files changed, 1009 insertions(+), 666 deletions(-) rename js/src/modals/FirstRun/{TnC/tnc.css => firstRun.css} (71%) delete mode 100644 js/src/ui/Modal/modal.css delete mode 100644 js/src/ui/Modal/modal.js diff --git a/js/src/modals/AddAddress/addAddress.js b/js/src/modals/AddAddress/addAddress.js index 750e7f8b8..78633c52c 100644 --- a/js/src/modals/AddAddress/addAddress.js +++ b/js/src/modals/AddAddress/addAddress.js @@ -14,13 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import ContentAdd from 'material-ui/svg-icons/content/add'; -import ContentClear from 'material-ui/svg-icons/content/clear'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; -import { Button, Form, Input, InputAddress, Modal } from '~/ui'; +import { Button, Form, Input, InputAddress, Portal } from '~/ui'; +import { AddIcon, CancelIcon } from '~/ui/Icons'; import Store from './store'; @@ -46,8 +45,10 @@ export default class AddAddress extends Component { render () { return ( - { this.renderFields() } - + ); } @@ -66,7 +67,7 @@ export default class AddAddress extends Component { return ([
); - this.props.onClose(); + this.onClose(); } }) .catch((error) => { diff --git a/js/src/modals/SaveContract/saveContract.js b/js/src/modals/SaveContract/saveContract.js index cd9285336..242ba1593 100644 --- a/js/src/modals/SaveContract/saveContract.js +++ b/js/src/modals/SaveContract/saveContract.js @@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react'; import SaveIcon from 'material-ui/svg-icons/content/save'; import ContentClear from 'material-ui/svg-icons/content/clear'; -import { Button, Modal, Form, Input } from '~/ui'; +import { Button, Form, Input, Portal } from '~/ui'; import Editor from '~/ui/Editor'; import { ERRORS, validateName } from '~/util/validation'; @@ -42,10 +42,11 @@ export default class SaveContract extends Component { const { name, nameError } = this.state; return ( -
@@ -60,11 +61,11 @@ export default class SaveContract extends Component {
-
+ ); } @@ -72,6 +73,7 @@ export default class SaveContract extends Component { const cancelBtn = (
- ); + return this.renderStepInfo(newversion, currentversion); case STEP_UPDATING: - return ( - { newversion }
- } } - /> - } - /> - ); + return this.renderStepBusy(newversion); case STEP_COMPLETED: case STEP_ERROR: - if (store.error) { - return ( - -
+ return store.error + ? this.renderStepError(newversion) + : this.renderStepCompleted(newversion); + } + } + + renderStepBusy (newversion) { + return ( +
+ +
+ { newversion }
+ } } + /> +
+
+ ); + } + + renderStepCompleted (newversion) { + return ( +
+ +
+ { newversion }
+ } } + /> +
+ + ); + } + + renderStepError (newversion) { + const { store } = this.props; + + return ( +
+ +
+ { newversion }
+ } } + /> +
+ { store.error.message } +
+
+ + ); + } + + renderStepInfo (newversion, currentversion) { + return ( +
+ +
+
+ +
+
+
    +
  • { currentversion }
+ } } + /> + +
  • + { currentversion }
  • , newversion:
    { newversion }
    } } /> -
    -
    - { store.error.message } -
    -
    - ); - } - - return ( - + +
  • + { this.renderConsensusInfo() } +
  • + + +
    { newversion }
    - } } + id='upgradeParity.info.next' + defaultMessage='Proceed with "upgrade now" to start your Parity upgrade.' /> -
    - ); - } + + + + ); } renderConsensusInfo () { @@ -217,47 +268,39 @@ export default class UpgradeParity extends Component { if (consensusCapability) { if (consensusCapability === 'capable') { return ( -
    - -
    + ); } else if (consensusCapability.capableUntil) { return ( -
    - -
    + ); } else if (consensusCapability.incapableSince) { return ( -
    - -
    + ); } } return ( -
    - -
    + ); } @@ -275,4 +318,20 @@ export default class UpgradeParity extends Component { return `${version.major}.${version.minor}.${version.patch}-${track}`; } + + onClose = () => { + this.props.store.closeModal(); + } + + onDone = () => { + if (this.props.store.error) { + this.onClose(); + } else { + window.location.reload(); + } + } + + onUpgrade = () => { + this.props.store.upgradeNow(); + } } diff --git a/js/src/modals/Verification/QueryCode/queryCode.js b/js/src/modals/Verification/QueryCode/queryCode.js index 47e87e7f7..ded2cd224 100644 --- a/js/src/modals/Verification/QueryCode/queryCode.js +++ b/js/src/modals/Verification/QueryCode/queryCode.js @@ -15,19 +15,26 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Form, Input } from '~/ui'; +import { nodeOrStringProptype } from '~/util/proptypes'; export default class QueryCode extends Component { static propTypes = { receiver: PropTypes.string.isRequired, - hint: PropTypes.string, + hint: nodeOrStringProptype(), isCodeValid: PropTypes.bool.isRequired, setCode: PropTypes.func.isRequired } static defaultProps = { - hint: 'Enter the code you received.' + hint: ( + + ) } render () { @@ -37,9 +44,23 @@ export default class QueryCode extends Component {

    The verification code has been sent to { receiver }.

    + } hint={ hint } - error={ isCodeValid ? null : 'invalid code' } + error={ + isCodeValid + ? null + : ( + + ) + } onChange={ this.onChange } onSubmit={ this.onSubmit } /> diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index 37166b4af..b57f9a9dd 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -22,7 +22,7 @@ import { observable } from 'mobx'; import DoneIcon from 'material-ui/svg-icons/action/done-all'; import CancelIcon from 'material-ui/svg-icons/content/clear'; -import { Button, IdentityIcon, Modal } from '~/ui'; +import { Button, IdentityIcon, Portal } from '~/ui'; import RadioButtons from '~/ui/Form/RadioButtons'; import SMSVerificationStore from './sms-store'; @@ -30,17 +30,72 @@ import EmailVerificationStore from './email-store'; import styles from './verification.css'; -const methods = { +const METHODS = { sms: { - label: 'SMS Verification', key: 0, value: 'sms', - description: (

    It will be stored on the blockchain that you control a phone number (not which).

    ) + label: ( + + ), + key: 0, + value: 'sms', + description: ( +

    + +

    + ) }, email: { - label: 'E-mail Verification', key: 1, value: 'email', - description: (

    The hash of the e-mail address you prove control over will be stored on the blockchain.

    ) + label: ( + + ), + key: 1, + value: 'email', + description: ( +

    + +

    + ) } }; +const STEPS = [ + , + , + , + , + , + +]; + import { LOADING, QUERY_DATA, @@ -83,6 +138,7 @@ class Verification extends Component { @observable store = null; render () { + const { onClose } = this.props; const store = this.store; let phase = 0; let error = false; @@ -95,16 +151,26 @@ class Verification extends Component { } return ( - + } > { this.renderStep(phase, error) } - + ); } @@ -112,32 +178,40 @@ class Verification extends Component { const { account, onClose } = this.props; const store = this.store; - const cancel = ( + const cancelButton = (